Skip to main content

Input Injection 2 picoMini by CMU-Africa Solution

A binary exploitation challenge with input filtering in place. Find a way past the defenses to redirect program execution.

Published: April 2, 2026Updated: August 13, 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 1Read the two buffer addresses and the offset
    Observation
    The program prints both the username buffer address and the shell command buffer address at startup. Subtracting one from the other gives the exact overflow offset, with no guessing or hardcoded 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 chunk sizes vary by platform, libc version, and compile flags. If the username buffer really has 28 usable bytes, a hardcoded 32-byte pad overshoots the command buffer entirely, corrupting memory past it and giving a segfault or a silent no-op rather than a replaced command. Subtracting the two leaked addresses gives the exact offset for this instance.

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

    ASLR and heap layout on the remote server differ from your local binary unless the libc and allocator match exactly. Cyclic patterns earn their keep when nothing is leaked, but here the program prints both addresses outright, so subtraction is faster and more reliable than local reversing that may not match.

    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 2Overflow with a no-whitespace command
    Observation
    The input is read by scanf("%s"), which stops at the first whitespace character. So the payload has to be one whitespace-free token, using input redirection (cat<flag.txt) or $IFS substitution.
    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") stops at the first whitespace, so the payload splits in two and only 'cat' plus the filler reaches the command buffer. The program then runs a bare 'cat' with no argument, prints nothing, and never reads the flag. Use input redirection, 'cat<flag.txt', or the IFS trick, 'cat${IFS}flag.txt', to keep the payload one 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 to chain commands in before the program reads input; the connection goes straight to the scanf call. The only injection point is the username buffer overflowing into its neighbour, so the whole no-space payload has to arrive in that single read, before system() runs.

    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

An overflow that lands next to a command buffer combines two primitives: an unbounded write, and system() executing attacker-controlled data. The no-whitespace constraint is a common real filter-bypass problem, and shell features like input redirection and IFS substitution exist precisely to express commands without literal spaces, which is why they show up in privilege-escalation payloads and CTF exploits alike. Any C program reading input with scanf("%s"), gets(), or an unbounded strcpy is a candidate for this attack.

Related reading

Useful tools for Binary Exploitation

Where to go next