← back to /home
KAPOLRI CUP 2026: Cocoon
KAPOLRI CUP 2026 • 2026-08-01 • Reverse Engineering
#KAPOLRI CUP 2026
#Cocoon
#Binary Analysis
#Decompilation
Cocoon — polriCTF 2026 Writeup
Category: Reverse Engineering Binary:
cocoon— ELF 64-bit LSB PIE, stripped, entry0x1230Flag:polriCTF26{p33l_th3_l4y3rs_x0r_r0t_qu4d_ch41n}
1. Recon
$ file cocoon
ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, stripped
$ readelf -h cocoon | grep -E "Type|Entry"
Type: DYN (Position-Independent Executable file)
Entry point address: 0x1230
Programnya stripped (tanpa simbol), prompt-nya unpack> . Fungsi utama (FUN_001010a0):
fwrite("unpack> ")→fgets(buf, 0x80, stdin)→ buang newline.- Cek
strlen == 0x2e(46). - Bangun 46 byte hasil transform input, bandingkan dengan 46 byte target di
DAT_001020a0(memcmp).
Target 46 byte (dari .rodata, offset file 0x20a0):
e2 d8 a6 5f 70 b5 98 30 4f b5 ef 81 2d 3e b0 87
33 33 09 65 b8 61 1d ca c4 2d 75 ea 91 b0 05 4e
aa 5e cc d8 d1 b7 47 95 ba 63 57 07 8a 29
2. Analisis FUN_001010a0 (dekompilasi)
fwrite("unpack> ", 1, 8, stdout);
fflush(stdout);
pcVar6 = fgets((char *)(abStack_99 + 1), 0x80, stdin);
...
if (sVar7 == 0x2e) { // panjang 46
uVar4 = 0;
_DAT_001040ec = 0;
do {
_DAT_001040ec = _DAT_001040ec * 0x1000193 ^ uVar4; // FNV-ish
uVar4 = uVar4 + 0x2545f491;
} while (uVar4 != 0xa8be9220);
_DAT_001040ec = _DAT_001040ec ^ 0xc0c0012e;
lVar9 = 0; uVar4 = 0;
do {
_DAT_001040ec = _DAT_001040ec << 0xd ^ _DAT_001040ec; // xorshift32
_DAT_001040ec = _DAT_001040ec >> 0x11 ^ _DAT_001040ec;
_DAT_001040ec = _DAT_001040ec << 5 ^ _DAT_001040ec;
bVar8 = *pbVar10 ^ (byte)_DAT_001040ec; // in[i] ^ LSB state
bVar1 = (lVar9 % 7) + 1; // rotasi 1..7
uVar4 = uVar4 ^ (lVar9*lVar9 + rol8(bVar8, bVar1)); // akumulator
out[i] = (byte)uVar4;
} while (++lVar9 != 0x2e);
memcmp(out, &DAT_001020a0, 0x2e) == 0 → "correct! that is the flag."
}
puts("nope");
3. Transformasi per byte
Untuk tiap i = 0..45:
state = xorshift32(state); // PRNG, independen dari input
k = state & 0xFF // byte kunci
v = input[i] ^ k // XOR
rot = (i % 7) + 1 // 1..7
t = rol8(v, rot) // rotate left 8-bit
acc ^= i*i + t // akumulator 32-bit
out[i] = (byte)acc // byte terendahnya dibandingkan
- PRNG adalah xorshift32:
x ^= x<<13; x ^= x>>17; x ^= x<<5(semua mod 2³²), dengan seed dari loop FNV-ish lalu^ 0xc0c0012e. - Di disassembly, operasi
(i % 7)diimplementasi dengan magic number0x4924924924924925(imul+ shift = pembagian integer oleh 7).
Nilai seed (hasil reimplementasi):
seed = 0xa5b3596e
state[1] = 0x1d413a01 (lsb 0x01)
state[2] = 0x7a21e721 (lsb 0x21)
state[3] = 0x9e796c23 (lsb 0x23)
...
4. Inverse (dekripsi)
Karena out[i] = (byte)(acc_before ^ (i*i + t)):
t = (acc_before ^ out[i]) - i*i (mod 256)
v = ror8(t, (i%7)+1)
input[i] = v ^ (state & 0xFF)
acc_before = out[i] (untuk iterasi berikutnya)
Alurnya forward (kiri ke kanan) karena state & acc keduanya deterministic dan cuma bergantung pada indeks + acc lama (yang low byte-nya = out[i-1]). Tidak ada brute-force yang diperlukan — pure inverse.
5. Solver (cocoon.py)
#!/usr/bin/env python3
TARGET = bytes.fromhex(
"e2d8a65f70b598304fb5ef812d3eb08733330965b8611dcac42d75ea91b0054eaa5eccd8d1b74795ba6357078a29")
assert len(TARGET) == 46
def u32(x): return x & 0xffffffff
def rol8(x, r):
x &= 0xff
return ((x << r) | (x >> (8 - r))) & 0xff
def ror8(x, r):
x &= 0xff
return ((x >> r) | (x << (8 - r))) & 0xff
def xorshift32(x):
x = u32(x)
x ^= (x << 13) & 0xffffffff
x ^= (x >> 17)
x ^= (x << 5) & 0xffffffff
return x
def init_state():
state, u = 0, 0
while u != 0xA8BE9220:
state = u32(state * 0x1000193) ^ u
u = u32(u + 0x2545F491)
return u32(state ^ 0xC0C0012E)
state = init_state()
prev, flag = 0, []
for i in range(46):
state = xorshift32(state)
rotated = (prev ^ TARGET[i] - i*i) & 0xff
ch = ror8(rotated, (i % 7) + 1) ^ (state & 0xff)
flag.append(ch)
prev = TARGET[i]
print(bytes(flag).decode())
Output:
polriCTF26{p33l_th3_l4y3rs_x0r_r0t_qu4d_ch41n}
6. Verifikasi
$ printf 'polriCTF26{p33l_th3_l4y3rs_x0r_r0t_qu4d_ch41n}\n' | ./cocoon
unpack> correct! that is the flag.
7. Ringkasan
- Recon: stripped PIE, prompt
unpack>, cek panjang 46,memcmp46 byte target di0x20a0. - Reverse: PRNG = xorshift32 + seed FNV-ish (
0xa5b3596e); per byte =acc ^= i*i + rol8(in[i]^lsb, (i%7)+1). - Inverse: forward,
in[i] = ror8((acc^out[i] - i*i)&0xff, (i%7)+1) ^ (state&0xff). - Flag diekstrak & diverifikasi ke binary.