Skip to content

45. Buffer Overflow

Occurs when a program writes more data into a buffer than it can hold, overwriting adjacent memory.

Stack layout (why it's dangerous): local buffers sit below the saved base pointer and return address on the stack. Overflowing a local buffer with enough data overwrites the return address itself.

c
void vulnerable() {
    char buf[8];
    gets(buf);   // reads unlimited input — never use this
}

A 20-byte malicious input can overwrite the return address with an attacker-chosen value (e.g., pointing at injected shellcode) — when the function returns, the CPU jumps there instead.

Even without full shellcode, overflowing past a buffer can flip an adjacent flag variable (e.g., an authenticated int placed after password[16] on the stack), granting access without the correct password.

Defenses:

  1. Stack canary — a random value placed between the buffer and the return address; checked before returning, aborting if it changed (gcc -fstack-protector).
  2. ASLR — randomizes stack/heap/library/executable base addresses so an attacker can't hardcode a jump target.
  3. NX bit / DEP — marks the stack and heap non-executable; injected shellcode simply can't run.
  4. Safe functionsfgets/strncpy/snprintf with explicit bounds instead of gets/strcpy/sprintf.
  5. Bounds-checked languages — Rust, Java (throws on out-of-bounds), Python (no manual memory management) versus C/C++ (no bounds checking at all).

Attack evolution (bypassing defenses): NX blocks injected shellcode → attackers use return-to-libc (jump to existing code like system(), passing /bin/sh). ASLR randomizes addresses → attackers use ROP (Return-Oriented Programming), chaining small existing code snippets ("gadgets") already present in the binary.