$ cat writeup.md…
$ cat writeup.md…
sekai2026
Task: Android credential-verifier setup with SekaiID.apk, Verifier.apk, and an ADB TLS proxy. Solution: intercept the implicit presentation intent, recover the HMAC seed from the public fingerprint, forge admin claims, and return a valid presentation.
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
Original task description was not present in the local notes. The distributed setup contained
SekaiID.apk,Verifier.apk, andtls_proxy.pyfor accessing the Android instance over ADB.
English summary: SekaiID.apk is a wallet/credential provider and Verifier.apk asks it for a conference badge presentation. The goal is to make the verifier accept an administrator credential and read the flag from the admin dashboard.
Static analysis of the manifests showed the important IPC surface:
com.sekai.id.IdentityProviderActivity is exported and handles com.sekai.id.ACTION_PRESENT_CREDENTIAL.com.sekai.id.share.BadgeShareActivity is exported and handles com.sekai.id.ACTION_SHARE_BADGE for content://com.sekai.id.companion/... URIs.com.sekai.id.companion.CompanionInfoProvider is exported as content://com.sekai.id.companion.info and leaks public badge metadata.com.sekai.id.companion.CompanionProvider is not exported, but grants URI access through BadgeShareActivity.Verifier.apk queries for ACTION_PRESENT_CREDENTIAL with an implicit intent, so a malicious APK can register the same action and be selected as the credential provider.The native library libsekaibind.so was the key weakness. Its JNI wrappers return 32-byte values that Kotlin Base64-encodes without wrapping. The constants and formulas are:
SEP = b"\x1f" DOM_BIND = b"SEKAI-ID:credential-binding:v2" DOM_FP = b"pairing-fingerprint:v2" DOM_CLAIMS = b"claims-tag" DOM_AUTH = b"request-auth" ks = SHA256(DOM_FP + SEP + credentialId + SEP + holderPublicKey) pairingFingerprint = seed32 XOR ks bindingKey = HMAC_SHA256(seed32, DOM_BIND + SEP + credentialId + SEP + holderPublicKey) claimsTag = HMAC_SHA256(bindingKey, DOM_CLAIMS + SEP + canonicalClaims) requestAuth = HMAC_SHA256(bindingKey, DOM_AUTH + SEP + challenge)
Because CompanionInfoProvider exposes credentialId, holderPublicKey, and pairingFingerprint, the supposedly secret seed can be recovered:
seed32 = pairingFingerprint XOR SHA256(DOM_FP + SEP + credentialId + SEP + holderPublicKey)
That recovered seed is enough to compute both the requestAuth needed to ask the wallet for a real presentation and a new claimsTag for modified claims.
...
$ grep --similar