Description
Patrick and Sponge Bob were really happy with those orders you made for them, but now they're curious about the secret menu. Find it, and along the way, maybe you'll find something else of interest!
Setup
Download the binary/source for local testing, then connect to the remote menu with netcat.
Have CyberChef (or another hex→ASCII tool) ready to decode the leaked pointers.
wget https://artifacts.picoctf.net/c_mimas/50/vuln && \
wget https://artifacts.picoctf.net/c_mimas/50/vuln.c && \
nc mimas.picoctf.net 57322Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Spray the stackObservationI noticed the binary accepted user input that was passed directly to printf without a fixed format string, which suggested exploiting the format string vulnerability by sending multiple %p specifiers to dump raw stack words and locate where the flag was stored.Send a payload of repeated %p separated by commas to dump many stack words at once.bash%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%pWhat didn't work first
Tried: Send %s,%s,%s,... instead of %p to dump the stack and see the flag.
%s treats each stack word as a pointer to a null-terminated string and tries to dereference it. Most stack words are not valid string pointers, so printf either prints garbage or the process crashes with a segfault. %p reads the raw word value without dereferencing, which is why it is the safe choice for stack reconnaissance.
Tried: Use %d,%d,%d,... to print the stack values as signed integers and then convert each one to ASCII manually.
%d prints a 32-bit signed integer, truncating the 64-bit stack word and discarding the upper 4 bytes that often contain the flag characters. You see a wall of small or negative numbers with no recognizable flag bytes. %p prints the full 64-bit pointer value in hex, which is what you need to recover 8 flag characters per word.
Learn more
The
%pformat specifier prints a pointer value in hexadecimal, typically prefixed with0x. Unlike%d(decimal integer) or%s(string),%palways prints the raw word-sized value from the stack without trying to dereference it - making it the safest specifier for stack spraying since it won't crash from an invalid pointer.Separating specifiers with commas (or any printable delimiter) makes the output easy to parse: each comma-separated value corresponds to one stack word. The stack layout on a typical x86-64 Linux binary includes the format string argument itself, saved registers, local variables, return addresses, and often the flag or a pointer to it - if it was recently in scope.
The number of
%pspecifiers determines how far up the stack you read. Each specifier advances the "argument pointer" by one word (8 bytes on 64-bit). Spraying 24 specifiers reads 24 words = 192 bytes of stack data. Professional exploit developers use a direct parameter access syntax like%15$pto read the 15th stack argument directly, without the preceding 14 specifiers - cleaner but requires knowing the offset first.Stack spraying with format strings is the first step in many format string exploits. After mapping the stack layout, attackers locate the specific offset that holds useful addresses (like a stack canary, a return address, or the address of a buffer containing the flag) and target them precisely.
Step 2
Filter the useful wordsObservationI noticed that several of the leaked hex values in the %p output contained byte sequences in the ASCII printable range (0x21-0x7e), which suggested those words held the flag characters and could be identified by scanning for the known prefix 'picoCTF{' encoded as a 64-bit little-endian value.Among the outputs you'll see 0x7b4654436f636970 etc. These 0x-prefixed pointers are ASCII chunks of the flag, but they appear in reverse order.Learn more
The values
0x7b4654436f636970etc. are 8-byte (64-bit) words read directly from the stack, printed as hex. To understand why they contain flag data: when C stores a string on the stack or in a register, its bytes appear in memory in sequential order. When read as a 64-bit integer, the bytes are interpreted in little-endian order - so the last character of an 8-character chunk appears in the most significant byte and is printed first in the hex representation.Decoding:
0x7b4654436f636970→ bytes (big-endian display)7b 46 54 43 6f 63 69 70→{FTCocip→ reversed (little-endian) →picoCTF{. This reversal is a constant source of confusion in format string exploitation and must be accounted for when reassembling leaked data.The fact that flag bytes appear on the stack at all is because the program likely stored the flag string in a local variable or passed it as a function argument at some point. The stack is not garbage-collected - values persist until overwritten. This persistence is what makes stack leaking so powerful: data that was "done with" by the program may still be recoverable by an attacker who reads the stack contents.
Step 3
Decode and reorderObservationI noticed the identified hex chunks did not spell readable text directly because x86-64 stores multi-byte values in little-endian order, which suggested reversing the bytes of each chunk individually and then reordering the chunks to reconstruct the complete flag string.In CyberChef, run From Hex then Reverse with subsection set to Byte; this undoes the little-endian printing order. Reorder the resulting chunks last-to-first to spell the flagpicoCTF{7y13_4x4_f14g_b54n1m41_5d7...}. The format string guide covers stack-leak decoding end to end.Learn more
Chunks come back in reverse because
printf's argument pointer walks the stack low-to-high, while the flag was placed bottom-up: the last chunk pushed sits at the lowest address and is read first, so reading the leaked words right-to-left restores write order.CyberChef's Reverse operation handles the endianness swap: since each 8-byte chunk was read in little-endian byte order but printed most-significant-byte-first, reversing the bytes of each chunk restores the original character order. The operation works at the byte level, not the string level - which is why you process each chunk individually before concatenating.
This manual decode-and-reorder process is exactly what pwntools automates. The
unpack()function and thestructmodule handle endianness conversions, and pwntools' format string utilities can automate the entire reconnaissance phase. Learning the manual process first builds the intuition needed to debug automated exploits when they fail.In real-world attacks, leaked stack data can reveal: ASLR bypass addresses (defeating Address Space Layout Randomization), stack canary values (bypassing stack smashing protection), and return addresses (for ROP chain construction). Format string vulnerabilities that leak stack data are therefore extremely high-severity even if they don't directly allow arbitrary write.
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{7y13_4x4_f14g_b54n1m41_5d7...}
Key takeaway
How to prevent this
How to prevent this
Stack leaks via format string do not require %n write primitives to be dangerous; they leak ASLR, canaries, and return addresses for free.
- Same root cause as format-string-0:
printf("%s", input), neverprintf(input). Compile with-Werror=format-security; modern toolchains catch this at build time. - Even when the format is fixed, treat any leaked stack value as ASLR-defeating. Combined with stack canaries and PIE, this turns one bug into a full ROP chain.
- Build with
_FORTIFY_SOURCE=2and-fstack-protector-strong. The runtime checks block%nwrites and detect canary corruption before the attacker can pivot.