pwn ctf guide
how to approach binary exploitation challenges.
checksec · stack overflows · ROP chains · format strings · heap attacks
first steps for any pwn challenge
- 1.run checksec on the binary first. note which protections are enabled: NX (no executable stack), PIE (position-independent executable), stack canary, RELRO, FORTIFY.
- 2.run file on the binary — 32-bit or 64-bit? ELF or PE? stripped or not? this determines calling conventions and exploit approach.
- 3.run strings on the binary. look for flag-like strings, function names, hard-coded paths, or interesting messages.
- 4.run the binary with ltrace or strace to see which library calls and syscalls it makes without reversing the assembly.
- 5.open in Ghidra or GDB. find main(), the vulnerable function, and understand what input the program reads and how it processes it.
match the protection to your exploit path
- no NX, no canaryclassic stack smash — overflow the buffer, overwrite return address with pointer to shellcode you placed on the stack.
- NX enabled, no PIEret2libc — leak not needed if no ASLR. find plt/got addresses statically. ROP to system("/bin/sh").
- NX + PIE enabledneed to leak a runtime address first. find a format string or partial overwrite to leak libc/binary base, then calculate offsets.
- stack canaryneed to leak the canary value before overwriting it, or find an arbitrary write that skips the canary entirely.
- full RELROGOT is read-only — can't overwrite GOT entries. target __malloc_hook, __free_hook, or one_gadget in libc instead.
common challenge types and approach
- ret2wina win() function exists in the binary that prints the flag. find its address, overflow the buffer to overwrite the return address with it. offset = cyclic pattern in GDB.
- ret2libcno win function — ROP to call system("/bin/sh"). find a gadget to set RDI (64-bit) or push the argument (32-bit), then call system@plt or system via libc offset.
- ROP chainbuild a chain of small code snippets ending in ret (gadgets) to set registers and call functions. use ROPgadget or ropper to find available gadgets.
- format stringprintf(input) with no format string — %p leaks stack values, %s dereferences pointers, %n writes to memory. calculate the offset to the format string's own position on the stack first.
- heap exploitationfree a chunk then use it again (UAF), or corrupt heap metadata. modern glibc uses tcache — double-free corrupts fd pointer, enabling allocation over arbitrary address.
useful tools
- pwntools — Python exploit scripting library. connects to remote, crafts payloads, handles encoding. nearly every pwn solve uses it.
- GDB + pwndbg — dynamic analysis. set breakpoints, inspect registers and stack, step through assembly. pwndbg adds heap inspection and pattern commands.
- Ghidra — free decompiler from NSA. turns assembly into readable C-like pseudocode. essential for understanding binary logic without source.
- ROPgadget / ropper — find ROP gadgets in the binary. filter by instruction sequence to build your chain.
- checksec — audit binary protections in one command. tells you NX, PIE, canary, RELRO status.
- one_gadget — find single gadgets in libc that call execve("/bin/sh") with specific register constraints. faster than a full ROP chain if constraints match.