Format String Vulnerabilities: Read, Write, and GOT Overwrites
Cybersecurity
Format string bugs are not extinct. Cover printf-family abuse for stack reads with %p, arbitrary writes with %n, GOT overwrites, and detection with FORTIFY_SOURCE.
By Arjun Raghavan, Security & Systems Lead, BIPI · December 27, 2024 · 9 min read
printf(user_input) is the classic format string bug. It still ships, especially in embedded firmware, custom logging libraries, and code that thought it was safe because the input was already escaped against XSS. Format strings give you stack read and write primitives in one bug.
The Read Primitive
%p prints a pointer from the next variadic argument slot. On x86_64, the first 5 variadic args come from RSI, RDX, RCX, R8, R9, then the stack. Send %1$p, %2$p, %3$p... to walk register and stack contents. Within the first dozen reads you usually find a libc address, the canary, and a stack pointer, full ASLR break.
- %p reads a pointer-sized value, %s reads a string from a pointer, dangerous (segfault risk)
- %n$p with positional argument selector lets you jump to any slot directly
- %llx for raw 64-bit, %d for signed integer, all useful for canary leak
- Combine with format string offset finder: AAAA%6$p to identify where your input lands
The Write Primitive
%n writes the number of bytes printed so far to a pointer argument. %hn writes a short, %hhn writes a byte. With width specifiers (%100c) you can control the count. To write 0xdeadbeef to an address: pad to 0xbeef chars then %hn, jump to upper half via positional arg, pad more, %hn again. fmtstr_payload in pwntools automates this entirely.
GOT Overwrite
Without Full RELRO, the GOT is writable. Format string write to printf@GOT can replace the printf address with system. Next call to printf(user_str) becomes system(user_str). One-shot RCE. Full RELRO (-Wl,-z,now -Wl,-z,relro) kills this by marking GOT read-only after init.
If a binary has format string and partial RELRO in 2024, you have GOT overwrite to system in under fifty bytes. Always check Full RELRO before declaring victory in hardening reviews.
Workflow
- Identify the bug: source review for printf, fprintf, syslog, snprintf with user-controlled first arg
- Find offset: send 'AAAA' + '.%p' * 20, look for 0x41414141 in output, position is your offset
- Pick target: GOT entry for a function called after the printf (puts, exit, free)
- Generate payload: fmtstr_payload(offset, {got_puts: system_addr})
- Trigger: send payload, get shell on next call to the hooked function
FORTIFY_SOURCE
Glibc's FORTIFY_SOURCE wraps printf to detect %n with non-literal format strings backed by writable memory. With _FORTIFY_SOURCE=2 and -O2, compile-time and runtime checks abort on %n abuse. Bypass requires either format strings without %n (read-only attacks) or environments without FORTIFY (alpine musl, busybox, custom libcs in firmware).
Real Targets in 2024
- Embedded printers, routers, and IoT firmware still ship busybox without FORTIFY
- Custom logging code that wraps printf rarely matches the FORTIFY pattern even on glibc
- Lua, Python, and other binding layers occasionally pass user input into native printf
- CVE-2024-3596 BlastRADIUS exposed format-string-adjacent integrity issues in RADIUS implementations
Detection in Code Review
- grep for printf, fprintf, sprintf, snprintf, syslog, asprintf without literal format string
- Clang -Wformat-security warns on non-literal format strings, enable as -Werror
- Coverity, Semmle/CodeQL, and Snyk Code all have rules for format string sinks
- Static binary analysis: angr or radare2 to trace tainted input into printf-family
Defense
- Always: printf('%s', user_input), never printf(user_input)
- Compile with -Wformat=2 -Wformat-security -Werror=format-security
- Enable _FORTIFY_SOURCE=2 and Full RELRO on every shipped binary
- For non-glibc targets (musl, uclibc), audit log macros manually, the protection often is not there
Format strings are easy bugs to find, easy bugs to exploit, and inexplicably easy bugs to ship. Every firmware audit should grep for them on day one.
Read more field notes, explore our services, or get in touch at info@bipi.in. Privacy Policy · Terms.