DiceCTF rev_explorer Writeup
I’m back with another reverse engineering writeup. This time we are looking into a randomized 3D kernel maze challenge that required reverse engineering a custom Rust ioctl driver and developing a DFS solver with reset/replay capabilities to escape the labyrinth.
1. Initial artifacts and environment
bzImageinitramfs.cpio.gz- nc endpoint:
explorer.chals.dicec.tf 1337
Connecting to nc showed a booting VM (SeaBIOS/iPXE) and eventually a shell as ctf.
/dev/challenge existed and was world RW, but direct read/write failed with Invalid argument, indicating ioctl protocol.
3. Extracting kernel ELF from bzImage
bzImage is a packed boot image, not directly pleasant to reverse.
I extracted embedded compressed kernel stream and decompressed to:
vmlinux_from_17059.bin
String triage on vmlinux revealed:
drivers/misc/challenge.rs(Rust driver source path markers)drivers/misc/challenge.rs:170,:266dice{fake_flag_for_testing}(decoy)
Ofcourse no real flag plaintext appeared statically, so I had to dive into the code logic to find the winning conditions and how to trigger them.
4. Reversing it with IDA
I did the binary analysis in IDA on vmlinux_from_17059.bin and found several key functions in the challenge driver. I renamed them for clarity as I worked through the code:
challenge_init- module initializationchallenge_open- file open handlerchallenge_release- cleanup on closechallenge_unlocked_ioctl- ioctl entry pointchallenge_ioctl_dispatch- command routerchallenge_build_graph_state- maze state builderchallenge_step_from_idx- maze traversal logicchallenge_read_u32_from_user- user space reader
Important globals:
challenge_fopschallenge_miscdev
Key behavior
open()allocates/builds per-file graph state (fresh randomized maze)release()drops refcounted stateunlocked_ioctl()is the full user interface
5. Ioctl protocol map
Recovered from challenge_ioctl_dispatch:
0x80046480-> read field (+0x50 in state)0x80046481-> readdim_a0x80046482-> readdim_b0x80046483-> readdim_c0x80046484-> readsteps0x80046485-> readstatus0x80046486-> read available-direction bitmask (6 bits)0x40046488-> move(direction 0..5)0x6489-> reset to start node, clear status/steps0x80406487-> flag output path, only valid whenstatus==1
Direction semantics are 6-neighbor 3D moves ( equivalent mapping).
6. Graph/maze generation findings
challenge_build_graph_state constructs a per-open randomized 3D graph:
- dimensions vary by session (observed around 8..16 each axis)
- start/root node resettable via
0x6489 - each node has up to 6 directional edges
- node metadata can set terminal status on entry
Observed at runtime:
status=0normalstatus=2common terminal trapstatus=1rare win state
This is why random walking often looked “stuck” in status-2-heavy behavior.
7. Runtime probing and solver development
I built minimal static syscall-only Linux x86_64 clients (no libc) and uploaded over nc shell.
Why naive DFS failed initially
After stepping into terminal nodes, move/backtrack often fails. So classic recursive backtracking breaks.
Fix
Use reset + replay:
- keep recorded path prefix
- when backtracking becomes invalid, send reset ioctl (
0x6489) - replay moves from root to desired depth
- continue DFS from there
This made traversal complete and deterministic.
8. Critical moment: proving reachability of status==1
Deterministic DFS eventually found status==1 in live runs, proving win state is reachable (not dead code).
Early attempts that called flag ioctl from deep stack sometimes segfaulted in user process.
Final reliable behavior:
- On first
status==1, immediately call0x80406487, print output, exit.
That eliminated instability.
9. Final successful run
From remote run logs:
status1 reachedio87 rc=0 out=dice{twisty_rusty_kernel_maze}
This confirmed the full solution path worked end-to-end, escaping the labyrinth and retrieving the flag.
It took me a while to write the reset/replay logic, but it was satisfying to see it succeed in the end. The key was proving that status==1 was actually reachable and not just a distraction, which required careful handling of the ioctl interactions and state management.
I took a while to publish this writeup because of tight schedules but ill be back with some interesting blogs! Thanks for reading.