← Back to Writeups
HTBN/AReversing

GODMODE--999

XESXOR8/23/202610 min read
#reversing#htb#n/a

GODMODE--999

Platform: Uiuctf 2026 | Category: Reversing | Type: Challenge | Difficulty: Hard | OS: NA | Author: D3v0o0Nu11 | Date: 2026-08-08 | Status: Solved Techniques: firmware_static_reverse_engineering, journal_replay_reconstruction, committed_vs_speculative_timeline, blake2s_keystream_file_decryption, raid9_vm_emulation, opcode_remap_recovery, checkpoint_rollback_if_modeling, smt_bitblast_offline_solving, chacha20_poly1305_key_derivation, lldb_gdbstub_ground_truth_extraction

Summary

Task: AArch64 firmware for a 'ranked console' plus an encrypted RankedFS disk; reconstruct the committed (post-rollback) journal timeline and defeat a RAID//9 dataflow VM to unlock a hidden achievement. Solution: reimplement the journaling FS offline, derive BLAKE2s keystream file keys, emulate the VM and solve the 48-byte player code with bitwuzla (modeling op9 as a symbolic checkpoint/rollback If), then ChaCha20-Poly1305-decrypt achievement.bin.

Recon

Port scan

nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: UIUCTF 2026 | ID: 20260808_uiuc2026_godmode_999
  • Tags: firmware, custom_vm, qemu, aarch64, chacha20_poly1305, rollback, z3, blake2s, custom_filesystem, journaling_fs, dataflow_vm, smt_solver, bitwuzla, virtio_blk, unaligned_access_fault
  • Indicators: superblock magic RNK9, RankedFS journal magic JRNL with committed_generation, RAID//9 header magic and dataflow node/edge table, ACHV999 achievement.bin with ChaCha20-Poly1305 nonce+tag, QEMU cortex-a72 MMU-off Device-memory unaligned LDP fault ESR 0x96000021
  • Source: 20260808_uiuc2026_godmode_999.md

Foothold

Vulnerability / Misconfiguration

  1. Firmware_static_reverse_engineering
  2. Journal_replay_reconstruction
  3. Committed_vs_speculative_timeline
  4. Blake2s_keystream_file_decryption
  5. Raid9_vm_emulation
<command>

Exploitation

  • See original writeup content for detailed exploitation.

Privilege Escalation

Enumeration

sudo -l
find / -perm -4000 2>/dev/null
getcap -r / 2>/dev/null
cat /etc/crontab
ps aux

Exploitation

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

Key Takeaways / Lessons

  • firmware_static_reverse_engineering
  • journal_replay_reconstruction
  • committed_vs_speculative_timeline
  • blake2s_keystream_file_decryption
  • raid9_vm_emulation
  • opcode_remap_recovery
  • checkpoint_rollback_if_modeling
  • smt_bitblast_offline_solving
  • chacha20_poly1305_key_derivation
  • lldb_gdbstub_ground_truth_extraction
  • Tags: firmware, custom_vm, qemu, aarch64, chacha20_poly1305, rollback, z3, blake2s, custom_filesystem, journaling_fs, dataflow_vm, smt_solver, bitwuzla, virtio_blk, unaligned_access_fault

Original Writeup

<details><summary>Click to expand original content</summary>

Description

Silver Wolf calls it a one-button clear. The ranked server calls it an impossible replay. We recovered the firmware from her LV.999 console and the drive containing her last match. Restore the committed timeline and claim the achievement she left behind. The server remembers every rollback. Only committed ticks affect MMR.

Boot command: qemu-system-aarch64 -M virt -cpu cortex-a72 -m 128M -global virtio-mmio.force-legacy=false -bios godmode.rom -drive file=ranked.img,format=raw,if=none,id=ranked -device virtio-blk-device,drive=ranked -nographic

English summary: Two files are given — godmode.rom (13672-byte raw AArch64 firmware) and ranked.img (16 MB disk, superblock magic RNK9). The firmware is a bare-metal "ranked console" that mounts a custom journaling filesystem (RankedFS), decrypts per-file blocks, and gates an achievement behind a verify command that runs a RAID//9 dataflow VM over four replay files. The goal is to reconstruct the committed (post-rollback) timeline, recover the 48-byte player code the VM accepts, and decrypt the hidden achievement to obtain the flag.

Analysis

Recon

  • godmode.rom: raw AArch64 firmware loaded by QEMU -bios (entry PC=0, base 0x0). PL011 UART at 0x09000000, virtio-mmio scanned from 0x0a000000.
  • ranked.img: 16 MB disk, superblock magic RNK9, block bodies look encrypted/random.
  • Booting the console prints GODMODE//999 RANKED CONSOLE. achievementLOCKED // restore the committed LV.999 replay; verify prompts PLAYER CODE (48 bytes):. Strings reveal RankedFS tags (RANKEDFS-COMMITTED/ROOT/BLOCK), RAID9 tags (RAID9-MAP/TARGET/DROP), ACHV999, file paths (/profile/player.dat, /replays/{tutorial,placement,promotion,godmode}.raid, /cache/achievement.bin), and the win string ONE-BUTTON CLEAR // HIDDEN MMR 999.

