Description
Overflow the stack buffer so that the neighboring local variable num becomes 65. Once num holds the magic value the binary prints the flag.
Setup
Grab both the binary and its source to understand how the 16-byte buffer and num variable sit in memory.
Run it locally to test candidate payload lengths before attacking the remote instance.
wget https://artifacts.picoctf.net/c/519/local-targetwget https://artifacts.picoctf.net/c/519/local-target.ccat local-target.cSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Measure the offsetObservationI noticed the source code declared a fixed 16-byte input buffer immediately before a neighboring int variable, which suggested the buffer size and compiler alignment padding together determined exactly how many bytes to write before reaching the target variable.num is stored after the 16-byte input buffer with 8 bytes of alignment padding between them. Feeding exactly 24 bytes fills through the buffer and its alignment padding without touching num; the 25th byte overwrites the low byte of num.Learn more
A stack buffer overflow occurs when more data is written to a fixed-size buffer than it can hold. The excess bytes spill into adjacent memory, overwriting whatever variables happen to be stored there. On the stack, local variables are allocated in a predictable order (though compilers may reorder them or add padding), so overflowing one variable can reliably reach and overwrite a neighboring one.
In this binary, the source code declares
char input[16]followed byint num = 64. On the stack, the compiler adds 8 bytes of alignment padding betweeninputandnum, so the effective offset from the start of the buffer to the start ofnumis 24 bytes rather than 16. Writing exactly 24 bytes fillsinputand its trailing padding without touchingnum; writing 25 bytes overwrites the first byte ofnum.Reading the source code (when available) is always the first step. The source reveals the buffer size, the target variable, its initial value, and the win condition. With source code, you can calculate the exact offset mathematically rather than guessing. Without source code, you would use GDB to map the stack layout or use a cyclic pattern (e.g., from pwntools'
cyclic()) to determine offsets experimentally.Step 2
Overflow by one byteObservationI noticed that num needed to equal 65 (0x41) and that the ASCII value of 'A' is exactly 65, which suggested sending a 25-byte payload where the final byte is 'A' to overwrite the low byte of num with the correct value.Adding a single extra byte overwrites the low byte of num. Writing 'A' bumps it from 64 (0x40) to 65 (0x41), which satisfies the win condition.pythonpython3 - <<'PY' print('12345678901234567890123AA') PY | nc saturn.picoctf.net 64108Expected output
picoCTF{l0c4l5_1n_5c0p...8441a}What didn't work first
Tried: Sending exactly 24 bytes of padding (filling the buffer and alignment gap) and expecting num to change
24 bytes fills input[16] and the 8-byte alignment padding but does not reach num, so num stays at 64 and the win condition is never triggered. You need 25 bytes total - 24 to fill through the padding, then one more byte that actually overwrites the low byte of num.
Tried: Sending 'A' * 25 and expecting num to become 65 because 25 bytes of 'A' are sent
The 25th byte lands on the least-significant byte of num and sets it to 0x41 (65) only if that 25th byte is the character 'A'. Sending a different character like 'B' (0x42 = 66) or '\x00' would write a different value, failing the check. The payload must ensure the overflowing byte is exactly 'A' (0x41) to satisfy num == 65.
Learn more
The byte
'A'has ASCII value 65 (0x41). The variablenumis initialized to 64 (0x40). Since x86 is little-endian, the least significant byte ofnumis stored at the lowest address - immediately adjacent to the end of the buffer. Writing one extra byte past the buffer writes directly into that low byte ofnum, changing it from0x40to0x41(i.e., from 64 to 65).This is a classic example of a one-byte overflow or off-by-one overflow. In real-world vulnerabilities, off-by-one errors in bounds checking are surprisingly common and have led to serious exploits. The difference between
<=and<in a length check, or betweenstrlen()(excludes null terminator) andsizeof()(includes null), can create exactly this type of overflow.The payload here uses
python3 -c 'print(...)'piped intonc(netcat) to send data to the remote service. Netcat is the standard tool for sending raw data to TCP services in CTF exploitation. For more complex exploits, pwntools (a Python library) provides a much richer API:p32()/p64()for packing integers into bytes,remote()for connections, andprocess()for local testing.Step 3
Capture the flag outputObservationI noticed the binary's win condition was a simple equality check on num, which suggested that once the overflow sets num to 65 the program would immediately print the flag without any further interaction.Once num == 65, the binary congratulates you and prints the picoCTF flag directly.Learn more
When the overflow successfully sets
numto 65, the program's conditional check (if (num == 65)) passes and it prints the flag. This demonstrates the core principle of buffer overflow exploitation: corrupting program state to reach code paths that were not intended to be accessible with normal input.Local Target is a deliberately simple example of this class of vulnerability. Real-world stack overflows target the return address - the address on the stack that the function will jump to when it returns. By overwriting the return address with the address of a useful function (like a
win()function that the developer left in the binary) or with shellcode, an attacker can gain arbitrary code execution. Protections like stack canaries, ASLR, and non-executable stacks (NX/DEP) exist specifically to make this harder.The picoGym buffer overflow series (local-target, buffer-overflow-0, buffer-overflow-1, buffer-overflow-2, buffer-overflow-3) progressively removes training wheels: first you overwrite a neighbor variable, then a return address in a binary without protections, then with increasing numbers of mitigations active. This progression mirrors the actual learning path for binary exploitation and pwn CTF categories.
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{l0c4l5_1n_5c0p...8441a}
Any payload that increases num from 0x40 to 0x41 will work; the example string above is just one convenient option.