Input Injection 1 picoMini by CMU-Africa Solution

Published: April 2, 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 1
    Find the two adjacent buffers
    Observation
    I noticed the program reads input into a small fixed buffer and then calls system() with a separate buffer, which suggested the stack layout placed these two buffers adjacently and that overflowing the first could 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 2
    Overflow into the command buffer
    Observation
    I noticed the first input buffer is exactly 10 bytes, which suggested that sending 10 bytes of padding followed by 'cat flag.txt' would place the command string at the start of the adjacent system() command 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 start at the beginning of the command buffer. The extra filler bytes land in the middle of the existing command string, producing garbled output or 'command not found' rather than flag output. The padding must exactly match the first buffer size.

    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 indefinitely. Adding '\n' (or using Python's sys.stdout.buffer.write with the newline included) ensures the server reads and processes 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

Memory layout on the stack places local variables in declaration order, so adjacent buffers can be overwritten by overflowing one into the next without ever touching the return address. When any of those adjacent buffers feeds into a shell-invoking function like system(), the overflow becomes direct command injection rather than a control-flow attack. This data-only exploitation path is harder to block with canaries or ASLR alone because neither protects the contents of adjacent data buffers. The same pattern appears in real-world embedded firmware and industrial control software where small fixed-size inputs are processed next to command template buffers.

Related reading

Want more picoMini by CMU-Africa writeups?

Useful tools for Binary Exploitation

What to try next