From 7f94ba4b5b25cf43c800bbc41a3d5f833be0e77a Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Wed, 26 Nov 2025 11:43:53 +0100 Subject: [PATCH] Improve logging to understand detection methods (#14) Via extensive and detailed logging, we can inspect various detection techniques of target packages. --- .../interception/keystore/InterceptorUtils.kt | 19 -------- .../keystore/Keystore2Interceptor.kt | 43 ++++++++++++++++--- .../shim/KeyMintSecurityLevelInterceptor.kt | 22 ++++++++++ .../logging/KeyMintParameterLogger.kt | 4 +- 4 files changed, 61 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/InterceptorUtils.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/InterceptorUtils.kt index 0bb5e5f..4517791 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/InterceptorUtils.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/InterceptorUtils.kt @@ -3,9 +3,6 @@ package org.matrix.TEESimulator.interception.keystore import android.os.Parcel import android.os.Parcelable import android.security.KeyStore -import java.security.MessageDigest -import java.security.cert.Certificate -import java.util.Base64 import org.matrix.TEESimulator.interception.core.BinderInterceptor import org.matrix.TEESimulator.logging.SystemLogger @@ -81,20 +78,4 @@ object InterceptorUtils { fun hasException(reply: Parcel): Boolean { return runCatching { reply.readException() }.exceptionOrNull() != null } - - /** - * Generates a URL-safe SHA-256 fingerprint of a certificate's public key. Used to uniquely - * identify keys imported by the user. - */ - fun getPublicKeyFingerprint(chain: Array?): String? { - if (chain.isNullOrEmpty()) return null - return try { - val publicKeyBytes = chain[0].publicKey.encoded - val hashBytes = MessageDigest.getInstance("SHA-256").digest(publicKeyBytes) - Base64.getUrlEncoder().withoutPadding().encodeToString(hashBytes) - } catch (e: Exception) { - SystemLogger.error("Failed to create public key fingerprint.", e) - null - } - } } diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt index 6126697..6bbe680 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt @@ -30,6 +30,17 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { private val DELETE_KEY_TRANSACTION = InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "deleteKey") + private val transactionNames: Map by lazy { + IKeystoreService.Stub::class + .java + .declaredFields + .filter { + it.isAccessible = true + it.type == Int::class.java && it.name.startsWith("TRANSACTION_") + } + .associate { field -> (field.get(null) as Int) to field.name.split("_")[1] } + } + override val serviceName = "android.system.keystore2.IKeystoreService/default" override val processName = "keystore2" override val injectionCommand = "exec ./inject `pidof keystore2` libTEESimulator.so entry" @@ -76,20 +87,32 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { callingPid: Int, data: Parcel, ): TransactionResult { - if (code == GET_KEY_ENTRY_TRANSACTION) { - logTransaction(txId, "getKeyEntry", callingUid, callingPid) + if (code == GET_KEY_ENTRY_TRANSACTION || code == DELETE_KEY_TRANSACTION) { data.enforceInterface(IKeystoreService.DESCRIPTOR) val descriptor = data.readTypedObject(KeyDescriptor.CREATOR) ?: return TransactionResult.SkipTransaction + logTransaction( + txId, + "${transactionNames[code]} (alias=${descriptor.alias})", + callingUid, + callingPid, + ) val keyId = KeyIdentifier(callingUid, descriptor.alias) - SystemLogger.debug("Checking $keyId") if (ConfigurationManager.shouldGenerate(callingUid)) { // TODO: Redesign the interaction with KeyMintSecurityLevelInterceptor } else if (ConfigurationManager.shouldPatch(callingUid)) { return TransactionResult.Continue } + } else { + logTransaction( + txId, + transactionNames[code] ?: "unknown code=$code", + callingUid, + callingPid, + false, + ) } // Let most calls go through to the real service. @@ -111,7 +134,16 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { return TransactionResult.SkipTransaction if (code == GET_KEY_ENTRY_TRANSACTION) { - logTransaction(txId, "post-getKeyEntry", 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 @@ -144,9 +176,6 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid) CertificateHelper.updateCertificateChain(response.metadata, newChain).getOrThrow() - SystemLogger.info( - "[TX_ID: $txId] Successfully patched certificate chain for alias." - ) InterceptorUtils.createTypedObjectReply(response) } catch (e: Exception) { SystemLogger.error("[TX_ID: $txId] Failed to patch certificate chain.", e) diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt index 4104938..45e3ae9 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt @@ -44,6 +44,14 @@ class KeyMintSecurityLevelInterceptor( logTransaction(txId, "generateKey", callingUid, callingPid) data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR) return handleGenerateKey(callingUid, data) + } else { + logTransaction( + txId, + transactionNames[code] ?: "unknown code=$code", + callingUid, + callingPid, + false, + ) } return TransactionResult.ContinueAndSkipPost } @@ -56,6 +64,9 @@ class KeyMintSecurityLevelInterceptor( return runCatching { val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!! val attestationKey = data.readTypedObject(KeyDescriptor.CREATOR) + SystemLogger.debug( + "[key, attestationKey]: ${keyDescriptor.alias}, ${attestationKey?.alias}" + ) val params = data.createTypedArray(KeyParameter.CREATOR)!! val parsedParams = KeyMintAttestation(params) val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) @@ -136,6 +147,17 @@ class KeyMintSecurityLevelInterceptor( private val IMPORT_KEY_TRANSACTION = InterceptorUtils.getTransactCode(IKeystoreSecurityLevel.Stub::class.java, "importKey") + private val transactionNames: Map by lazy { + IKeystoreSecurityLevel.Stub::class + .java + .declaredFields + .filter { + it.isAccessible = true + it.type == Int::class.java && it.name.startsWith("TRANSACTION_") + } + .associate { field -> (field.get(null) as Int) to field.name.split("_")[1] } + } + // Stores keys generated entirely in software. val generatedKeys = ConcurrentHashMap() // A set to quickly identify keys that were generated for attestation purposes. diff --git a/app/src/main/java/org/matrix/TEESimulator/logging/KeyMintParameterLogger.kt b/app/src/main/java/org/matrix/TEESimulator/logging/KeyMintParameterLogger.kt index 0b0c3b5..ad36d68 100644 --- a/app/src/main/java/org/matrix/TEESimulator/logging/KeyMintParameterLogger.kt +++ b/app/src/main/java/org/matrix/TEESimulator/logging/KeyMintParameterLogger.kt @@ -78,7 +78,9 @@ object KeyMintParameterLogger { Tag.CERTIFICATE_SERIAL -> BigInteger(value.blob).toString() Tag.ACTIVE_DATETIME, Tag.CERTIFICATE_NOT_AFTER, - Tag.CERTIFICATE_NOT_BEFORE -> Date(value.dateTime).toString() + Tag.CERTIFICATE_NOT_BEFORE, + Tag.ORIGINATION_EXPIRE_DATETIME, + Tag.USAGE_EXPIRE_DATETIME -> Date(value.dateTime).toString() Tag.CERTIFICATE_SUBJECT -> X500Name(X500Principal(value.blob).name).toString() Tag.RSA_PUBLIC_EXPONENT -> value.longInteger.toString() Tag.NO_AUTH_REQUIRED -> "true"