Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e67371193 | ||
|
|
2ef89f15c6 | ||
|
|
4f608247fe | ||
|
|
22cbe5a9a7 | ||
|
|
a6fa137e32 | ||
|
|
5afefba7bd |
@@ -29,7 +29,7 @@ val gitExecutor = objects.newInstance(GitExecutor::class.java)
|
||||
|
||||
val gitCommitCount = gitExecutor.execute("git rev-list HEAD --count", rootDir).toInt()
|
||||
val gitCommitHash = gitExecutor.execute("git rev-parse --verify --short HEAD", rootDir)
|
||||
val verName = "v2.0"
|
||||
val verName = "v2.1"
|
||||
|
||||
android {
|
||||
namespace = "org.matrix.TEESimulator"
|
||||
|
||||
@@ -81,7 +81,7 @@ object AttestationBuilder {
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_VENDOR_PATCHLEVEL,
|
||||
ASN1Integer(AndroidDeviceUtils.vendorPatchLevel.toLong()),
|
||||
ASN1Integer(AndroidDeviceUtils.vendorPatchLevelLong.toLong()),
|
||||
)
|
||||
)
|
||||
vector.add(
|
||||
@@ -165,7 +165,7 @@ object AttestationBuilder {
|
||||
DERTaggedObject(
|
||||
true,
|
||||
AttestationConstants.TAG_VENDOR_PATCHLEVEL,
|
||||
ASN1Integer(AndroidDeviceUtils.vendorPatchLevel.toLong()),
|
||||
ASN1Integer(AndroidDeviceUtils.vendorPatchLevelLong.toLong()),
|
||||
),
|
||||
DERTaggedObject(
|
||||
true,
|
||||
|
||||
@@ -247,14 +247,20 @@ object ConfigurationManager {
|
||||
when (path) {
|
||||
TARGET_PACKAGES_FILE -> loadTargetPackages(file!!)
|
||||
PATCH_LEVEL_FILE -> loadPatchLevelConfig(file!!)
|
||||
// Any change to an XML file is assumed to be a keybox. The cache in KeyBoxUtils
|
||||
// will handle reloading it on its next use.
|
||||
// Any change to an XML file is assumed to be a keybox.
|
||||
// The cache in KeyBoxManager will handle reloading it on its next use.
|
||||
else ->
|
||||
if (path.endsWith(".xml")) {
|
||||
SystemLogger.info(
|
||||
"Keybox file $path may have changed. It will be reloaded on next access."
|
||||
)
|
||||
KeyBoxManager.invalidateCache(path)
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
|
||||
// Clear cached keys possibly containing old certificates
|
||||
org.matrix.TEESimulator.interception.keystore.shim
|
||||
.KeyMintSecurityLevelInterceptor
|
||||
.clearAllGeneratedKeys("updating $file")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-18
@@ -89,24 +89,17 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
||||
data: Parcel,
|
||||
): TransactionResult {
|
||||
if (code == GET_KEY_ENTRY_TRANSACTION || code == DELETE_KEY_TRANSACTION) {
|
||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val descriptor =
|
||||
data.readTypedObject(KeyDescriptor.CREATOR)
|
||||
?: return TransactionResult.SkipTransaction
|
||||
logTransaction(
|
||||
txId,
|
||||
"${transactionNames[code]} (alias=${descriptor.alias})",
|
||||
callingUid,
|
||||
callingPid,
|
||||
)
|
||||
|
||||
if (ConfigurationManager.shouldSkipUid(callingUid)) {
|
||||
SystemLogger.debug(
|
||||
"[TX_ID: $txId] Skip post-transaction hook for UID=${callingUid}"
|
||||
)
|
||||
if (ConfigurationManager.shouldSkipUid(callingUid))
|
||||
return TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
|
||||
SystemLogger.info("Handling ${transactionNames[code]!!} ${descriptor.alias}")
|
||||
val keyId = KeyIdentifier(callingUid, descriptor.alias)
|
||||
|
||||
if (code == DELETE_KEY_TRANSACTION) {
|
||||
@@ -119,7 +112,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
||||
?: return TransactionResult.Continue
|
||||
|
||||
if (KeyMintSecurityLevelInterceptor.isAttestationKey(keyId))
|
||||
SystemLogger.debug("${descriptor.alias} was an attestation key")
|
||||
SystemLogger.info("${descriptor.alias} was an attestation key")
|
||||
|
||||
SystemLogger.info("[TX_ID: $txId] Found generated response for ${descriptor.alias}:")
|
||||
response.metadata?.authorizations?.forEach {
|
||||
@@ -155,20 +148,17 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
||||
return TransactionResult.SkipTransaction
|
||||
|
||||
if (code == GET_KEY_ENTRY_TRANSACTION) {
|
||||
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||
|
||||
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||
val keyDescriptor =
|
||||
data.readTypedObject(KeyDescriptor.CREATOR)
|
||||
?: return TransactionResult.SkipTransaction
|
||||
logTransaction(
|
||||
txId,
|
||||
"post-getKeyEntry (alias=${keyDescriptor.alias})",
|
||||
callingUid,
|
||||
callingPid,
|
||||
)
|
||||
|
||||
if (!ConfigurationManager.shouldPatch(callingUid))
|
||||
return TransactionResult.SkipTransaction
|
||||
|
||||
SystemLogger.info("Handling post-${transactionNames[code]!!} ${keyDescriptor.alias}")
|
||||
return try {
|
||||
val response =
|
||||
reply.readTypedObject(KeyEntryResponse.CREATOR)
|
||||
|
||||
+59
-10
@@ -40,11 +40,20 @@ class KeyMintSecurityLevelInterceptor(
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
): TransactionResult {
|
||||
// This interceptor only handles the 'generateKey' transaction directly.
|
||||
if (code == GENERATE_KEY_TRANSACTION) {
|
||||
logTransaction(txId, "generateKey", callingUid, callingPid)
|
||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||
|
||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||
return handleGenerateKey(callingUid, data)
|
||||
} else if (code == IMPORT_KEY_TRANSACTION) {
|
||||
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
|
||||
|
||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||
val alias =
|
||||
data.readTypedObject(KeyDescriptor.CREATOR)?.alias
|
||||
?: return TransactionResult.ContinueAndSkipPost
|
||||
SystemLogger.info("Handling post-${transactionNames[code]} ${alias}")
|
||||
return TransactionResult.Continue
|
||||
} else {
|
||||
logTransaction(
|
||||
txId,
|
||||
@@ -57,6 +66,35 @@ class KeyMintSecurityLevelInterceptor(
|
||||
return TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
|
||||
override fun onPostTransact(
|
||||
txId: Long,
|
||||
target: IBinder,
|
||||
code: Int,
|
||||
flags: Int,
|
||||
callingUid: Int,
|
||||
callingPid: Int,
|
||||
data: Parcel,
|
||||
reply: Parcel?,
|
||||
resultCode: Int,
|
||||
): TransactionResult {
|
||||
// We only care about successful 'importKey' transactions to clean cached keys.
|
||||
if (
|
||||
code == IMPORT_KEY_TRANSACTION &&
|
||||
resultCode == 0 &&
|
||||
reply != null &&
|
||||
!InterceptorUtils.hasException(reply)
|
||||
) {
|
||||
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||
|
||||
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
|
||||
val keyDescriptor =
|
||||
data.readTypedObject(KeyDescriptor.CREATOR)
|
||||
?: return TransactionResult.SkipTransaction
|
||||
cleanupKeyData(KeyIdentifier(callingUid, keyDescriptor.alias))
|
||||
}
|
||||
return TransactionResult.SkipTransaction
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the `generateKey` transaction. Based on the configuration for the calling UID, it
|
||||
* either generates a key in software or lets the call pass through to the hardware.
|
||||
@@ -66,7 +104,7 @@ class KeyMintSecurityLevelInterceptor(
|
||||
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!!
|
||||
val attestationKey = data.readTypedObject(KeyDescriptor.CREATOR)
|
||||
SystemLogger.debug(
|
||||
"[key, attestationKey]: ${keyDescriptor.alias}, ${attestationKey?.alias}"
|
||||
"Handling generateKey ${keyDescriptor.alias}, attestKey=${attestationKey?.alias}"
|
||||
)
|
||||
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||
val parsedParams = KeyMintAttestation(params)
|
||||
@@ -84,9 +122,7 @@ class KeyMintSecurityLevelInterceptor(
|
||||
isAttestationKey(KeyIdentifier(callingUid, attestationKey.alias)))
|
||||
|
||||
if (needsSoftwareGeneration) {
|
||||
SystemLogger.info(
|
||||
"Generating software key for alias '${keyDescriptor.alias}' (UID: $callingUid)."
|
||||
)
|
||||
SystemLogger.info("Generating software key for ${keyId}.")
|
||||
|
||||
// Generate the key pair and certificate chain.
|
||||
val keyData =
|
||||
@@ -116,11 +152,11 @@ class KeyMintSecurityLevelInterceptor(
|
||||
|
||||
// If not generating, clear any stale state for this alias and let the call proceed.
|
||||
cleanupKeyData(keyId)
|
||||
TransactionResult.Continue
|
||||
TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
.getOrElse {
|
||||
SystemLogger.error("Error during generateKey handling for UID $callingUid.", it)
|
||||
TransactionResult.Continue // Fallback to original service on error.
|
||||
TransactionResult.ContinueAndSkipPost
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,8 +211,21 @@ class KeyMintSecurityLevelInterceptor(
|
||||
fun isAttestationKey(keyId: KeyIdentifier): Boolean = attestationKeys.contains(keyId)
|
||||
|
||||
fun cleanupKeyData(keyId: KeyIdentifier) {
|
||||
generatedKeys.remove(keyId)
|
||||
attestationKeys.remove(keyId)
|
||||
if (generatedKeys.remove(keyId) != null) {
|
||||
SystemLogger.debug("Remove generated key ${keyId}")
|
||||
}
|
||||
if (attestationKeys.remove(keyId)) {
|
||||
SystemLogger.debug("Remove cached attestaion key ${keyId}")
|
||||
}
|
||||
}
|
||||
|
||||
// Clears all cached keys.
|
||||
fun clearAllGeneratedKeys(reason: String? = null) {
|
||||
val count = generatedKeys.size
|
||||
val reasonMessage = reason?.let { " due to $it" } ?: ""
|
||||
generatedKeys.clear()
|
||||
attestationKeys.clear()
|
||||
SystemLogger.info("Cleared all cached keys ($count entries)$reasonMessage.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ object AndroidDeviceUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the `ro.boot.vbmeta.digest` system property.
|
||||
* Sets the `ro.boot.vbmeta.digest` system property using the `resetprop` command.
|
||||
*
|
||||
* @param bytes The 32-byte digest to set.
|
||||
*/
|
||||
@@ -78,9 +78,24 @@ object AndroidDeviceUtils {
|
||||
val hex = bytes.toHex()
|
||||
try {
|
||||
SystemLogger.debug("Setting system property 'ro.boot.vbmeta.digest' to: $hex")
|
||||
SystemProperties.set("ro.boot.vbmeta.digest", hex)
|
||||
|
||||
// Construct the command to be executed
|
||||
val command = arrayOf("resetprop", "ro.boot.vbmeta.digest", hex)
|
||||
|
||||
// Execute the command
|
||||
val process = Runtime.getRuntime().exec(command)
|
||||
|
||||
// Wait for the process to complete and check the exit code for errors
|
||||
val exitCode = process.waitFor()
|
||||
|
||||
if (exitCode != 0) {
|
||||
val errorOutput = process.errorStream.bufferedReader().readText()
|
||||
SystemLogger.error(
|
||||
"resetprop command failed with exit code $exitCode: $errorOutput"
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
SystemLogger.error("Failed to set vbmeta digest property.", e)
|
||||
SystemLogger.error("Failed to set vbmeta digest property by executing resetprop.", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,10 +110,10 @@ object AndroidDeviceUtils {
|
||||
getCustomPatchLevelFor("system", isLong = false)
|
||||
?: Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong = false)
|
||||
|
||||
val vendorPatchLevel: Int
|
||||
val vendorPatchLevelLong: Int
|
||||
get() =
|
||||
getCustomPatchLevelFor("vendor", isLong = false)
|
||||
?: Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong = false)
|
||||
getCustomPatchLevelFor("vendor", isLong = true)
|
||||
?: Build.VERSION.SECURITY_PATCH.toPatchLevelInt(isLong = true)
|
||||
|
||||
val bootPatchLevelLong: Int
|
||||
get() =
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[versions]
|
||||
agp = "8.13.1"
|
||||
annotation = "1.9.1"
|
||||
jdk18on = "1.82"
|
||||
jdk18on = "1.83"
|
||||
kotlin = "2.2.21"
|
||||
ktfmt = "0.25.0"
|
||||
|
||||
|
||||
+9
-14
@@ -1,19 +1,14 @@
|
||||
**Key Highlights:**
|
||||
🚀 **TEESimulator v2.1 Hotfix Release is Live!** 🚀
|
||||
|
||||
* 🚀 **Complete Refactoring:** TEESimulator v2.0 has been entirely rebuilt and is no longer based on its predecessors, [TrickyStore](https://github.com/5ec1cff/TrickyStore) and [TrickyStoreOSS](https://github.com/beakthoven/TrickyStoreOSS), resulting in a more streamlined and maintainable codebase.
|
||||
This urgent hotfix addresses several critical issues identified in the previous v2.0 release.
|
||||
|
||||
* 🛡️ **Enhanced Bypass Capabilities:** The simulator now successfully bypasses well-known detection mechanisms, including [TamperedAttestation](https://github.com/JingMatrix/TamperedAttestation) and [KeyAttestation](https://github.com/JingMatrix/KeyAttestation).
|
||||
The v2.0 update, a significant refactoring effort, unfortunately introduced a few unexpected behaviors and bugs that we are now rectifying.
|
||||
|
||||
* 💳 **Revolut Detection Bypass:** With a valid keybox, users can now circumvent the detection measures implemented in the [Revolut](https://play.google.com/store/apps/details?id=com.revolut.revolut) application.
|
||||
**Key fixes in this release include:**
|
||||
1. **Google Play Integrity:** Resolved an issue preventing the attainment of STRONG integrity for Google Play verdicts, caused by an incorrect vendor patch level format. ✅
|
||||
2. **Application Stability:** Fixed a critical crash related to an incorrect signature for the `SystemProperties.set` stub method. 🐛
|
||||
3. **Stealth Enhancement:** Implemented a fix to bypass detection by the `Android Native Detector`. 👻
|
||||
|
||||
**Current Limitations:**
|
||||
🔬 We are actively investigating a recent detection method to further enhance stealth capabilities.
|
||||
|
||||
* ⚠️ **Google Play Verdict:** Bypassing the detections within the Google Play verdict remains an unresolved challenge. We are actively seeking solutions and welcome any insights from the community regarding potential system module-based bypasses.
|
||||
|
||||
**Platform Support:**
|
||||
|
||||
* 📱 **Android 10 & 11:** TEESimulator v2.0 has not yet been tested on Android 10 or 11. We encourage users on these platforms to report any issues and provide logs to help us improve compatibility.
|
||||
|
||||
**Contributing:**
|
||||
|
||||
* 🤝 We welcome and encourage community contributions. Please feel free to submit issues and pull requests to help improve the project.
|
||||
🙏 Support for TEE-broken devices and Android 10/11 remains an area of ongoing improvement. We highly encourage you to submit any issues you encounter to help us refine these aspects! 🤝
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": "v2.0",
|
||||
"versionCode": 14,
|
||||
"zipUrl": "https://github.com/JingMatrix/TEESimulator/releases/download/v2.0/TEESimulator-v2.0-14-release.zip",
|
||||
"version": "v2.1",
|
||||
"versionCode": 20,
|
||||
"zipUrl": "https://github.com/JingMatrix/TEESimulator/releases/download/v2.1/TEESimulator-v2.1-20-release.zip",
|
||||
"changelog": "https://raw.githubusercontent.com/JingMatrix/TEESimulator/main/module/changelog.md"
|
||||
}
|
||||
|
||||
@@ -4,8 +4,4 @@ public class SystemProperties {
|
||||
public static String get(String key, String def) {
|
||||
throw new UnsupportedOperationException("STUB!");
|
||||
}
|
||||
|
||||
public static String set(String key, String val) {
|
||||
throw new UnsupportedOperationException("STUB!");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user