The Last Astra: An Unprovable Command
By Team Cryptonite · 3 min read
About the CTF: Pinaka CTF
Two seals guard the forbidden order. Both must accept before the Astra fires. The sages were certain no single command could deceive them both. Validator: https://pinaka-l05-cyber-xo-x.vercel.app/
**Pinaka CTF 2026** · Cryptography · Team Cryptonite · All 44 Pinaka write-ups
Challenge
> Two seals guard the forbidden order. Both must accept before the Astra fires. The sages were certain no single command could deceive them both.
> Validator: https://pinaka-l05-cyber-xo-x.vercel.app/
We're given params.json, instance.json, pinaka_command.py and a README.md.
The command desk fires an order only when two independent seals both accept a command v against that order's published syndrome t. We need to fire the forbidden order loose-last-astra, whose syndrome has no honest opening at all. The claim the challenge wants us to break is "two seals can't both be fooled by one command".
The scheme
It's a gadget / SIS-style setup. The matrix A = I_m (x) g is block-diagonal, with gadget row g = (B^0, B^1, ..., B^{k-1}). The parameters are B = 65536 (2^16), k = 8, m = 3, so a command is v in [0, B)^24.
The syndrome just splits into 3 registers. For register i, ti = suml v[ik+l] B^l, which is the base-B digit expansion of some value Si. Because each digit is in [0, B) and there are k = 8 of them, an honest Si can only ever reach [0, B^k), and B^k = 2^128.
The two seals are:
Dharma Seal : 0 <= v_j < B for all j AND A.v == t (mod p)
Rigor Seal : 0 <= v_j < B for all j AND A.v == t (mod p2)
with p a 192-bit prime and p2 a 208-bit prime.
The bug
Neither seal ever checks that t is actually in the achievable range [0, B^k). Each only checks a congruence modulo its own prime. That's the whole hole.
The forbidden syndrome values are enormous (around 600 digits each), far bigger than B^k, so there's no honest integer opening. But look at the sizes:
B^k = 2^128 (129 bits)
p (192 bits) > B^k
p2 (208 bits) > B^k
Both primes are bigger than B^k. So if I can find a value Si with 0 <= Si < B^k that satisfies Si == ti (mod p) and Si == ti (mod p2), then its base-B digits are a perfectly bounded command that both seals accept, even though t_i itself is nowhere near the legal range.
And the author built the forbidden syndrome so this works: each ti was chosen so that ti mod p == ti mod p2, and that shared residue is already < B^k. In other words ti is congruent, mod p*p2, to a small in-range value. Checking all three registers confirms it:
reg 0: t % p == t % p2 == 222096782302045457542069122992793806115 (< B^k)
reg 1: t % p == t % p2 == 275613658661615283968060356816014367549 (< B^k)
reg 2: t % p == t % p2 == 209736133354915237605252499179498355628 (< B^k)
Two verifiers give no extra soundness when they share the same blind spot. Here both skip the range check, so a command that's illegal over the integers is still congruent to the target under both primes at once.
The exploit
For each register, take the common residue Si = ti mod p and emit its 8 base-B digits. Concatenate the three registers into 24 integers. pinaka_command.py is public arithmetic, so I used its opening and seal functions directly. I ran its self-check first, then forged:
import pinaka_command as C
params = C.load_params("params.json")
inst = C.load_instance("instance.json")
B = params["gadget"]["B"]; k = params["gadget"]["k"]
p = params["seals"]["dharma_seal"]["modulus_p"]
p2 = params["seals"]["rigor_seal"]["modulus_p2"]
t = inst["orders"]["loose-last-astra"]["syndrome"]
v = []
for ti in t:
S = ti % p
assert ti % p == ti % p2 and 0 <= S < B**k
v += C.opening(S, B, k)
print(v)
print("dharma", C.dharma_seal(params, v, t),
"rigor", C.rigor_seal(params, v, t),
"both", C.both_seals(params, v, t))
python3 forge.py
hold-position: dharma = True rigor = True both = True
[25891, 13729, 48228, 53333, 26052, 48198, 18635, 42774, 23357, 44623, 11421, 64876, 806, 13712, 17028, 53081, 48044, 45191, 45694, 9504, 18718, 16686, 46528, 40393]
dharma True rigor True both True
Both seals accept the forged command locally, so it's ready to submit.
Submitting
curl -X POST "https://pinaka-l05-cyber-xo-x.vercel.app/astra/command" \
-H "Content-Type: application/json" \
-d '{"order": "loose-last-astra", "command": [25891, 13729, 48228, 53333, 26052, 48198, 18635, 42774, 23357, 44623, 11421, 64876, 806, 13712, 17028, 53081, 48044, 45191, 45694, 9504, 18718, 16686, 46528, 40393]}'
{
"dharma_seal": true,
"rigor_seal": true,
"result": "fired",
"order": "loose-last-astra",
"proclamation": "The Last Astra is loosed by a command that could not be proven.",
"flag": "PINAKA{the_last_astra_an_unprovable_command}"
}
Flag
PINAKA{the_last_astra_an_unprovable_command}
Summary
- A command opens a syndrome that honestly can only reach
[0, B^k)withB^k = 2^128. Both seals only check a congruence, one modp(192-bit), one modp2(208-bit), and neither checks the target is in range. - Both primes are larger than
B^k, and the forbidden syndrome was built soti mod p == ti mod p2with that shared residue belowB^k. - Take
Si = ti mod pper register, expand it in base B, and both seals accept the 24-digit command even though the order has no honest opening.