Skip to main content

Input Injection 1 picoMini by CMU-Africa Solution

A binary service takes user input without proper sanitization. Exploit this flaw to hijack execution and read the flag.

Published: April 2, 2026Updated: August 13, 2026

Description

A stack buffer overflow, but not the return-address kind. A small input buffer sits right before a second buffer that the program passes to system(). Overflow the first buffer so your text spills into that command buffer, and you control what gets executed.

Remote

Connect to the challenge server and read the source/disassembly if provided to see how your input is read and stored.

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 1Find the two adjacent buffers
    Observation
    The program reads input into a small fixed buffer, then calls system() with a separate one. If the stack puts those two next to each other, overflowing the first will corrupt the second.
    The program reads your input into a small fixed buffer (10 bytes) with an unbounded strcpy/gets-style copy. Immediately after it on the stack is a second buffer that the program passes to system(). Because the copy is not length-bounded, your input overruns the first buffer and writes into the command buffer.
    bash
    # Read the source / disassembly: confirm buffer[10], then a command buffer, then system(command).
    What didn't work first

    Tried: Look for a win() function or a one_gadget address to redirect execution to.

    There is no win() function in this binary. The vulnerability bypasses control-flow entirely: the overflow writes data into the command buffer that system() will run, so return-address overwriting and ROP gadgets are not needed or useful here.

    Tried: Assume the saved return address is the target and calculate a cyclic-pattern offset with pwndbg's cyclic command.

    A cyclic pattern finds the saved-RIP offset, but that offset is irrelevant here. Overwriting the return address crashes the process before system() is even called with your injected command. The target is the data buffer, not the instruction pointer.

    Learn more

    Why this is command injection, not control-flow hijack. The classic stack overflow overwrites the saved return address. Here the more direct target is data: a buffer the program will run through system(). Overflowing your input straight into that buffer means whatever you write becomes the shell command, so you never need a gadget, a leak, or a win function.

  2. Step 2Overflow into the command buffer
    Observation
    The first input buffer is exactly 10 bytes. So 10 bytes of padding followed by 'cat flag.txt' lands the command string at the start of the adjacent system() buffer.
    Fill the 10-byte input buffer, then append the command you want executed. The bytes after the padding land in the command buffer that system() runs, so 'cat flag.txt' reads the flag.
    python
    python3 -c "import sys; sys.stdout.buffer.write(b'a'*10 + b'cat flag.txt\n')" | nc <host> <PORT_FROM_INSTANCE>
    bash
    # 10 bytes of filler reach the end of the input buffer; the rest overwrites the system() command.

    Expected output

    picoCTF{0v3rfl0w_c0mm4nd_...}

    The exact padding (10 here) is the size of the first buffer; confirm it from the source or by testing. If the input is read with a function that stops at whitespace (scanf %s), use a no-space command such as cat<flag.txt or cat${IFS}flag.txt.

    What didn't work first

    Tried: Use fewer padding bytes (e.g. 8) to try to avoid crashing the program.

    With fewer than 10 filler bytes the command string does not begin at the start of the command buffer. The leftover bytes land mid-string, so you get garbled output or 'command not found' rather than the flag. The padding has to match the first buffer size exactly.

    Tried: Send 'cat flag.txt' through the pipe without the newline, expecting the server to flush and execute.

    Most remote challenge servers wait for a newline before processing input. Without the trailing '\n', the payload sits in the socket buffer and the server blocks forever. Include the newline, whether by hand or through sys.stdout.buffer.write, so the server reads the full input.

    Learn more

    Why the padding equals the buffer size. The two buffers are adjacent on the stack, so the distance from the start of your input to the start of the command buffer is just the first buffer's length. Filling exactly that many bytes puts your next characters at the very start of the command string passed to system().

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_c0mm4nd_...}

Not a return-address overflow. A 10-byte input buffer sits before a command buffer passed to system(); overflow your input with 10 bytes of filler then a shell command (e.g. cat flag.txt) so it becomes the system() argument.

Key takeaway

The stack lays local variables out in declaration order, so overflowing one buffer into the next overwrites it without ever touching the return address. When the neighbouring buffer feeds a shell-invoking function like system(), that overflow becomes direct command injection rather than a control-flow attack. Canaries and ASLR do not help, because neither protects the contents of adjacent data buffers. The same pattern turns up in embedded firmware and industrial control software, where small fixed-size inputs sit right next to command template buffers.

Related reading

Useful tools for Binary Exploitation

Where to go next