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/

The Last Astra: An Unprovable Command

**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

  1. A command opens a syndrome that honestly can only reach [0, B^k) with B^k = 2^128. Both seals only check a congruence, one mod p (192-bit), one mod p2 (208-bit), and neither checks the target is in range.
  2. Both primes are larger than B^k, and the forbidden syndrome was built so ti mod p == ti mod p2 with that shared residue below B^k.
  3. Take Si = ti mod p per register, expand it in base B, and both seals accept the 24-digit command even though the order has no honest opening.

Read this post on CyberHx →