diff --git a/app/src/main/cpp/binder_interceptor.cpp b/app/src/main/cpp/binder_interceptor.cpp index 7845782..ff0d66c 100644 --- a/app/src/main/cpp/binder_interceptor.cpp +++ b/app/src/main/cpp/binder_interceptor.cpp @@ -235,19 +235,21 @@ class BinderInterceptor : public BBinder { struct RegistrationEntry { wp target; sp callback_interface; + std::vector filtered_codes; }; - // Reader-Writer lock for the registry to allow concurrent reads (lookups) mutable std::shared_mutex registry_mutex_; std::map, RegistrationEntry> registry_; public: BinderInterceptor() = default; - // Checks if a specific Binder instance is currently registered for interception - bool isBinderIntercepted(const wp &target) const { + bool shouldIntercept(const wp &target, uint32_t code) const { std::shared_lock lock(registry_mutex_); - return registry_.find(target) != registry_.end(); + auto it = registry_.find(target); + if (it == registry_.end()) return false; + const auto &codes = it->second.filtered_codes; + return codes.empty() || std::find(codes.begin(), codes.end(), code) != codes.end(); } // Main entry point for processing the "Man-in-the-Middle" logic @@ -393,7 +395,7 @@ void inspectAndRewriteTransaction(binder_transaction_data *txn_data) { // This is safe because we are holding a strong reference. wp wp_target = target_binder_ptr; - if (g_interceptor_instance->isBinderIntercepted(wp_target)) { + if (g_interceptor_instance->shouldIntercept(wp_target, txn_data->code)) { info.transaction_code = txn_data->code; info.target_binder = wp_target; // Assign the valid weak pointer hijack = true; @@ -538,18 +540,29 @@ status_t BinderInterceptor::handleRegister(const Parcel &data) { if (data.readStrongBinder(&callback) != OK || !callback) return BAD_VALUE; - // We can only intercept local Binders (BBinder), not remote proxies (BpBinder) if (target->localBinder() == nullptr) { LOGE("Cannot intercept remote binder proxies."); return BAD_TYPE; } + std::vector codes; + int32_t code_count = 0; + if (data.dataAvail() >= sizeof(int32_t) && data.readInt32(&code_count) == OK && code_count > 0) { + codes.reserve(code_count); + for (int32_t i = 0; i < code_count; i++) { + uint32_t c = 0; + if (data.readUint32(&c) == OK) codes.push_back(c); + } + LOGI("Interceptor registered for binder %p with %zu filtered codes", target.get(), codes.size()); + } else { + LOGI("Interceptor registered for binder %p (all codes)", target.get()); + } + wp weak_target = target; std::unique_lock lock(registry_mutex_); - registry_[weak_target] = {weak_target, callback}; + registry_[weak_target] = {weak_target, callback, std::move(codes)}; - LOGI("Interceptor registered for binder %p", target.get()); return OK; } diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/core/BinderInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/core/BinderInterceptor.kt index 370141b..19b3b5d 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/core/BinderInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/core/BinderInterceptor.kt @@ -293,15 +293,21 @@ abstract class BinderInterceptor : Binder() { } } - /** Uses the backdoor binder to register an interceptor for a specific target service. */ - fun register(backdoor: IBinder, target: IBinder, interceptor: BinderInterceptor) { + fun register( + backdoor: IBinder, + target: IBinder, + interceptor: BinderInterceptor, + filteredCodes: IntArray = intArrayOf(), + ) { val data = Parcel.obtain() val reply = Parcel.obtain() try { data.writeStrongBinder(target) data.writeStrongBinder(interceptor) + data.writeInt(filteredCodes.size) + for (code in filteredCodes) data.writeInt(code) backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0) - SystemLogger.info("Registered interceptor for target: $target") + SystemLogger.info("Registered interceptor for target: $target (${filteredCodes.size} filtered codes)") } catch (e: Exception) { SystemLogger.error("Failed to register binder interceptor.", e) } finally { diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/AbstractKeystoreInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/AbstractKeystoreInterceptor.kt index d080fb6..c2aac08 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/AbstractKeystoreInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/AbstractKeystoreInterceptor.kt @@ -68,11 +68,12 @@ abstract class AbstractKeystoreInterceptor : BinderInterceptor() { } } - /** Registers this interceptor with the native hook layer and sets up a death recipient. */ + protected open val interceptedCodes: IntArray = intArrayOf() + private fun setupInterceptor(service: IBinder, backdoor: IBinder) { keystoreService = service SystemLogger.info("Registering interceptor for service: $serviceName") - register(backdoor, service, this) + register(backdoor, service, this, interceptedCodes) service.linkToDeath(createDeathRecipient(), 0) onInterceptorReady(service, backdoor) } 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 af6ad64..b5ccb8c 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 @@ -1,11 +1,16 @@ package org.matrix.TEESimulator.interception.keystore +import android.hardware.security.keymint.KeyParameter +import android.hardware.security.keymint.KeyParameterValue +import android.hardware.security.keymint.Tag import android.os.Parcel import android.os.Parcelable import android.security.KeyStore import android.security.keystore.KeystoreResponse +import android.system.keystore2.Authorization import org.matrix.TEESimulator.interception.core.BinderInterceptor import org.matrix.TEESimulator.logging.SystemLogger +import org.matrix.TEESimulator.util.AndroidDeviceUtils data class KeyIdentifier(val uid: Int, val alias: String) @@ -124,4 +129,53 @@ object InterceptorUtils { if (exception != null) reply.setDataPosition(0) return exception != null } + + fun createServiceSpecificErrorReply( + errorCode: Int + ): BinderInterceptor.TransactionResult.OverrideReply { + val parcel = + Parcel.obtain().apply { + writeException(android.os.ServiceSpecificException(errorCode)) + } + return BinderInterceptor.TransactionResult.OverrideReply(parcel) + } + + fun patchAuthorizations( + authorizations: Array?, + callingUid: Int, + ): Array? { + if (authorizations == null) return null + + val osPatch = AndroidDeviceUtils.getPatchLevel(callingUid) + val vendorPatch = AndroidDeviceUtils.getVendorPatchLevelLong(callingUid) + val bootPatch = AndroidDeviceUtils.getBootPatchLevelLong(callingUid) + + return authorizations + .map { auth -> + val replacement = + when (auth.keyParameter.tag) { + Tag.OS_PATCHLEVEL -> + if (osPatch != AndroidDeviceUtils.DO_NOT_REPORT) osPatch else null + Tag.VENDOR_PATCHLEVEL -> + if (vendorPatch != AndroidDeviceUtils.DO_NOT_REPORT) vendorPatch + else null + Tag.BOOT_PATCHLEVEL -> + if (bootPatch != AndroidDeviceUtils.DO_NOT_REPORT) bootPatch else null + else -> null + } + if (replacement != null) { + Authorization().apply { + keyParameter = + KeyParameter().apply { + tag = auth.keyParameter.tag + value = KeyParameterValue.integer(replacement) + } + securityLevel = auth.securityLevel + } + } else { + auth + } + } + .toTypedArray() + } } 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 a43d775..37a5d88 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 @@ -5,6 +5,7 @@ import android.hardware.security.keymint.SecurityLevel import android.os.Build import android.os.IBinder import android.os.Parcel +import android.system.keystore2.Domain import android.system.keystore2.IKeystoreService import android.system.keystore2.KeyDescriptor import android.system.keystore2.KeyEntryResponse @@ -45,6 +46,8 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { if (Build.VERSION.SDK_INT >= 34) InterceptorUtils.getTransactCode(stubBinderClass, "listEntriesBatched") else null + private val GET_NUMBER_OF_ENTRIES_TRANSACTION = + InterceptorUtils.getTransactCode(stubBinderClass, "getNumberOfEntries") private val transactionNames: Map by lazy { stubBinderClass.declaredFields @@ -57,11 +60,24 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { private const val RESPONSE_KEY_NOT_FOUND = 7 private val deletedSoftwareKeys: MutableSet = ConcurrentHashMap.newKeySet() + private val userUpdatedKeys = ConcurrentHashMap.newKeySet() override val serviceName = "android.system.keystore2.IKeystoreService/default" override val processName = "keystore2" override val injectionCommand = "exec ./inject `pidof keystore2` libTEESimulator.so entry" + override val interceptedCodes: IntArray by lazy { + listOfNotNull( + GET_KEY_ENTRY_TRANSACTION, + DELETE_KEY_TRANSACTION, + UPDATE_SUBCOMPONENT_TRANSACTION, + LIST_ENTRIES_TRANSACTION, + LIST_ENTRIES_BATCHED_TRANSACTION, + GET_NUMBER_OF_ENTRIES_TRANSACTION, + ) + .toIntArray() + } + /** * This method is called once the main service is hooked. It proceeds to find and hook the * security level sub-services (e.g., TEE, StrongBox). @@ -78,7 +94,12 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { SystemLogger.info("Found TEE SecurityLevel. Registering interceptor...") val interceptor = KeyMintSecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT) - register(backdoor, tee.asBinder(), interceptor) + register( + backdoor, + tee.asBinder(), + interceptor, + KeyMintSecurityLevelInterceptor.INTERCEPTED_CODES, + ) interceptor.loadPersistedKeys() } } @@ -90,7 +111,12 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { SystemLogger.info("Found StrongBox SecurityLevel. Registering interceptor...") val interceptor = KeyMintSecurityLevelInterceptor(strongbox, SecurityLevel.STRONGBOX) - register(backdoor, strongbox.asBinder(), interceptor) + register( + backdoor, + strongbox.asBinder(), + interceptor, + KeyMintSecurityLevelInterceptor.INTERCEPTED_CODES, + ) interceptor.loadPersistedKeys() } } @@ -106,7 +132,12 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { callingPid: Int, data: Parcel, ): TransactionResult { - if (code == LIST_ENTRIES_TRANSACTION || code == LIST_ENTRIES_BATCHED_TRANSACTION) { + if (code == GET_NUMBER_OF_ENTRIES_TRANSACTION) { + logTransaction(txId, transactionNames[code]!!, callingUid, callingPid, true) + return if (ConfigurationManager.shouldSkipUid(callingUid)) + TransactionResult.ContinueAndSkipPost + else TransactionResult.Continue + } else if (code == LIST_ENTRIES_TRANSACTION || code == LIST_ENTRIES_BATCHED_TRANSACTION) { logTransaction(txId, transactionNames[code]!!, callingUid, callingPid, true) val packages = ConfigurationManager.getPackagesForUid(callingUid).joinToString() @@ -149,29 +180,40 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { data.readTypedObject(KeyDescriptor.CREATOR) ?: return TransactionResult.ContinueAndSkipPost - if (descriptor.alias != null) { - SystemLogger.info("Handling ${transactionNames[code]!!} ${descriptor.alias}") - } else { - SystemLogger.info( - "Skip ${transactionNames[code]!!} for key [alias, blob, domain, nspace]: [${descriptor.alias}, ${descriptor.blob}, ${descriptor.domain}, ${descriptor.nspace}]" - ) - return TransactionResult.ContinueAndSkipPost - } - val keyId = KeyIdentifier(callingUid, descriptor.alias) - if (code == DELETE_KEY_TRANSACTION) { - val wasSoftwareKey = KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId) != null - KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId) - if (wasSoftwareKey) { - deletedSoftwareKeys.add(keyId) - SystemLogger.info( - "[TX_ID: $txId] Deleted cached keypair ${descriptor.alias}, replying with empty response." - ) - return InterceptorUtils.createSuccessReply(writeResultCode = false) + val keyId = + if (descriptor.alias != null) { + KeyIdentifier(callingUid, descriptor.alias) + } else if (descriptor.domain == Domain.KEY_ID) { + KeyMintSecurityLevelInterceptor.findGeneratedKeyByKeyId( + callingUid, descriptor.nspace + )?.let { info -> + KeyMintSecurityLevelInterceptor.generatedKeys.entries + .find { it.value.nspace == info.nspace && it.key.uid == callingUid } + ?.key + } + } else null + + if (keyId != null) { + val isSoftwareKey = + KeyMintSecurityLevelInterceptor.generatedKeys.containsKey(keyId) + KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId) + if (isSoftwareKey) { + deletedSoftwareKeys.add(keyId) + SystemLogger.info( + "[TX_ID: $txId] Deleted cached keypair ${keyId.alias}, replying with empty response." + ) + return InterceptorUtils.createSuccessReply(writeResultCode = false) + } } return TransactionResult.ContinueAndSkipPost } + if (descriptor.alias == null) { + return TransactionResult.ContinueAndSkipPost + } + val keyId = KeyIdentifier(callingUid, descriptor.alias) + val response = KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId) if (response == null) { if (deletedSoftwareKeys.remove(keyId)) { @@ -217,7 +259,26 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { if (target != keystoreService || reply == null || InterceptorUtils.hasException(reply)) return TransactionResult.SkipTransaction - if (code == LIST_ENTRIES_TRANSACTION || code == LIST_ENTRIES_BATCHED_TRANSACTION) { + if (code == GET_NUMBER_OF_ENTRIES_TRANSACTION) { + logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid) + return runCatching { + val hardwareCount = reply.readInt() + val softwareCount = + KeyMintSecurityLevelInterceptor.generatedKeys.keys.count { + it.uid == callingUid + } + val totalCount = hardwareCount + softwareCount + val parcel = Parcel.obtain().apply { + writeNoException() + writeInt(totalCount) + } + TransactionResult.OverrideReply(parcel) + } + .getOrElse { + SystemLogger.error("[TX_ID: $txId] Failed to modify getNumberOfEntries.", it) + TransactionResult.SkipTransaction + } + } else if (code == LIST_ENTRIES_TRANSACTION || code == LIST_ENTRIES_BATCHED_TRANSACTION) { logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid) return runCatching { @@ -252,6 +313,11 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { val response = reply.readTypedObject(KeyEntryResponse.CREATOR)!! val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) + if (userUpdatedKeys.remove(keyId)) { + SystemLogger.debug("[TX_ID: $txId] Skipping cert patch for user-updated key $keyId.") + return TransactionResult.SkipTransaction + } + val authorizations = response.metadata.authorizations val parsedParameters = KeyMintAttestation( @@ -269,6 +335,11 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { return InterceptorUtils.createTypedObjectReply(response) } + if (KeyMintSecurityLevelInterceptor.importedKeys.contains(keyId)) { + SystemLogger.debug("[TX_ID: $txId] Skipping attest-key override for imported key $keyId") + return TransactionResult.SkipTransaction + } + if (parsedParameters.isAttestKey()) { SystemLogger.warning( "[TX_ID: $txId] Found hardware attest key ${keyId.alias} in the reply." @@ -289,11 +360,13 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { .getOrThrow() keyDescriptor.nspace = SecureRandom().nextLong() + response.metadata.key.nspace = keyDescriptor.nspace KeyMintSecurityLevelInterceptor.generatedKeys[keyId] = KeyMintSecurityLevelInterceptor.GeneratedKeyInfo( keyData.first, keyDescriptor.nspace, response, + parsedParameters, ) KeyMintSecurityLevelInterceptor.attestationKeys.add(keyId) @@ -342,6 +415,11 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { CertificateHelper.updateCertificateChain(response.metadata, finalChain) .getOrThrow() + response.metadata.authorizations = + InterceptorUtils.patchAuthorizations( + response.metadata.authorizations, + callingUid, + ) return InterceptorUtils.createTypedObjectReply(response) } @@ -359,9 +437,25 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { private fun handleUpdateSubcomponent(callingUid: Int, data: Parcel): TransactionResult { data.enforceInterface(IKeystoreService.DESCRIPTOR) val descriptor = data.readTypedObject(KeyDescriptor.CREATOR) + ?: return TransactionResult.ContinueAndSkipPost + val generatedKeyInfo = - KeyMintSecurityLevelInterceptor.findGeneratedKeyByKeyId(callingUid, descriptor?.nspace) - ?: return TransactionResult.ContinueAndSkipPost + when (descriptor.domain) { + Domain.KEY_ID -> + KeyMintSecurityLevelInterceptor.findGeneratedKeyByKeyId( + callingUid, descriptor.nspace + ) + Domain.APP -> + descriptor.alias?.let { + KeyMintSecurityLevelInterceptor.generatedKeys[KeyIdentifier(callingUid, it)] + } + else -> null + } + + if (generatedKeyInfo == null) { + descriptor.alias?.let { userUpdatedKeys.add(KeyIdentifier(callingUid, it)) } + return TransactionResult.ContinueAndSkipPost + } SystemLogger.info("Updating sub-component with key[${generatedKeyInfo.nspace}]") val metadata = generatedKeyInfo.response.metadata diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/KeystoreInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/KeystoreInterceptor.kt index f7d2c1f..33bb559 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/KeystoreInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/KeystoreInterceptor.kt @@ -431,6 +431,23 @@ private data class LegacyKeygenParameters( manufacturer = null, model = null, secondImei = null, + activeDateTime = null, + originationExpireDateTime = null, + usageExpireDateTime = null, + usageCountLimit = null, + callerNonce = null, + unlockedDeviceRequired = null, + includeUniqueId = null, + rollbackResistance = null, + earlyBootOnly = null, + allowWhileOnBody = null, + trustedUserPresenceRequired = null, + trustedConfirmationRequired = null, + noAuthRequired = null, + maxUsesPerBoot = null, + maxBootLevel = null, + minMacLength = null, + rsaOaepMgfDigest = emptyList(), ) } diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/OperationInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/OperationInterceptor.kt index 91e0dde..c8236a3 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/OperationInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/OperationInterceptor.kt @@ -44,6 +44,8 @@ class OperationInterceptor( private val ABORT_TRANSACTION = InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "abort") + val INTERCEPTED_CODES = intArrayOf(FINISH_TRANSACTION, ABORT_TRANSACTION) + private val transactionNames: Map by lazy { IKeystoreOperation.Stub::class .java