Lion Cipher Lab
Exploit a format string vulnerability (in your own lab)
- Penetration Testing / Red Team
- Advanced
- ~60 min
- Free · no account required
Why it matters
This is the third rung of binary exploitation, and it is a genuinely different beast from an overflow. An overflow smashes memory by writing PAST a buffer; a format string bug needs no overflow at all — it hands attacker-controlled text straight to `printf` as the FORMAT argument, and printf will faithfully do whatever that text says: dump the stack with `%p`, read arbitrary memory with `%s`, and — the crown jewel — WRITE to an arbitrary address with `%n`. One missing `"%s",` turns a harmless log line into a read/write primitive over the whole process. The class is old and still very much…
What you'll learn
- reading a format string bug in C on sight: WHY `printf(user_input)` is catastrophic while `printf("%s", user_input)` is safe, and recognising the same shape in logging wrappers and legacy code
- the varargs / stack-argument mechanism that makes the bug work: how printf blindly walks registers then the stack looking for arguments that were never pushed
- using the leak primitive (`%p`) to dump stack words and COUNT to the positional offset where your own input buffer appears, then addressing it directly with `%N$p`
- the read primitive (`%s`) and the write primitive (`%n`) — what each one does, and why `%hn`/`%hhn` short writes exist (so you never have to print billions of bytes)
- building a DETERMINISTIC 64-bit write that puts a fixed global's address in the buffer and uses `%N$n` to flip it — no ASLR bypass needed, because a -no-pie global lives at a known address
- the 64-bit gotcha 32-bit hides: addresses contain NUL bytes, so the target address must go at the END of the format string or printf stops parsing early
- the judgment: preventing this class for real with `-Wformat -Wformat-security`, FORTIFY_SOURCE, and the iron rule of never passing user data as a format string — and seeing a hardened build reject the exact payload that just worked
Steps in this lab
- Read this first: still your own machine, still authorized-only
- What a format string actually is (the mechanism the bug abuses)
- Why `printf(buf)` is catastrophic and `printf("%s", buf)` is safe
- Get Linux and the toolchain
- Write the vulnerable program — with a global that gates access
- Compile with mitigations off for teaching — and watch the compiler warn you
- Leak: dump the stack with %p and find WHERE your input lands
- Read: use %s at that offset to dereference memory (the read primitive)
- Write: use %n to flip the global and grant yourself access
- Why short writes exist: %hn and %hhn (don't print four billion bytes)
- The defenses that make this hard — and watch a hardened build reject your payload