Skip to main content

format string 1 picoCTF 2024 Solution

Spray %p across the stack, keep the words that decode to ASCII, and reassemble them in the right order to recover the flag.

Published: April 3, 2024Updated: August 25, 2026

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!

Netcat + CyberChef

Download the binary/source for local testing, then connect to the remote menu with netcat. Get the files from the challenge page on CyLab Security Academy (formerly play.picoctf.org).

Have CyberChef (or another hex→ASCII tool) ready to decode the leaked pointers.

bash
nc mimas.picoctf.net <PORT_FROM_INSTANCE>

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
This builds on format string 0 by requiring you to leak stack data instead of just selecting menu items. Once you master stack leaks, advance to format string 2 to learn memory overwrites with pwntools. The Buffer Overflow and Binary Exploitation guide explains format string stack leaks and writes in depth.
  1. Step 1Spray the stack
    Observation
    The binary hands user input straight to printf with no fixed format string. Send a run of %p specifiers to dump raw stack words and find where the flag is sitting.
    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,%p
    What didn't work first

    Tried: Send %s,%s,%s,... instead of %p to dump the stack and see the flag.

    %s treats every stack word as a pointer to a string and dereferences it. Most words are not valid pointers, so printf prints garbage or the process segfaults. %p reads the raw value without following it, which is what makes it safe for 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 each 64-bit word and throwing away the upper four bytes, which often hold flag characters. You get a wall of small and negative numbers. %p prints the full 64-bit value in hex, eight flag characters per word.

    Learn more

    The %p format specifier prints a pointer value in hexadecimal, typically prefixed with 0x. Unlike %d (decimal integer) or %s (string), %p always 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 %p specifiers 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$p to 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.

  2. Step 2Filter the useful words
    Observation
    Several leaked words are made entirely of printable ASCII bytes, so those hold flag characters. Find them by scanning for '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 0x7b4654436f636970 etc. 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.

  3. Step 3Decode and reorder
    Observation
    The chunks do not read as text directly, because x86-64 stores multi-byte values little-endian. Reverse the bytes within each chunk, then put the chunks in order.
    In CyberChef, run From Hex on a single leaked word, then Reverse with its By argument on Character (the op only offers Character and Line, and on decoded bytes Character means byte); this undoes the little-endian printing order. Swap endianness with a word length of 8 does the same job on all the words at once. Reorder the resulting chunks last-to-first to spell the flag picoCTF{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 the struct module 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...}

The trailing bytes differ per instance. Read the full value out of the leaked stack words and unpack them little-endian, as shown above.

Key takeaway

Leaking the stack with %p dumps raw word-sized values without dereferencing them, which exposes ASLR base addresses, stack canaries, and data that lingers after going out of scope. Because x86-64 is little-endian, each 8-byte chunk arrives byte-reversed, so reassembly means reversing within chunks and then ordering the chunks. Even with no %n write available, a read-only format string bug is severe: a leaked canary plus an ASLR offset is enough to build a full ROP chain.

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), never printf(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=2 and -fstack-protector-strong. The runtime checks block %n writes and detect canary corruption before the attacker can pivot.

Related reading

Tools used in this challenge

Where to go next