buffer overflow 0 picoCTF 2022 Solution

Published: July 20, 2023

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.

Connect to the challenge server via netcat. No local binary is required.

Send more than 16 bytes of input to overflow the stack buffer.

bash
nc saturn.picoctf.net <PORT_FROM_INSTANCE>
python
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.

Walk me through it
New to binary exploitation? Buffer Overflow and Binary Exploitation for CTF covers stack layout, ret2win, format strings, heap exploitation, and PIE bypass.
  1. Step 1
    Understand the vulnerability
    Observation
    I 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(), and sprintf() are all unsafe because they perform no length validation. Modern C code should use fgets(buf, sizeof(buf), stdin) or strncpy() instead. The gets() 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.

  2. Step 2
    Send the overflow payload
    Observation
    I 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.
    python
    python3 -c "print('A'*100)" | nc saturn.picoctf.net <PORT_FROM_INSTANCE>
    python
    python3 -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)" or echo -n. The buffer.write form also avoids any encoding surprises.

    Despite the description hinting at NX, NX (checksec --file will 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.

  3. Step 3
    Read the flag from the SIGSEGV handler output
    Observation
    I 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) or sigaction. 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.

Key takeaway

Stack buffer overflows happen when a program writes user-controlled data into a fixed-size array without checking the input length, allowing bytes past the array boundary to corrupt adjacent stack memory. Unsafe C functions like gets() and strcpy() are the canonical source of this class of bug, which is why they were deprecated or removed from the C standard. The same root cause appears everywhere C and C++ are written without bounds-checking wrappers: network daemons, embedded firmware, browser engines, and operating system kernels have all shipped critical CVEs from this single pattern.

Related reading

Want more picoCTF 2022 writeups?

Tools used in this challenge

What to try next