Jambavan: The Forgotten Strength

By Team Cryptonite · 1 min read

About the CTF: Pinaka CTF

jambavanseal is an oracle that reads 0/1 characters from stdin. The goal is a 128-bit "chant" that drives its hidden state to a target sigil.

Jambavan: The Forgotten Strength

**Pinaka CTF 2026** · Reverse engineering · Team Cryptonite · All 44 Pinaka write-ups


Category: reverse engineering

Flag: PINAKA{jambavanforgottenstrengthlearnedfromtheoracle}

Overview

jambavan_seal is an oracle that reads 0/1 characters from stdin. The goal is a 128-bit "chant" that drives its hidden state to a target sigil.

Files:

Analysis

Every 0 or 1 character advances a hidden 128-bit linear-feedback automaton and emits one resonance bit. The state lives in two 64-bit registers (r12 low, rbp high), and the constants are in the binary:


tap1 = 0xca504c4a84c21f09   (AND r12)
tap2 = 0x7f78bc91aa80f7ef   (AND rbp)
tap3 = 0x49fbc9fedff08584   (AND new r12)
tap4 = 0x53b71036898edaf9   (AND new rbp)

feedback = parity(r12 & tap1) ^ parity(rbp & tap2) ^ input_bit
new_r12  = (r12 >> 1) | (rbp << 63)
new_rbp  = (rbp >> 1) | (feedback << 63)
output   = parity(new_r12 & tap3) ^ parity(new_rbp & tap4)

The resting state is also in the binary:


r12 = 0xb43f4db6aefb5271
rbp = 0x2d54ac7aec3fd6fd

the tap constants and initial state visible in the disassembly

Solution

Shifts, ANDs, parities and XORs are all affine over GF(2), so the whole machine is:


S_{i+1} = A*S_i  XOR  b*u_i
y_i     = c*S_{i+1}

S is the 128-bit state and u_i is the input bit. A and b come from stepping the basis vectors with input 0. After 128 input bits:


S_128 = A^128*S_0  XOR  sum_{i=0..127} A^(127-i)*b*u_i

The 128 columns A^(127-i)*b form a square 128x128 system over GF(2). Solving it for the target sigil 0x5e0d6ee1d2c86176c52d690679648156 with Gaussian elimination gives the chant.


t1=0xca504c4a84c21f09; t2=0x7f78bc91aa80f7ef
t3=0x49fbc9fedff08584; t4=0x53b71036898edaf9
S0=0xb43f4db6aefb5271 | (0x2d54ac7aec3fd6fd<<64)

def par(x): return bin(x).count('1') & 1

def step(S,bit):
    r12=S&((1<<64)-1); rbp=S>>64
    fb=par(r12&t1)^par(rbp&t2)^bit
    nr12=((r12>>1)|(rbp<<63))&((1<<64)-1)
    nrbp=((rbp>>1)|(fb<<63))&((1<<64)-1)
    out=par(nr12&t3)^par(nrbp&t4)
    return nr12|(nrbp<<64),out

A=[step(1<<i,0)[0] for i in range(128)]
b=step(0,1)[0]
def applyA(S):
    r=0
    for i in range(128):
        if (S>>i)&1: r^=A[i]
    return r

target=int("5e0d6ee1d2c86176c52d690679648156",16)
cols=[b]
for _ in range(127): cols.append(applyA(cols[-1]))
cols=cols[::-1]                      # column for u_i = A^(127-i) b

v=S0
for _ in range(128): v=applyA(v)
rhs=target^v

rows=[]
for eq in range(128):
    row=0
    for i in range(128):
        if (cols[i]>>eq)&1: row|=1<<i
    if (rhs>>eq)&1: row|=1<<128
    rows.append(row)

piv=[]; r=0
for col in range(128):
    p=next((rr for rr in range(r,128) if (rows[rr]>>col)&1), None)
    if p is None: continue
    rows[r],rows[p]=rows[p],rows[r]
    for rr in range(128):
        if rr!=r and ((rows[rr]>>col)&1): rows[rr]^=rows[r]
    piv.append(col); r+=1
sol=0
for k,col in enumerate(piv):
    if (rows[k]>>128)&1: sol|=1<<col
chant=''.join('1' if (sol>>i)&1 else '0' for i in range(128))

The solved chant:


1000101110000110110010010100110101010011111101001001001100010111
0011110010101001011101101110101000100001111111011100011111000010

Feeding it back through the simulated state lands exactly on 0x5e0d6ee1d2c86176c52d690679648156.

solver output printing the 128-bit chant and the simulated final state matching the target

Submission


curl -X POST https://pinaka-k06-cyber-xo-x.vercel.app/seal/awaken \
  -H 'Content-Type: application/json' \
  -d '{"chant":"10001011100001101100100101001101010100111111010010010011000101110011110010101001011101101110101000100001111111011100011111000010"}'

{"flag":"PINAKA{jambavan_forgotten_strength_learned_from_the_oracle}",
 "proclamation":"Jambavan remembers his strength; the seal yields",
 "status":"awakened"}

Notes

Read this post on CyberHx →