Description
A binary has a stack-based buffer overflow vulnerability. Overflow the local buffer to corrupt an adjacent variable and trigger the flag output.
Setup
Connect to the challenge server.
Use checksec and static analysis to understand the binary's protections.
nc <host> <PORT>Solution
- Step 1Identify the buffer sizeRun `checksec` to see stack protections. Analyze the binary with Ghidra or GDB to find the local buffer size and the offset to the target variable or return address.
checksec --file=./binaryLearn more
checksec is a shell script that reports which binary hardening mitigations a compiled program has enabled. The key protections it checks include:
- NX/DEP - Non-Executable stack: prevents executing shellcode placed on the stack
- ASLR/PIE - Address Space Layout Randomization: randomizes where code and libraries load in memory
- Stack Canary - a secret value placed before the return address that is checked before function return; overflow detection
- RELRO - Relocation Read-Only: makes the GOT (Global Offset Table) read-only to prevent GOT overwrite attacks
A binary with no stack canary and a fixed buffer allows a classic stack overflow - writing past the buffer overwrites adjacent stack variables or the saved return address. Ghidra's decompiler shows variable layout in C-like pseudocode, making it straightforward to measure the distance from the buffer start to the target variable.
- Step 2Find the overflow offsetUse pwntools cyclic() to generate a de Bruijn pattern and find the exact offset at which the target variable is overwritten.
python3 -c "from pwn import *; print(cyclic(200))"Learn more
A de Bruijn sequence is a cyclic sequence in which every possible subsequence of a given length appears exactly once. pwntools'
cyclic(n)generates such a pattern of length n using 4-byte subsequences. When this pattern overflows a buffer and the program crashes, the value at the overwritten location (a register, a variable) is a unique substring of the pattern -cyclic_find(value)immediately returns the byte offset.The advantage over simple
AAAA...BBBB...CCCCpatterns is precision: the de Bruijn sequence guarantees that any 4-byte window is unique, so wherever the overflow lands is unambiguously identified. This eliminates trial-and-error in finding the exact offset. In GDB, after the crash, reading the value at the overwritten location and passing it tocyclic_find()gives the exact byte count needed for the padding. - Step 3Send the overflow payloadCraft a payload of padding plus the target value. Send it to the server to trigger the flag output.
python3 -c "from pwn import *; p=remote('<host>',PORT); p.sendline(b'A'*<offset>+p64(<target>)); p.interactive()"Learn more
pwntools is a Python library designed for binary exploit development.
p64(value)packs a 64-bit integer into 8 bytes in little-endian byte order (the native format for x86-64), which is how values are stored on the stack. Little-endian means the least significant byte comes first:p64(0xdeadbeef)produces\xef\xbe\xad\xde\x00\x00\x00\x00.The payload structure for a variable overwrite is:
[padding bytes] + [target value]. The padding fills the buffer up to (but not past) the target variable's location, and the appended bytes then overwrite the variable with the desired value. If the binary checksif (code == 0xdeadbeef) { print_flag(); }, writing that exact value at the right offset satisfies the condition.p.interactive()hands control of the connection to the terminal, allowing you to read output and send additional input. For challenges where the flag is printed immediately after the overflow, you can also usep.recvall()to capture all output automatically.
Flag
picoCTF{...}
Stack buffer overflows write past a local buffer into adjacent stack memory - variables allocated after the buffer are the first to be overwritten.