Description
A roguelike dungeon-crawler binary hides the flag in a room blocked by invisible walls. You can see the flag characters on the map but cannot reach them under normal collision rules.
Patch the wall-collision check in Ghidra, or use GDB to skip the check at runtime, allowing free movement through walls.
Setup
Download the binary and make it executable.
Run it to understand the game mechanics (WASD or arrow keys to move).
Use Ghidra to find and patch the wall-collision check.
wget https://artifacts.picoctf.net/c/218/wizardlike && chmod +x wizardlike./wizardlikeSolution
Walk me through itset $eflags trick used here, and Ghidra Reverse Engineering covers the on-disk Patch Instruction workflow if you want a permanent fix.- Step 1Explore the dungeon and identify blocked areasRun the binary and navigate the dungeon with WASD/arrow keys. You will see flag characters spelled out across rooms separated by
#walls and locked corridors. Sketch the layout so you know where you need to walk after the patch.Learn more
A typical screen looks like this (your character is
@, walls are#, doors are+, flag characters are scattered glyphs visible through gaps):#################### #......##....p..i..# #..@...##..........# #......++....c..o..# ########++###..C..T# #......##....F..{..# #..h...##..........# #......##..!..}..._# #################### ^ flag chars visible across the wall @ = player # = wall + = door ! = key h = monsterThe binary renders the map in the terminal (ncurses or direct escape codes). The flag is spelled out in a room or corridor that your character cannot enter under normal rules because the wall-collision function blocks attempts to walk onto
#tiles. Before modifying anything, note which tiles you can see but cannot reach: this gives you the target coordinates. - Step 2Find the collision check in GhidraIn Ghidra, find the movement function. It contains a check like 'if (tile[y][x] == WALL) return;'. Patch this check to always allow movement.
Learn more
Import the binary into Ghidra and run auto-analysis. Search for string references related to wall tiles (often
'#'in ASCII dungeons) or look for the movement handling function in main().The collision check in decompiled C looks like:
if (map[player_y + dy][player_x + dx] == '#') { return; }In the disassembly, the pattern looks like:
mov rax, QWORD PTR [rip+0xNNNN] ; load map base pointer movzx eax, BYTE PTR [rax+rcx] ; load tile at (player_y * width + dx) cmp BYTE PTR [rip+0xMMMM], '#' ; or: cmp al, 0x23 -> compare to wall char jne <update_position> ; skip the early return when not a wall ret ; bail without moving update_position: mov ... ; write new player_x / player_yLook for a
cmpagainst0x23(the ASCII for#) followed by a conditional jump. Patching this conditional to aNOP(no-operation,0x90) or flipping it to an unconditionaljmpeliminates the wall check.In Ghidra: right-click the conditional jump instruction, select "Patch Instruction", and change it to
NOP. Then export the patched binary (File > Export Program > ELF). - Step 3Run the patched binary and navigate through wallsExecute the patched binary. Your character can now move onto any tile. Navigate to the flag room and read the flag characters displayed on the map.bash
chmod +x wizardlike-patched && ./wizardlike-patchedbash# Navigate with WASD/arrow keys through the previously blocked wallsLearn more
An alternative to patching is using GDB at runtime: set a breakpoint on the conditional jump instruction address, and when it triggers run
set $eflags ^= 0x40. The0x40bit is the zero flag; XORing flips it, which means ajnetaken-because-not-a-wall becomes ajnenot-taken (or vice versa). The single line turns "step into wall > bail" into "step into wall > proceed." You only have to do this once per movement attempt;continueuntil you reach the next blocked tile.Another approach is to directly modify the player's coordinates in memory while the program is paused in GDB:
set *(int*)&player_x = TARGET_X. The cast tells GDB to write a 32-bit integer at the address ofplayer_x, overwriting the current value withTARGET_X. Pair withset *(int*)&player_y = TARGET_Yandcontinue: the next render places you at the target tile, no walking required. (Ifplayer_xis not a symbol in a stripped build, replace&player_xwith the absolute address you found in Ghidra.)Reading the flag off the rendered map. Once you can move freely, walk every accessible cell and watch the rendered output. The flag glyphs are written into the tile map at fixed coordinates, so you may need to step adjacent to each character (the renderer often only reveals a tile when the player is within line-of-sight). Record the visible glyphs in order, prefix with
picoCTF{, and submit. If line-of-sight is blocking the read, set the player tile to each flag coordinate in turn with the GDBset *(int*)trick.This class of challenge - where the flag is hidden in the game world behind an artificial barrier - teaches binary patching, a fundamental technique in game modding, license bypass analysis, and malware modification.
Flag
picoCTF{ur_4_w1z4rd_...}
This challenge was not solved during the competition. Patch the wall-collision check in Ghidra to NOP, run the patched binary, and navigate to the hidden flag room.