In the sequel, the developer removed the buffer overflow from the original Flag Vault — bounded reads now protect both buffers. But in the process they introduced a new and equally serious bug: user input is passed directly to printf() as the format string.
The flag is read into memory but is never deliberately printed. It simply sits on the stack — which is exactly what a format string vulnerability lets us reach.
When the format string is attacker-controlled, supplying conversion specifiers such as %p, %x, or %s makes printf walk the stack looking for arguments that were never passed. Each specifier reads and prints a value from the stack, turning a benign print into an arbitrary memory read.
| Specifier | Reads | Use |
|---|---|---|
| %p | Pointer value off the stack | Map stack layout, find offsets |
| %x | Raw hex word off the stack | Leak adjacent stack data |
| %s | String at the pointed-to address | Dereference and print the flag |
| %N$s | String at the Nth stack argument | Directly target the flag’s slot |
First, fingerprint the stack by submitting a series of %p reads. Walking the positions reveals where the pointer to the flag buffer sits. Positional specifiers (%N$) let us jump straight to a chosen slot rather than padding our way there.
Testing positional reads, stack position 5 held the pointer to the flag string. Dereferencing that slot with %s prints the flag itself.
The %5$s payload instructs printf to treat the 5th stack value as a char* and print the string it points to — the flag.
When printf() receives user input as the format string instead of a fixed string, the attacker controls how printf reads memory. Stack offsets can be discovered by iterating %p or %x until the target appears. %N$s reads a string at stack position N directly.
Never pass user-controlled data as the format string argument to printf(). Always use printf("%s", userInput). Enable stack canaries and ASLR. Compiler warnings (-Wformat-security) catch this pattern at build time.