Firmware function map (full disasm via capstone/r2)

reset 0x0, uart_gets 0x80, main 0x140, sb_verify 0x500, uart putc/getc 0x5b4/0x5d4, puts 0x600, virtio_blk_init 0x840, virtio_read_sector 0x984, rankedfs_mount 0xa40, fs_lookup 0x1020, fs_read(+decrypt) 0x10a0, raid9_validate 0x12a0, derive_key 0x1440, verify_part1 0x1500, VM dispatch 0x18d8, VM opcode handlers 0x1c60-0x1e64, verify_part2 0x1e70, cmd_replay 0x2010, BLAKE2s init/update/final 0x2534/0x2590/0x2654, BLAKE2s G-round 0x21c0, ChaCha20 block 0x2780, Poly1305 0x2ad0, ChaCha20-Poly1305 AEAD 0x2dd0.

RankedFS on-disk layout

  • Two superblocks: disk 0x0 (gen0, committed_generation=0) and 0x1000 (gen28, committed_generation=28). Struct: magic RNK9, ver=9, hdr_len=0x3c, journal_start_sector=2, journal_count, committed_generation@+0x14, FNV-1a checksum@+0x18 (init 0x811c9dc5, prime 0x01000193, per byte x ^= x>>13, over 0x3c bytes with the checksum word zeroed).
  • Plaintext journal at disk 0x2000, 0x80-byte entries, magic JRNL, strictly increasing seq, per-entry FNV checksum @+0x1c. Fields: type@+4, seq@+8, cond/count@+0xc, node_id@+0x10, sizeA@+0x14, offB@+0x18, 0x30-byte payload@+0x20.

The rollback mechanic (core insight)

rankedfs_mount replays the journal, maintaining a live directory of 16 inode slots (0x40 bytes each: rec[0:0x30]=name, f30@0x30, f34@0x34, nid@0x38, present@0x3c). Journal op types:

  • type1: create/find inode by nid, copy 0x30-byte name payload, present=1
  • type2: set inode[0x30:0x38] = entry[0x14:0x1c] (f30=block index, f34=size)
  • type3: set inode record (0x30 bytes)
  • type4: DROP (present=0)
  • type5: ROOT snapshot (root = copy(live); save marker = entry[0xc])
  • type6: CONDITIONAL ROLLBACK — if entry[0xc] == saved marker: live = copy(root) (undo speculative changes since the snapshot)
  • type7: COMMIT (ctx = copy(live); committed_id = seq)

This disk's journal contains type5/6 pairs with matching markers (101/101 @seq5/10, 202/202 @15/19, 303/303 @24/27), so seq 10/19/27 roll back the speculative writes (the speculative godmode nid99 / achievement nid98 inserts and a cleared.raid rename), leaving the committed directory = {player.dat(nid1), tutorial(nid2), placement(nid3), promotion(nid4), godmode(nid5), achievement(nid6)} with committed_id=28. This is literally "restore the committed timeline / only committed ticks affect MMR."

Environmental note: under this QEMU (cortex-a72, MMU OFF, SCTLR_EL1=0x00c50838 ⇒ all RAM is Device memory), mount always alignment-faults on an unaligned LDP inside sb_verify (ESR=0x96000021, DFSC=0x21). The firmware cannot actually complete mount in this emulator, so the entire solve was reproduced offline in Python by reimplementing the logic and cross-checking against the firmware via lldb over the QEMU gdbstub.

Crypto (all confirmed byte-exactly against firmware)

  • The hash is standard BLAKE2s-256 (hashlib.blake2s(digest_size=32)). It was first mis-read as BLAKE2b because init stores four 64-bit words = eight 32-bit IV words with h[0] ^= 0x01010020.
  • derive_key(name, ctx, scratch, flagbyte, salt16) = blake2s(name || ctx[0x404]=committed_id(4 LE) || flagbyte(1) || [if flagbyte>0: scratch[0:flagbyte*0x10]] || salt16).
  • File-decryption key ctx[0x408] = blake2s("RANKEDFS-COMMITTED" || committed_id(4) || for each present committed inode: nid(4)+f30(4)+f34(4)+rec(0x30)).
  • Block cipher (fs_read): per 32-byte block, ks = blake2s(ctx[0x408](32) || node.nid(4) || (byteoff>>5)(4) || "RANKEDFS-BLOCK"); plaintext = ciphertext ^ ks. File data lives at disk byte node.f30 * 0x1000, length node.f34.
  • Validation: decrypting /profile/player.dat with this key yields clean text PLAYER=SILVER WOLF / MODE=FINAL LV.999 / HIDDEN_MMR=998 / POLICY=COMMITTED_TICKS_ONLY, confirming the key and committed-directory reconstruction.

