Description
A simple C program reads user input into a fixed-size buffer on the stack without bounds checking. Overflow the buffer to trigger SIGSEGV - the signal handler for the crash prints the flag.
This is the most introductory buffer overflow challenge in picoCTF 2022, requiring no return-address control - just enough bytes to crash the program.
Setup
Connect to the challenge server via netcat. No local binary is required.
Send more than 16 bytes of input to overflow the stack buffer.
nc saturn.picoctf.net <PORT_FROM_INSTANCE>python3 -c "print('A'*100)" | nc saturn.picoctf.net <PORT_FROM_INSTANCE>Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Understand the vulnerabilityObservationI noticed the challenge description says user input is read into a fixed-size buffer without bounds checking, which immediately pointed to a classic C stack buffer overflow using an unsafe function like gets() or strcpy() as the root cause.The program uses gets() or a similarly unsafe function to fill a fixed-size buffer. No bounds check means any input larger than the buffer overflows adjacent stack space.Learn more
Buffer overflows are the classic memory-corruption vulnerability. When a program copies user input into a stack-allocated array without checking the length, bytes beyond the array boundary overwrite adjacent memory - saved frame pointers, return addresses, and local variables of calling functions.
The C functions
gets(),strcpy(), andsprintf()are all unsafe because they perform no length validation. Modern C code should usefgets(buf, sizeof(buf), stdin)orstrncpy()instead. Thegets()function was deprecated in C99 and removed entirely in C11 for this reason.When overflowed memory contains critical control-flow data (like a saved return address), the CPU attempts to jump to a garbage address, triggering a SIGSEGV (segmentation fault). In this challenge, the SIGSEGV handler is deliberately set to print the flag - so triggering the crash is sufficient.
Step 2
Send the overflow payloadObservationI noticed the buffer is declared as 16 bytes and no canary protection is mentioned, which suggested that piping a large blob of repeated bytes (well beyond 16) remotely via netcat would reliably clobber the saved return address and trigger the SIGSEGV handler.Pipe ~100 bytes of 'A' into the program. The buffer is 16 bytes; anything past it spills into adjacent stack data and eventually corrupts the saved return address.pythonpython3 -c "print('A'*100)" | nc saturn.picoctf.net <PORT_FROM_INSTANCE>pythonpython3 -c "import sys; sys.stdout.buffer.write(b'A'*100)" | nc saturn.picoctf.net <PORT_FROM_INSTANCE>Expected output
picoCTF{ov3rfl0ws_ar3_ez_56...}What didn't work first
Tried: Sending exactly 16 bytes (the declared buffer size) to fill it completely.
Filling the buffer exactly leaves the saved return address untouched, so the program returns normally and the SIGSEGV handler never fires. You need to send enough bytes to spill past the buffer AND overwrite adjacent stack data - anything from 17 bytes up will start corrupting the frame, but a safe margin like 100 bytes guarantees the return address itself is clobbered.
Tried: Typing 'A's interactively into nc instead of piping them.
Typing interactively sends each line only after you press Enter, so the server reads your input one buffered line at a time. The newline counts as one of your bytes and the server's read loop may process the input before you've sent enough. Piping a pre-built payload ensures all bytes arrive in one write, which is the reliable way to deliver overflow input to a remote service.
Learn more
The exact overflow size is layout-dependent: how big the buffer is, what local variables sit between it and the saved frame pointer, whether a stack canary is present, and so on. 100 bytes is a deliberately oversized blunt-force value. Anything >= 32 bytes will overrun this particular buffer; you want enough to clobber the return address regardless of layout.
If the program is whitespace-sensitive,
print()'s trailing newline can become a problem. Two trailing-newline-free alternatives:python3 -c "import sys; sys.stdout.buffer.write(b'A'*100)"orecho -n. Thebuffer.writeform also avoids any encoding surprises.Despite the description hinting at NX, NX (
checksec --filewill confirm) doesn't apply here: you're not executing your input as code. The flag is printed by the SIGSEGV handler, which runs after the crash but before the process exits. NX would only matter if you were trying to jump into your buffer.Step 3
Read the flag from the SIGSEGV handler outputObservationI noticed the challenge description says the signal handler for the crash prints the flag, which meant that once the overflow triggered a SIGSEGV the output on stdout would contain the flag with no further exploitation needed.The challenge process registers a SIGSEGV handler that prints the flag and exits. Triggering the crash is the entire exploit.Learn more
A signal handler is a function registered with
signal(SIGSEGV, handler)orsigaction. When the kernel raises SIGSEGV (memory access violation), execution jumps into the handler before the process is killed. The challenge author wired the handler to print the flag - so a clean fault is the win.In real exploitation this is the opposite of normal: signal handlers usually fight the exploit by catching the crash, logging it, and restarting the process. The challenge inverts that for didactic effect.
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{ov3rfl0ws_ar3_ez_56...}
Send more than 16 bytes to crash the program; the SIGSEGV handler prints the flag automatically.