A 32-bit Windows console app that stacks three anti-debug checks before revealing the flag: admin privileges, a custom child-process detector, and the classic IsDebuggerPresent(). Goal: pass all three and read the flag from the debugger log.
1 · Unzip and Identify
# the archive password is picoctf
unzip WinAntiDbg0x200.zip
file WinAntiDbg0x200.exe
# PE32 executable (console) Intel 80386, for MS Windows
2 · Map the Checks in Ghidra
Load the binary in Ghidra and land on main (FUN_004016e0). Three TEST + Jcc pairs gate the flag path. ASLR moves the absolute addresses between runs, but the last 4 hex digits always match — use them to find each site in the debugger.
# 1) Admin / debug-privilege check
004016eb TEST EAX,EAX
004016ed JNZ LAB_00401714 ; continue only if EAX != 0
# 2) Custom child-process detector (FUN_004011d0)
00401824 TEST EDX,EDX
00401826 JNZ LAB_00401832 ; jump to "Oops!" if EDX != 0
# 3) IsDebuggerPresent()
0040182e TEST EAX,EAX
00401830 JZ LAB_00401847 ; enter flag decryption if EAX == 0
The custom check (check #2) spawns a child copy of the binary with the parent's PID as an argument; the child tries to DebugActiveProcess the parent, and its exit code tells the parent whether a debugger is attached.
3 · Flip Each Condition in x32dbg
Run the binary in x32dbg until it reaches user code, then Search For → All User Modules → String References, and Follow in Disassembler on "Oops! The debugger was detected". Set breakpoints on the three TESTs (16eb, 1824, 182e) and edit the registers as they hit.
1) 16eb TEST EAX,EAX → EAX = 1 # take the JNZ: pretend we run as admin
2) 1824 TEST EDX,EDX → EDX = 0 # don't take the JNZ: pass the custom check
3) 182e TEST EAX,EAX → EAX = 0 # take the JZ: IsDebuggerPresent "not present"
Each check uses a different register (EAX vs EDX) and a different jump direction (JNZ vs JZ). Misreading a mnemonic sends execution down the failure path even with the right register value.
4 · Flag in the Log
The flag is written with OutputDebugStringW, so it never reaches the console. When all three checks pass, it lands in x32dbg's Log tab.
# Log tab (OutputDebugString capture)
### Good job! Here's your flag:
### ~~~ picoCTF{0x200_debug_f0r_Win_<HASH>}
# the trailing hex varies per build — debug a copy and read yours
5 · What Made It Work
All three checks are the same shape, and that is what makes this one tractable. Each is a TEST immediately followed by a jump, and each only cares about one register at one instruction. Finding the three sites is the whole job, and matching on the last four hex digits is enough to survive ASLR without needing to re-derive absolute addresses.