From 108e027a980242e2708253d2036a5bcdcc381dcf Mon Sep 17 00:00:00 2001 From: Enginex0 Date: Wed, 20 May 2026 06:52:04 +0100 Subject: [PATCH] fix: reorder hal-enforced auths to evade duck detector Duck-Detector's generate-mode parser walks the reply parcel at 12-byte strides and matches (secLevel=256, tag=1, unionTag=32) at slot[count-1]. Those bytes are actually KEY_SIZE.value=256 followed by the next Authorization's presence flag and size header, an emergent fingerprint from misaligned parsing, not a fake value. Reorder toAuthorizations so PURPOSE/ALGORITHM/KEY_SIZE come first, mirroring AOSP keymint reference HAL output. KEY_SIZE moves from auth#4 to auth#2, so its int payload no longer lands at byte 224. Verified across 31 fresh duckdetector probes: zero matches (was 15/36 before). Also add gen-mode wire-byte diagnostic to InterceptorUtils and the generate-mode entry point, debug-gated, dumping request and reply parcels to /data/local/tmp for offline decode. --- .../interception/keystore/InterceptorUtils.kt | 9 +++++++ .../shim/KeyMintSecurityLevelInterceptor.kt | 24 +++++++++++++++---- 2 files changed, 29 insertions(+), 4 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 c690e75..8563b8f 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 @@ -116,12 +116,21 @@ object InterceptorUtils { fun createTypedObjectReply( obj: T, flags: Int = 0, + diagnosticTag: String? = null, ): BinderInterceptor.TransactionResult.OverrideReply { val parcel = Parcel.obtain().apply { writeNoException() writeTypedObject(obj, flags) } + if (diagnosticTag != null && SystemLogger.isDebugBuild) { + val savedPos = parcel.dataPosition() + val wire = parcel.marshall() + parcel.setDataPosition(savedPos) + val path = "/data/local/tmp/teesim-$diagnosticTag-${System.nanoTime()}.bin" + runCatching { java.io.File(path).writeBytes(wire) } + SystemLogger.debug("[$diagnosticTag] reply len=${wire.size} path=$path") + } return BinderInterceptor.TransactionResult.OverrideReply(parcel) } 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 1b5ce36..d7b9ced 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 @@ -416,6 +416,14 @@ class KeyMintSecurityLevelInterceptor( } private fun handleGenerateKey(txId: Long, callingUid: Int, callingPid: Int, data: Parcel): TransactionResult { + if (SystemLogger.isDebugBuild) { + val savedPos = data.dataPosition() + val req = data.marshall() + data.setDataPosition(savedPos) + val path = "/data/local/tmp/teesim-gen-mode-req-uid${callingUid}-tx${txId}-${System.nanoTime()}.bin" + runCatching { java.io.File(path).writeBytes(req) } + SystemLogger.debug("[gen-mode-req] uid=$callingUid txId=$txId len=${req.size} path=$path") + } val oversized = data.dataSize() > MAX_ALIAS_LENGTH return runCatching { @@ -615,7 +623,7 @@ class KeyMintSecurityLevelInterceptor( TeeLatencySimulator.simulateGenerateKeyDelay(parsedParams.algorithm, System.nanoTime() - genStartNanos) } - return InterceptorUtils.createTypedObjectReply(metadata) + return InterceptorUtils.createTypedObjectReply(metadata, diagnosticTag = "gen-mode-sym") } val keyData = if (NativeCertGen.isAvailable && attestationKey == null) { @@ -688,7 +696,7 @@ class KeyMintSecurityLevelInterceptor( TeeLatencySimulator.simulateGenerateKeyDelay(parsedParams.algorithm, System.nanoTime() - genStartNanos) } - return InterceptorUtils.createTypedObjectReply(response.metadata) + return InterceptorUtils.createTypedObjectReply(response.metadata, diagnosticTag = "gen-mode-asym") } private fun generateAttestedKeyPairNative( @@ -1237,15 +1245,23 @@ private fun KeyMintAttestation.toAuthorizations( } } + // HAL-enforced authorization ordering mirrors AOSP keymint reference + // HAL output: PURPOSE → ALGORITHM → KEY_SIZE → curve → mode params → + // exponent. Duck-Detector's generate-mode fingerprint walks the reply + // parcel at 12-byte parser strides and matches when slot[count-1] reads + // (secLevel=256, tag=1, unionTag=32) — which emerges in the original + // order because EC P-256's KEY_SIZE.value=256 lands at byte 224 (auth#4 + // value field). Reordering moves KEY_SIZE to auth#2, so byte 224 reads + // a different field entirely. + this.purpose.forEach { authList.add(createAuth(Tag.PURPOSE, KeyParameterValue.keyPurpose(it))) } authList.add(createAuth(Tag.ALGORITHM, KeyParameterValue.algorithm(this.algorithm))) + authList.add(createAuth(Tag.KEY_SIZE, KeyParameterValue.integer(this.keySize))) if (this.ecCurve != null) { authList.add(createAuth(Tag.EC_CURVE, KeyParameterValue.ecCurve(this.ecCurve))) } - this.purpose.forEach { authList.add(createAuth(Tag.PURPOSE, KeyParameterValue.keyPurpose(it))) } this.blockMode.forEach { authList.add(createAuth(Tag.BLOCK_MODE, KeyParameterValue.blockMode(it))) } this.digest.forEach { authList.add(createAuth(Tag.DIGEST, KeyParameterValue.digest(it))) } this.padding.forEach { authList.add(createAuth(Tag.PADDING, KeyParameterValue.paddingMode(it))) } - authList.add(createAuth(Tag.KEY_SIZE, KeyParameterValue.integer(this.keySize))) if (this.rsaPublicExponent != null) { authList.add(createAuth(Tag.RSA_PUBLIC_EXPONENT, KeyParameterValue.longInteger(this.rsaPublicExponent.toLong()))) }