WinAntiDbg0x200 picoCTF 2024 Solution

Published: April 3, 2024

Description

If you've solved WinAntiDbg0x100 you'll discover something new in this one. Debug the executable and find the flag.

Windows & x32dbg

Download WinAntiDbg0x200.zip (password: picoctf) and extract the executable.

Load WinAntiDbg0x200.exe in Ghidra first to map the anti-debug checks, then open it in x32dbg (32-bit).

Note: the challenge says it requires admin privileges but you can work around this in the debugger.

bash
wget https://artifacts.picoctf.net/c_titan/88/WinAntiDbg0x200.zip && \
unzip WinAntiDbg0x200.zip
This is the second challenge in the WinAntiDbg series, building on the single-check bypass from WinAntiDbg0x100. Here the binary has three separate anti-debug checks, and each must be handled correctly as you step through the debugger.
  1. Step 1Map all three checks in Ghidra
    In Ghidra, open the binary and locate the interesting function (the one containing the flag path). You will see: one check near address ending in 16ED that requires the program to be running with debug privileges (admin), one check near 1826, and one check near 1830. Each tests a different condition; if any of them branch to the failure path, you get the oops message instead of the flag.
    Learn more

    Multiple anti-debug checks are common in real malware: if the first check is bypassed, the second or third may still detect the debugger. Mapping all checks in Ghidra before setting breakpoints prevents being surprised mid-run when a second check fires unexpectedly.

    The challenge says it requires admin privileges. That requirement is one of the anti-debug conditions (near 16ED): it checks whether the process has debug privileges and if not, prints the admin error. In the debugger you can skip this check by manipulating registers, avoiding the need to actually run as admin.

    As with 0x100, ASLR means absolute addresses differ between runs, but the last four hex digits match between Ghidra and x32dbg. Use those last four digits to find each instruction in the debugger.

  2. Step 2Set breakpoints at each check and manipulate registers
    Run the binary in x32dbg until user code. Set breakpoints at the last-four-digit addresses matching the three Ghidra locations (16E6, 1826, and 1830 region). Then run and handle each in turn:
    • At the first breakpoint (16E6 region): you want the jump to happen so you clear the error path. Modify ECX or the relevant register to 1 so the conditional jump is taken.
    • At the second breakpoint (1826 region): ECX is already 1 which is non-zero, so this conditional jump executes correctly on its own; let it run.
    • At the third breakpoint (1830 region): you do NOT want this jump because it leads to the failure message. Make EDX zero so the jump is not taken and execution falls through to the flag.
    Learn more

    Each check uses a different register (ECX, EDX) and a different conditional jump direction. Reading the jump mnemonic is critical: JZ jumps when the zero flag is set (result was zero); JNZ jumps when it is not zero. Getting the direction backwards causes the wrong branch to be taken even after you change the register.

    The method is the same as 0x100: pause at the key instruction and double-click the register in the Registers pane to change its value. The difference here is that you must track three separate conditions and handle them correctly in sequence during a single run.

  3. Step 3Read the flag from the log
    After all three checks are handled correctly, the program follows the success path and prints the flag to the x32dbg log window. Check the log tab for the line starting with picoCTF.
    Learn more

    Once all anti-debug checks are bypassed, the binary executes the code that displays the flag. The log pane in x32dbg captures OutputDebugString calls, which is how the flag is printed in this binary. If the flag does not appear, check whether the third breakpoint caused the wrong jump direction.

    The progression from 0x100 (one check, simple EAX patch) to 0x200 (three checks, multiple registers) mirrors how real malware layering works: each layer adds cost to the analyst's time. Level 0x300 goes further by using an infinite loop and packed code that requires binary patching with Ghidra.

Flag

picoCTF{d3bug_f0r_th3_Win_0x200_b...}

Passing all three anti-debug checks in sequence prints the flag to the debugger log.

Want more picoCTF 2024 writeups?

Useful tools for Reverse Engineering

Related reading

What to try next