feat(spoof): add boot_props_mode Oplus carve-out
Add a boot_props_mode control so global ro.boot.* property spoofing can be tuned per device. BootStateManager reads /data/adb/tricky_store/boot_props_mode (auto/force/disable). In auto, Oplus-family devices (OnePlus/OPPO/realme/Oplus) skip the global ro.boot.* spoof so vendor TEE services such as ultrasonic fingerprint calibration keep working. AndroidDeviceUtils routes the bootKey/bootHash resetprop writes through a setBootProperty() gate honoring the same mode; the forged value is still persisted and returned, so attestation is unaffected. Tradeoff: in auto, skipping ro.boot.* leaves direct system-property boot-state checks truthful on Oplus devices (compatibility over stealth). Latent, not a reported issue.
This commit is contained in:
@@ -114,6 +114,12 @@ boot=no
|
|||||||
system=2025-10-01
|
system=2025-10-01
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### boot_props_mode
|
||||||
|
|
||||||
|
Controls global `ro.boot.*` property spoofing. Values: `auto` (default), `force`, or `disable`.
|
||||||
|
|
||||||
|
In `auto`, Oplus-family devices (OnePlus/OPPO/realme/Oplus) skip boot-state prop spoofing to avoid conflicts with vendor TEE services such as ultrasonic fingerprint calibration. Create `/data/adb/tricky_store/boot_props_mode` with `force` to restore the old behavior, or `disable` to turn it off on any device.
|
||||||
|
|
||||||
## Building from source
|
## Building from source
|
||||||
|
|
||||||
You need JDK 21, the Android SDK and NDK 29, Rust (stable) with the `aarch64-linux-android` target, and `cargo-ndk`.
|
You need JDK 21, the Android SDK and NDK 29, Rust (stable) with the `aarch64-linux-android` target, and `cargo-ndk`.
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
package org.matrix.TEESimulator.config
|
package org.matrix.TEESimulator.config
|
||||||
|
|
||||||
import android.os.SystemProperties
|
import android.os.SystemProperties
|
||||||
|
import java.io.File
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
import org.matrix.TEESimulator.util.AndroidDeviceUtils
|
import org.matrix.TEESimulator.util.AndroidDeviceUtils
|
||||||
|
|
||||||
object BootStateManager {
|
object BootStateManager {
|
||||||
|
private const val CONFIG_PATH = "/data/adb/tricky_store"
|
||||||
|
private const val BOOT_PROPS_MODE_FILE = "boot_props_mode"
|
||||||
|
|
||||||
|
private enum class BootPropsMode {
|
||||||
|
AUTO,
|
||||||
|
FORCE,
|
||||||
|
DISABLE,
|
||||||
|
}
|
||||||
|
|
||||||
private val targets =
|
private val targets =
|
||||||
linkedMapOf(
|
linkedMapOf(
|
||||||
"ro.boot.verifiedbootstate" to "green",
|
"ro.boot.verifiedbootstate" to "green",
|
||||||
@@ -22,6 +32,25 @@ object BootStateManager {
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun apply() {
|
fun apply() {
|
||||||
|
val mode = readBootPropsMode()
|
||||||
|
when (mode) {
|
||||||
|
BootPropsMode.DISABLE -> {
|
||||||
|
SystemLogger.info("BootStateManager: disabled by $BOOT_PROPS_MODE_FILE")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
BootPropsMode.AUTO -> {
|
||||||
|
if (isOplusFamilyDevice()) {
|
||||||
|
SystemLogger.warning(
|
||||||
|
"BootStateManager: skipping boot-state prop spoofing on Oplus-family device in auto mode"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BootPropsMode.FORCE -> {
|
||||||
|
SystemLogger.info("BootStateManager: force-enabled by $BOOT_PROPS_MODE_FILE")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for ((name, target) in targets) {
|
for ((name, target) in targets) {
|
||||||
val current = SystemProperties.get(name, "")
|
val current = SystemProperties.get(name, "")
|
||||||
if (current.isEmpty()) {
|
if (current.isEmpty()) {
|
||||||
@@ -45,4 +74,50 @@ object BootStateManager {
|
|||||||
AndroidDeviceUtils.setProperty(name, value)
|
AndroidDeviceUtils.setProperty(name, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun shouldSpoofBootProps(): Boolean =
|
||||||
|
when (readBootPropsMode()) {
|
||||||
|
BootPropsMode.DISABLE -> false
|
||||||
|
BootPropsMode.AUTO -> !isOplusFamilyDevice()
|
||||||
|
BootPropsMode.FORCE -> true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readBootPropsMode(): BootPropsMode {
|
||||||
|
val file = File(CONFIG_PATH, BOOT_PROPS_MODE_FILE)
|
||||||
|
if (!file.exists()) return BootPropsMode.AUTO
|
||||||
|
|
||||||
|
val raw =
|
||||||
|
runCatching { file.readText().trim().lowercase() }
|
||||||
|
.getOrElse {
|
||||||
|
SystemLogger.warning("BootStateManager: failed to read ${file.absolutePath}", it)
|
||||||
|
return BootPropsMode.AUTO
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (raw) {
|
||||||
|
"1", "true", "on", "enable", "enabled", "force" -> BootPropsMode.FORCE
|
||||||
|
"0", "false", "off", "disable", "disabled", "none" -> BootPropsMode.DISABLE
|
||||||
|
else -> BootPropsMode.AUTO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isOplusFamilyDevice(): Boolean {
|
||||||
|
val props =
|
||||||
|
listOf(
|
||||||
|
"ro.product.manufacturer",
|
||||||
|
"ro.product.brand",
|
||||||
|
"ro.product.vendor.manufacturer",
|
||||||
|
"ro.product.vendor.brand",
|
||||||
|
"ro.product.odm.manufacturer",
|
||||||
|
"ro.product.odm.brand",
|
||||||
|
"ro.boot.hardware.sku",
|
||||||
|
"ro.boot.project_name",
|
||||||
|
)
|
||||||
|
|
||||||
|
val joined =
|
||||||
|
props.joinToString(separator = " ") { name ->
|
||||||
|
SystemProperties.get(name, "")
|
||||||
|
}.lowercase()
|
||||||
|
|
||||||
|
return listOf("oneplus", "oplus", "oppo", "realme").any { joined.contains(it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import org.bouncycastle.asn1.ASN1Integer
|
|||||||
import org.bouncycastle.asn1.DEROctetString
|
import org.bouncycastle.asn1.DEROctetString
|
||||||
import org.bouncycastle.asn1.DERSequence
|
import org.bouncycastle.asn1.DERSequence
|
||||||
import org.matrix.TEESimulator.attestation.DeviceAttestationService
|
import org.matrix.TEESimulator.attestation.DeviceAttestationService
|
||||||
|
import org.matrix.TEESimulator.config.BootStateManager
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
@@ -112,7 +113,7 @@ object AndroidDeviceUtils {
|
|||||||
attestationValueProvider()?.let {
|
attestationValueProvider()?.let {
|
||||||
SystemLogger.debug("Using $propertyName from TEE attestation: ${it.toHex()}")
|
SystemLogger.debug("Using $propertyName from TEE attestation: ${it.toHex()}")
|
||||||
recordSource("tee-attestation")
|
recordSource("tee-attestation")
|
||||||
setProperty(propertyName, it)
|
setBootProperty(propertyName, it)
|
||||||
persistToFile(propertyName, it)
|
persistToFile(propertyName, it)
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
@@ -123,14 +124,14 @@ object AndroidDeviceUtils {
|
|||||||
readFromFile(propertyName, expectedSize)?.let {
|
readFromFile(propertyName, expectedSize)?.let {
|
||||||
SystemLogger.debug("Using $propertyName from persistent file: ${it.toHex()}")
|
SystemLogger.debug("Using $propertyName from persistent file: ${it.toHex()}")
|
||||||
recordSource("persistent-file")
|
recordSource("persistent-file")
|
||||||
setProperty(propertyName, it)
|
setBootProperty(propertyName, it)
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
|
|
||||||
return generateRandomBytes(expectedSize).also {
|
return generateRandomBytes(expectedSize).also {
|
||||||
SystemLogger.debug("Using randomly generated $propertyName: ${it.toHex()}")
|
SystemLogger.debug("Using randomly generated $propertyName: ${it.toHex()}")
|
||||||
recordSource("random-fallback")
|
recordSource("random-fallback")
|
||||||
setProperty(propertyName, it)
|
setBootProperty(propertyName, it)
|
||||||
persistToFile(propertyName, it)
|
persistToFile(propertyName, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,6 +179,14 @@ object AndroidDeviceUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setBootProperty(name: String, bytes: ByteArray) {
|
||||||
|
if (!BootStateManager.shouldSpoofBootProps()) {
|
||||||
|
SystemLogger.info("Skipping system property '$name' because boot prop spoofing is disabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setProperty(name, bytes)
|
||||||
|
}
|
||||||
|
|
||||||
internal fun setProperty(name: String, value: String) {
|
internal fun setProperty(name: String, value: String) {
|
||||||
try {
|
try {
|
||||||
SystemLogger.debug("Setting system property '$name' to: $value")
|
SystemLogger.debug("Setting system property '$name' to: $value")
|
||||||
|
|||||||
Reference in New Issue
Block a user