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.
Setup
Connect to the challenge server and read the source/disassembly if provided to see how your input is read and stored.
nc <host> <PORT_FROM_INSTANCE>Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Find the two adjacent buffersObservationI 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.Step 2
Overflow into the command bufferObservationI 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.pythonpython3 -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.txtorcat${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.