Input Injection 2 picoMini by CMU-Africa Solution

Published: April 2, 2026

Description

A heap overflow that turns into command injection. A username buffer read with scanf("%s") sits next to a second buffer pre-filled with a harmless command (/bin/pwd) that the program runs via system(). Overflow the username into that command buffer, but mind that scanf stops at whitespace, so your payload must contain no spaces.

Remote

Connect to the server and note that it prints two buffer addresses on startup.

bash
nc <host> <PORT_FROM_INSTANCE>

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Read the two buffer addresses and the offset
    Observation
    I noticed the program prints both the username buffer address and the shell command buffer address on startup, which suggested that subtracting the two values would give the exact overflow offset instead of guessing or hardcoding a chunk size.
    The program allocates a username buffer (read with scanf("%s"), about 28 bytes of usable space) and, adjacent to it, a shell command buffer initialized to /bin/pwd that it later runs with system(). It prints both addresses; the difference is the overflow offset from the username to the command buffer.
    bash
    # The service prints something like: username @ 0x..., shell @ 0x...
    bash
    # offset = shell_addr - username_addr  (about 28 bytes)
    What didn't work first

    Tried: Assume the offset is always exactly 32 bytes (one standard malloc chunk) without reading the printed addresses.

    Heap allocator chunk sizes vary by platform, libc version, and compile flags. If the actual username buffer is 28 bytes of usable space, a hardcoded 32-byte pad overshoots the command buffer entirely, corrupting memory past it and causing a segfault or silent no-op instead of replacing the command. Reading the two leaked addresses and subtracting gives the exact offset for the running instance.

    Tried: Use a debugger or pwntools cyclic pattern locally to find the offset instead of using the server-leaked addresses.

    The ASLR and heap layout on the remote server will differ from a local binary unless you have the exact same libc and allocator. Cyclic patterns are useful when no addresses are leaked, but here the program prints both buffer addresses directly, making subtraction more reliable and faster than local reversing that may not match the remote heap layout.

    Learn more

    Why the leaked addresses matter. You do not have to reverse chunk metadata: the program hands you both buffer addresses, so the padding length is a simple subtraction. The bug is that the username read is not bounded to its buffer, so writing past it reaches the adjacent command buffer that system() will execute.

  2. Step 2
    Overflow with a no-whitespace command
    Observation
    I noticed that the input is read by scanf("%s"), which terminates on any whitespace character, which suggested crafting a payload that uses input redirection (cat<flag.txt) or $IFS substitution to express the target command as a single whitespace-free token.
    Send 'offset' bytes of filler followed by a shell command that contains no whitespace, so scanf reads the whole thing into the username buffer and it overruns into the command buffer. Use shell tricks to avoid spaces: cat<flag.txt (input redirection) or cat${IFS}flag.txt (IFS expands to whitespace).
    python
    python3 -c "import sys; sys.stdout.buffer.write(b'A'*28 + b'cat<flag.txt\n')" | nc <host> <PORT_FROM_INSTANCE>
    bash
    # or, using the IFS trick for the space:
    python
    python3 -c "import sys; sys.stdout.buffer.write(b'A'*28 + b'cat${IFS}flag.txt\n')" | nc <host> <PORT_FROM_INSTANCE>

    Expected output

    picoCTF{0v3rfl0w_t0_c0mm4nd_...}

    The 28-byte offset is the typical value; use the exact difference of the two leaked addresses for your instance. The no-whitespace requirement is the whole trick: scanf("%s") would truncate a command containing a literal space.

    What didn't work first

    Tried: Send 'A'*28 + 'cat flag.txt' with a literal space between cat and flag.txt.

    scanf("%s") terminates reading at the first whitespace character, so the payload is split into two tokens and only 'cat' (plus the filler) reaches the command buffer. The program then tries to run just 'cat' with no argument, produces no output, and the flag is never read. Replacing the space with the input-redirection operator 'cat<flag.txt' or the IFS trick 'cat${IFS}flag.txt' keeps the entire payload as one whitespace-free token.

    Tried: Use printf or echo to build the command on the remote side instead of overflowing the buffer directly.

    There is no shell session available to chain commands before the program reads input; the connection goes straight to the scanf call. The only injection point is the username buffer that overflows into the adjacent command buffer, so the entire no-space payload must arrive in that single scanf read before system() is called.

    Learn more

    Why no-whitespace command injection. scanf("%s") reads a single whitespace-delimited token, so a space, tab, or newline ends your input early and the command buffer never gets your full payload. Shell features that encode a separator without a literal space, like input redirection < or the ${IFS} variable, let you express cat flag.txt as one whitespace-free token.

Interactive tools
  • Cyclic Pattern GeneratorGenerate de Bruijn cyclic patterns and find buffer overflow offsets. The browser equivalent of pwntools cyclic and cyclic_find.
  • 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{0v3rfl0w_t0_c0mm4nd_...}

A scanf("%s") username buffer (~28 bytes) overflows into an adjacent command buffer (init /bin/pwd) that system() runs. Pad to the leaked-address difference, then append a no-whitespace command (cat<flag.txt or cat${IFS}flag.txt) since scanf stops at spaces.

Key takeaway

Buffer overflows that land next to a command buffer combine two primitives: a memory-safety bug (unbounded write) and a code-execution primitive (system() calling attacker-controlled data). The no-whitespace constraint is a common real-world filter bypass problem; shell features like input redirection and IFS substitution exist specifically to express commands without literal space characters, and are routinely used in privilege-escalation payloads and CTF exploits alike. Any C program that reads user input with scanf("%s"), gets(), or strcpy without length bounds is potentially vulnerable to this class of attack.

Related reading

Want more picoMini by CMU-Africa writeups?

Useful tools for Binary Exploitation

What to try next