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.
This commit is contained in:
@@ -116,12 +116,21 @@ object InterceptorUtils {
|
||||
fun <T : Parcelable?> 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)
|
||||
}
|
||||
|
||||
|
||||
+20
-4
@@ -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())))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user