Description
Can you use your knowledge of format strings to make the customers happy?
Setup
Connect to mimas.picoctf.net <PORT_FROM_INSTANCE> via netcat.
Observe the menu items in each round and look for strings containing %.
nc mimas.picoctf.net <PORT_FROM_INSTANCE>Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Round 1
ObservationOne menu item is named Gr%114d_Cheese, with a printf specifier sitting inside the name. The server passes item names straight to printf, so selecting that item leaks stack data.You don't type%114dintoncdirectly. You select the menu item whose name isGr%114d_Cheeseand the server passes that string toprintf. Becausefgetsonly pushed the buffer pointer (no extra ints),%drenders whatever stack garbage sits at the next slot, padded to width 114, leaking the bytes. See the format string guide for the full leak primitive.bashGr%114d_CheeseWhat didn't work first
Tried: Typing %114d directly into the nc prompt instead of selecting the menu item by name
Your choice is read as a menu selection, not as printf input. The specifier only reaches printf because it is baked into the item name the server prints. Select the Gr%114d_Cheese item; typing the specifier yourself does nothing.
Tried: Using %114s instead of %114d to read stack data in Round 1
%s makes printf dereference the stack value as a char pointer, which usually points at unmapped memory and segfaults the server. %d reads the raw integer in that slot without following it, which is the safe leak here.
Learn more
A format string vulnerability occurs when user-controlled input is passed directly as the format string argument to
printf()or similar functions. Instead ofprintf("%s", user_input), the vulnerable code callsprintf(user_input). This allows an attacker to inject format specifiers like%d,%s,%p, and%nthat causeprintfto read from (or write to) the stack.The
%114dspecifier tellsprintfto print an integer with a minimum field width of 114 characters. Since there's no corresponding integer argument on the stack for this extra specifier,printfreads whatever value happens to be on the stack next - which could be a return address, a pointer, or sensitive data. This is called stack data leakage.What printf does with "Gr%114d_Cheese" when nothing else is pushed: arg slot 1: rsi (or [rsp+0x00] if 6 args were already used) -> printf reads it as int, prints with width 114 -> result is 114-char-wide ASCII rendering of whatever happened to be at that stack slot Output: Gr<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<...123456789>_Cheese The "leaked int" is part of a saved register or canary frame the function used before calling printf.Format string bugs were extremely prevalent in the late 1990s and early 2000s, leading to high-profile exploits in syslog, wu-ftpd, and many other servers. While modern compilers produce warnings for
printf(user_input), the vulnerability still appears in legacy code, embedded systems, and cases where the format string is dynamically constructed.The OWASP Testing Guide includes format string testing as part of input validation testing. Fuzzing tools like AFL and libFuzzer can automatically detect format string vulnerabilities by monitoring for crashes or unusual output when format specifiers are injected into inputs.
Step 2Round 2
ObservationThe Round 2 item Cla%sic_Che%s%steak hides three %s specifiers inside what looks like a misspelled Classic Cheesesteak. Each one makes printf dereference a stack slot as a char pointer, so three of them give three chances at the buffer holding the flag.Select Cla%sic_Che%s%steak so printf interprets each %s and prints arbitrary stack entries, eventually revealing picoCTF{...}.bashCla%sic_Che%s%steakWhat didn't work first
Tried: Using a single %s instead of three chained specifiers to read the flag
A single %s dereferences only the first unconsumed slot, and the flag pointer is not guaranteed to sit there; where it lands depends on the depth of the printf frame. Two more specifiers walk further up the stack and cover more candidates.
Tried: Using %p instead of %s to find the flag address in Round 2
%p prints each stack slot as raw hex without following it, so you see an address like 0x7ffd and never the string behind it. %s is what makes printf follow the pointer and print the bytes at that address.
Learn more
The
%sspecifier tellsprintfto treat the next stack argument as a pointer and print the null-terminated string at that address. When there's no corresponding argument,printfreads the next value off the stack and treats it as a string pointer. If that value happens to point to readable memory containing the flag, it gets printed. If it points to unmapped memory, the program crashes with a SEGFAULT - which explains the flag textSEGFAULTin the flag.Format string: "Cla%sic_Che%s%steak" printf walks specifiers in order: %s #1 -> deref rsi/rdx/... as char* -> dump bytes until \0 %s #2 -> deref next arg -> dump bytes until \0 %s #3 -> deref next arg -> dump bytes until \0 Three reads = three chances that one of the leaked pointers happens to point at a buffer containing "picoCTF{...}". Common winners on this binary: - the flag is read into a stack-resident char buf[] - that buf's address is sitting on the stack right above the printf frame because main called read(buf,...) - one of the %s walks lands on that pointer and prints the flag verbatimChaining multiple
%sspecifiers (%s%s%s...) walks further up the stack, reading more memory with each specifier. The attacker doesn't need to know the exact stack layout in advance - they can spray many specifiers and observe which one prints useful data. This brute-force approach makes format string bugs practical even without debugging access.The specific menu item names in this challenge are cleverly crafted to hide the format specifiers in plain sight.
Cla%siclooks like "Classic" with a typo;Che%s%steakresembles "Cheesesteak." This social engineering aspect - making malicious input look benign - is a technique used in real attacks where format strings appear in log entries, usernames, or other inputs that humans might not scrutinize carefully.
Interactive tools
- pwntools Payload BuilderPack integers into little-endian bytes (p32 / p64), unpack bytes back to integers, and build flat ROP payloads with offset-based insertion.
Flag
Reveal flag
picoCTF{7h3_cu570m3r_15_n3v3r_SEGFAULT_dc...}
Ordering the format-string specials leaks the flag directly in the connection output.
Key takeaway
How to prevent this
How to prevent this
Format string bugs are 100% preventable at compile time. Every modern toolchain ships the controls.
- Always pass user input as an argument, never as the format itself:
printf("%s", user_input), notprintf(user_input). Same rule forfprintf,sprintf,syslog,snprintf. - Compile with
-Wformat -Wformat-security -Werror=format-security. GCC and Clang will refuse to build the bad pattern. Add_FORTIFY_SOURCE=2for extra runtime checks on%n. - In code review, treat any dynamically built format string as a finding. If the format genuinely needs to be variable, build it from a fixed allowlist, never from request data.