The verify puzzle — RAID//9 dataflow VM (verify_part1)

verify reads exactly 48 bytes. verify_part1 runs a graph VM over the four committed replay files in order — tutorial(flagc0), placement(flagc1), promotion(flagc2), godmode(flagc3). A hidden-MMR seed starts at ctx[0x50]=0x3e3 (995); each file must add +1, reaching 0x3e7 (999) after all four.

Each file is a RAID//9 program: header (magic RAID//9, ver=9, nodes@0xa, edges, lanes@0x14, code-window offset f16@0x16, window length f18@0x18, section offsets), a node table (0x20-byte nodes: id, f2, op@4, f5, out_reg@8, in_reg@0xa, imm10@0x10, imm14@0x14, imm18@0x18), an edge list, and a lane-init table.

Opcode remap (16-byte table at sp+0xb0): for flagc==0 it is header[0x30:0x40] directly (a firmware quirk: 0x165c cbz flagc → 0x18b4 memcpy); for flagc>0 it is derive_key("RAID9-MAP", cid=28, flag=flagc, salt=header[0x40:0x50])[0:16] XOR header[0x30:0x40]. Nodes execute in deterministic topological order (ready-set, tie-break f2 desc, f5 desc, id asc). Selector = remap[node.op], must be ≤ 0xc.

Post-remap opcodes 0..12: op0 load u32 from code48[f16+imm10] (the ONLY input entry point), op1 imm, op2 ror/add mix, op3 ror/xor mix, op4 (imm10|1)*reg+imm14, op5 FNV/xxhash-style mul+xorshift, op6 two-register mix, op7/op12 swap, op8 RST (snapshot init_regs=regs, set flag), op9 CHK (xxHash over regfile with 0x9E3779B9/0x7f4a7c15/0x85ebca6b/0xaf286bcb, compared to node.imm14; COMMIT on match, ROLLBACK regs=init_regs & acc=chk_acc on mismatch), op10 acc mix (the +1), op11 set completion flag.

After the VM per file: require the op11 flag; then a TARGET-vector check — regfile must equal laneinit_bytes XOR blake2s-keystream(derive_key("RAID9-TARGET", flag=flagc, salt=header[0x68:0x78])). Then a DROP scratch update blake2s("RAID9-DROP" || flagc || regfile || acc || code48[f16:f16+f18]) fills scratch[flagc*0x10:...], integrity-checked against header[0x60:0x68]; scratch[0x30:0x50] is chained and scratch[0x50:0x54]=acc.

Coverage: tutorial reads code48 words 0-3 (bytes 0-15), placement words 2-7, promotion words 6-11, godmode all 12 — so the 48-byte code is pinned across all four files.

Solution

1. Reconstruct the committed timeline offline

Parse both superblocks (verify FNV checksums), replay the JRNL journal in Python with the exact type1–type7 semantics above, honoring the type5/type6 snapshot/rollback pairs. This yields committed_id=28 and the committed directory of six inodes. Build ctx[0x408] (the file key) as BLAKE2s over "RANKEDFS-COMMITTED" + committed_id + each present inode's (nid,f30,f34,rec).

2. Decrypt files with the BLAKE2s keystream

For each committed file, XOR each 32-byte block with blake2s(filekey || nid || (byteoff>>5) || "RANKEDFS-BLOCK"). Decrypting player.dat gives readable text (sanity check). Decrypt the four .raid replays and achievement.bin.

3. Emulate RAID//9 and solve for the 48-byte code

