← PortfolioWriteup library
Reverse Engineering · 3 min read
On this page

Spooky Cereals 02 — Nordics

Summary

An encrypted 7z (password KlippyAI) holds two sibling binaries (Linux x86 ELF + Windows x86 PE) that XOR a 256-byte "One Time Pad" against 256 bytes of data and search the output for the magic prefix badff00d. The shipped key is intentionally corrupted ("the key has been tampered with... TIME is REVERSIBLE, but the KEY is NOT"), and a bundled ConfuserEx-packed .NET helper (KlippyAI) leaks the repair: the Nordics hit Lytton Labs with Time-Reversal Rays while the key was captured — the key is byte-REVERSED. Reversing the key and re-XORing yields an ASCII banner containing the flag, written mirrored.

Solution

Step 1: Reverse the XOR logic in the Linux binary

re03-lin-x86.bin (main at 0x133b) copies two .rodata blobs to the stack, XORs them byte-wise for 256 bytes, then calls memmem(out, 0x100, "badff00d", 7). Disassembly of the check:

166d: mov DWORD PTR [ebp-0x325],0x66646162   ; "badf"
1677: mov WORD  PTR [ebp-0x321],0x3030       ; "00"
1680: mov BYTE  PTR [ebp-0x31f],0x64         ; "d"
1695: push 7 ; push 0x100 ; push out ; call memmem
16b1: jne WARNING_BRANCH                     ; found -> flag printed

Running it fails the check ("key has been tampered with"). The runtime buffers (dumped with gdb at the memmem-return, base+0x16a1) are the ground truth: B1 = the 256-byte key, B2 = the encrypted data, B1 ^ B2 = garbage.

Step 2: Extract the hidden hint from KlippyAI

KlippyAI-trained-on-CK02.exe is a ConfuserEx-packed .NET exe. Run it under Mono (user-space Arch package, plus stub symlinks for libSystem.Native.so), drive the menu over a pty, choose [A] Analyze File and give it the Linux binary. Klippy narrates:

the Nordics were blasting Lytton Labs with time-reversal rays (TRRs)... the XOR key data is in REVERSE! If you can somehow manage to reverse the XOR key data, you should be able to recover the flag!

Step 3: Reverse the key, XOR, read the mirrored banner

One script from buffers to flag:

#!/usr/bin/env python3
"""Dump the runtime key/enc buffers with gdb, reverse the key, decode the flag."""
import subprocess

GDB = r"""
set confirm off
set pagination off
file /tmp/spooky/ck02/re03-lin-x86.bin
starti
python
import gdb
base = None
for line in gdb.execute('info proc mappings', to_string=True).splitlines():
    if 're03-lin-x86.bin' in line:
        base = int(line.split()[0], 16); break
gdb.execute('break *%d' % (base + 0x16a1))   # after memmem returns
gdb.execute('continue')
ebp = int(gdb.parse_and_eval('$ebp'))
inf = gdb.selected_inferior()
open('runtime_b1.bin','wb').write(bytes(inf.read_memory(ebp-0x21e, 0x101)))  # key
open('runtime_b2.bin','wb').write(bytes(inf.read_memory(ebp-0x31e, 0x100)))  # enc
print('dumped')
end
quit
"""
open('dbg.gdb', 'w').write(GDB)
subprocess.run(['gdb', '-q', '-x', 'dbg.gdb', '--batch'], capture_output=True)

key = open('runtime_b1.bin', 'rb').read()[:256]
enc = open('runtime_b2.bin', 'rb').read()[:256]

# The TRR tamper: the key is byte-REVERSED. Undo it.
plain = bytes(k ^ e for k, e in zip(key[::-1], enc))

# The plaintext is an ASCII banner, mirrored (box art read backwards).
s = plain.decode('latin1')
core = ''.join(c for c in s if c not in '*\n .')[::-1]
# core = feedfacedeadbeefdeadface{...}badf00dda7e4107
print(core)
print("deadface{" + core.split('{')[1].split('}')[0] + "}")

Output:

feedfacedeadbeefdeadface{N0rdics-R3ally-like-Frank3nb3rry-cuz-bro-is-tall-like-they-are!}badf00dda7e4107
deadface{N0rdics-R3ally-like-Frank3nb3rry-cuz-bro-is-tall-like-they-are!}

The Nordics' favorite spooky cereal is Frank3nb3rry (spelled with 3s — the flag contains no spaces, periods, or asterisks, per the challenge note).

Flag

deadface{N0rdics-R3ally-like-Frank3nb3rry-cuz-bro-is-tall-like-they-are!}
← All writeups