← Back to Portfolio CTF Writeup
TryHackMe

Flag Vault 2

PWN Format String Vulnerability
Target"Patched" Flag Vault binary
VulnerabilityFormat string bug via printf(username)
SeverityHIGH
ReferencesCWE-134 · CWE-787 · OWASP A03:2021
Toolspwntools (Python)
FlagTHM{format_issues}

1. The "Fix" That Broke Things

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.

char username[100]; char flag[100]; read_flag_into_memory(flag); // flag loaded but never printed printf(username); // VULNERABLE: input as format string // should be: printf("%s", username);

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.

2. Why printf(username) Is Dangerous

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.

SpecifierReadsUse
%pPointer value off the stackMap stack layout, find offsets
%xRaw hex word off the stackLeak adjacent stack data
%sString at the pointed-to addressDereference and print the flag
%N$sString at the Nth stack argumentDirectly target the flag’s slot

3. Locating the Flag on the Stack

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.

from pwn import * p = process('./vault2') # Enumerate stack slots to find the flag pointer p.sendline(b'%p %p %p %p %p %p') print(p.recvline()) # inspect leaked addresses

Testing positional reads, stack position 5 held the pointer to the flag string. Dereferencing that slot with %s prints the flag itself.

4. Leaking the Flag

from pwn import * p = process('./vault2') # %5$s -> read the string pointed to by stack argument #5 p.sendline(b'%5$s') print(p.recvall().decode())

The %5$s payload instructs printf to treat the 5th stack value as a char* and print the string it points to — the flag.

5. Flag

$ python3 exploit.py [+] Starting local process './vault2' [+] Receiving all data: Done THM{format_issues}
Key Takeaways
Attacker Perspective

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.

Defender / Remediation

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.