Improve logging to understand detection methods (#14)

Via extensive and detailed logging, we can inspect various detection techniques of target packages.
This commit is contained in:
JingMatrix
2025-11-26 11:43:53 +01:00
committed by GitHub
parent fa1d9ecc56
commit 7f94ba4b5b
4 changed files with 61 additions and 27 deletions
@@ -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<Certificate>?): 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
}
}
}
@@ -30,6 +30,17 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
private val DELETE_KEY_TRANSACTION =
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "deleteKey")
private val transactionNames: Map<Int, String> 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)
@@ -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<Int, String> 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<KeyIdentifier, GeneratedKeyInfo>()
// A set to quickly identify keys that were generated for attestation purposes.
@@ -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"