A faithful Python emulator (emu2.py) of verify_part1 + finalization was validated against firmware ground truth (tutorial's op9 checkpoint matched, acc 995→996, integrity check passed because header[0x60:0x68] stores blake2s(scratch[0:0x10]||header[0x40:0x50])[0:8]).

Solve per file symbolically, chaining scratch concretely between files. z3 with QF_BV was far too slow (44 modular multiplications; >30 min, OOM on bit-blast). Exporting to SMT-LIB2 and solving with bitwuzla — defining ext_rotate_left via bvshl/bvlshr/bvor because z3 emits a variable-rotate form bitwuzla doesn't know natively — solved each file in seconds.

Key correctness fix: op9 must be modeled as a symbolic If (commit vs rollback), NOT forced to match and NOT treated as a no-op. With naive "commit-always"/"no-op" op9 the constraints were UNSAT; the correct solution actually relies on the checkpoint branch. With the If-model, placement became SAT and produced ASCII text.

The recovered 48-byte player code is human-readable ASCII (a filler phrase ending in LV999!!!n0w!); the emulator confirms all four TARGET + integrity checks pass and the final seed reaches 999.

4. Extract the flag (verify_part2 → ChaCha20-Poly1305 AEAD)

verify_part2 reads /cache/achievement.bin (ACHV999 header: ver=1, outlen=0x40, magic32=0x20260999@0xc, ctlen=45@0x10, nonce=hdr[0x14:0x20], tag=hdr[0x20:0x30], ciphertext=hdr[0x40:0x6d]).

The ChaCha20 key is blake2s(code48[0:0x30] || scratch[0x30:0x50] || scratch[0x50:0x54] || scratch[0:0x30] || achv_magic32(4)). RFC 8439 ChaCha20-Poly1305 decryption: the Poly1305 tag verifies (definitive), and the plaintext is the flag.

#!/usr/bin/env python3
# High-level offline solve outline for GODMODE//999.
# Full working scripts live in the task dir: emu2.py, gen_smt.py, solve_bw.py,
# chacha_decrypt.py. This is the end-to-end skeleton.
import struct
from hashlib import blake2s

DISK = open("ranked.img", "rb").read()

def fnv1a(buf):
    h = 0x811c9dc5
    for b in buf:
        h ^= b
        h = (h * 0x01000193) & 0xffffffff
        h ^= h >> 13
    return h & 0xffffffff

def b2s(*chunks):
    h = blake2s(digest_size=32)
    for c in chunks:
        h.update(c)
    return h.digest()

# --- 1. Superblocks + committed journal replay -----------------------------
def parse_sb(off):
    sb = DISK[off:off+0x3c]
    assert sb[:4] == b"RNK9"
    committed_gen = struct.unpack_from("<I", sb, 0x14)[0]
    return committed_gen

def replay_journal():
    # 16 inode slots; apply type1..type7 with snapshot/rollback semantics.
    live = {}          # nid -> dict(rec,f30,f34,present)
    root = None
    committed = {}
    committed_id = 0
    off = 0x2000
    while True:
        e = DISK[off:off+0x80]
        if e[:4] != b"JRNL":
            break
        typ  = struct.unpack_from("<I", e, 4)[0]
        seq  = struct.unpack_from("<I", e, 8)[0]
        cond = struct.unpack_from("<I", e, 0xc)[0]
        nid  = struct.unpack_from("<I", e, 0x10)[0]
        a    = struct.unpack_from("<I", e, 0x14)[0]
        b    = struct.unpack_from("<I", e, 0x18)[0]
        pay  = e[0x20:0x50]
        if typ == 1:
            live.setdefault(nid, {})
            live[nid].update(rec=pay, present=1)
        elif typ == 2:
            live[nid]["f30"], live[nid]["f34"] = a, b
        elif typ == 3:
            live[nid]["rec"] = pay
        elif typ == 4:
            live[nid]["present"] = 0
        elif typ == 5:
            replay_journal.marker = cond
            root = {k: dict(v) for k, v in live.items()}
        elif typ == 6:
            if cond == replay_journal.marker and root is not None:
                live = {k: dict(v) for k, v in root.items()}
        elif typ == 7:
            committed = {k: dict(v) for k, v in live.items()}
            committed_id = seq
        off += 0x80
    return committed, committed_id

# --- 2. File key + block decrypt -------------------------------------------
def file_key(committed, committed_id):
    parts = [b"RANKEDFS-COMMITTED", struct.pack("<I", committed_id)]
    for nid in sorted(n for n, v in committed.items() if v.get("present")):
        v = committed[nid]
        parts += [struct.pack("<III", nid, v["f30"], v["f34"]), v["rec"]]
    return b2s(*parts)

def decrypt_file(fkey, nid, f30, f34):
    base = f30 * 0x1000
    ct = DISK[base:base+f34]
    out = bytearray()
    for i in range(0, len(ct), 32):
        blk = ct[i:i+32]
        ks = b2s(fkey, struct.pack("<II", nid, i >> 5), b"RANKEDFS-BLOCK")
        out += bytes(x ^ y for x, y in zip(blk, ks))
    return bytes(out)

# --- 3. RAID//9 VM + bitwuzla solve -> code48 (see emu2.py/gen_smt.py) ------
# code48 recovered as printable ASCII; final MMR seed == 999.
# code48 = <RECOVERED_48_BYTE_PLAYER_CODE>  # do not embed here

# --- 4. ChaCha20-Poly1305 flag decrypt (see chacha_decrypt.py) -------------
# key = b2s(code48[0:0x30], scratch[0x30:0x50], scratch[0x50:0x54],
#           scratch[0:0x30], achv_magic32)
# flag = chacha20_poly1305_decrypt(key, nonce, ct, tag)  # Poly1305 verifies
</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR