Bala & Atibala: The Forgotten State
By Team Cryptonite · 2 min read
About the CTF: Pinaka CTF
The warden attests one record and then honours a different record that flattens to the same bytes. The goal is to get an "elevated" record (mantra=atibala, tier>=7) accepted using the attestation of a harmless one.
**Pinaka CTF 2026** · Reverse engineering · Team Cryptonite · All 44 Pinaka write-ups
Category: misc / canonicalization
Flag: PINAKA{balaatibalatheflattenedrecord_forgets}
Overview
The warden attests one record and then honours a different record that flattens to the same bytes. The goal is to get an "elevated" record (mantra=atibala, tier>=7) accepted using the attestation of a harmless one.
Files and components:
- Kotlin boundary:
serializeandcheckElevated - JNI glue into
fsvm_attest, which runsattest.vmbwith a 16-byte warden key sample_petition.json: sample pairs with a sample attested state
Analysis
A boon record is an ordered list of [key, value] string pairs, and duplicates are allowed. The Kotlin boundary serializes it with no escaping, so = and & inside a key or value are written as-is:
fun serialize(rec: Record): ByteArray {
val sb = StringBuilder()
for ((k, v) in rec.pairs) { sb.append(k); sb.append('='); sb.append(v); sb.append('&') }
return sb.toString().toByteArray(Charsets.UTF_8)
}
The check for "is this record supreme?" looks at the pairs list, not the bytes:
fun checkElevated(rec: Record): Boolean {
val m = rec.pairs.any { it.first == "mantra" && it.second == "atibala" }
val t = rec.pairs.any { it.first == "tier" && (it.second.toIntOrNull() ?: 0) >= 7 }
return m && t
}
nativeAttest(serialize(rec)) runs the bytecode with the warden key, which isn't in any player file, so the state can't be computed locally. That doesn't matter: the warden computes it for a non-elevated record, and a collision makes that state valid for an elevated one.

Solution
Two records with different pair structures serialize to identical bytes:
- Record A (attestable):
[["mantra=atibala&tier","7"]]->mantra=atibala&tier=7& - Record B (supreme):
[["mantra","atibala"],["tier","7"]]->mantra=atibala&tier=7&
checkElevated(A) is false: A has one pair whose key is the literal string mantra=atibala&tier, so there is no mantra key and no tier key, and the warden attests it. checkElevated(B) is true. Since serialize(A) == serialize(B), the attestation output is identical, so A's attested state can be presented with B.
Submission
Petition with record A:
POST /ashram/petition
{"pairs":[["mantra=atibala&tier","7"]]}
{"attested_state":"4ce21f87d685eba923a22160f8e17f70","status":"attested"}
Claim with record B, reusing that state:
POST /ashram/claim
{"pairs":[["mantra","atibala"],["tier","7"]],"attested_state":"4ce21f87d685eba923a22160f8e17f70"}
{"flag":"PINAKA{bala_atibala_the_flattened_record_forgets}","proclamation":"Vishwamitra honours the forgotten boon of Atibala","status":"granted"}
Notes
- The sample
attested_stateisf5ee9d3c3316c3890e9efef1705e95e4, but the live server returns644d61bc...for the same pairs. The sample was made with a different key or build and wasn't needed. - The claim endpoint re-attests whatever record you present and only compares the flattened state, never the pair list from the petition. That is why this works.
- Any equivalent collision works, for example hiding
&tier=7inside a value or splitting the key differently, as long asserializeproduces the same bytes.