💀 BrainStuffer
Memory Attacks
Stack overflow, heap corruption, UAF, TOCTOU, data races — interactive step-by-step

Stack Buffer Overflow

Write past the end of a stack-allocated buffer to overwrite the return address (RIP/EIP) and redirect execution to attacker-controlled code. The classic vulnerability — enabled by unsafe string functions like strcpy that perform no bounds checking.

#include <string.h> #include <stdio.h> void vulnerable(char *input) { char buf[8]; strcpy(buf, input); // no bounds check! printf("Hello, %s\n", buf); } int main() { // Safe call vulnerable("Alice"); // Attack: 24 bytes overwrites RIP vulnerable("AAAAAAAA" "BBBBBBBB" "\x41\x13\x40"); }
Step 1 / 7
Loading...
in-bounds write overflow corrupted hijacked freed reallocated
0x7fff_ffc8Return Addr (RIP)RIP0x0040_1234
0x7fff_ffc0Saved RBPRBP0x7fff_ffd0
0x7fff_ffbfbuf[7]0x00
0x7fff_ffbebuf[6]0x00
0x7fff_ffbdbuf[5]0x00
0x7fff_ffbcbuf[4]0x00
0x7fff_ffbbbuf[3]0x00
0x7fff_ffbabuf[2]0x00
0x7fff_ffb9buf[1]0x00
0x7fff_ffb8buf[0] RSP0x00

Heap Buffer Overflow

Overflowing a heap-allocated buffer corrupts the malloc chunk metadata of adjacent allocations. By crafting fake size/prev_size fields, an attacker can manipulate the allocator during a subsequent free() into performing an arbitrary write to any memory address.

#include <stdlib.h> #include <string.h> int main() { char *a = malloc(32); // chunk A char *b = malloc(32); // chunk B // Overflow: 48 bytes into 32-byte chunk memcpy(a, attacker_data, 32); // safe memcpy(a, attacker_data, 48); // overflow! free(b); // uses corrupted metadata malloc(8); // arbitrary write primitive }
Step 1 / 6
Loading...
in-bounds write overflow corrupted hijacked
0x601000A: prev_sizemeta0x0000_0000_0000_0000
0x601008A: size+flagsmeta0x0000_0000_0000_0031
0x601010A: data[0..7]0x00000000_00000000
0x601018A: data[8..15]0x00000000_00000000
0x601020A: data[16..23]0x00000000_00000000
0x601028A: data[24..31]0x00000000_00000000
— Chunk B starts here —
0x601030B: prev_sizemeta0x0000_0000_0000_0030
0x601038B: size+flagsmeta0x0000_0000_0000_0031
0x601040B: data[0..7]0x00000000_00000000
0x601048B: data[8..15]0x00000000_00000000
0x601050B: data[16..23]0x00000000_00000000
0x601058B: data[24..31]0x00000000_00000000

Use-After-Free (UAF)

Free a heap object, then let the allocator give the same memory to a different allocation. If the original pointer is used again, it now reads or writes the new owner's data. When the freed object contained a function pointer, UAF achieves control-flow hijacking.

typedef struct { void (*callback)(void); int value; } Widget; Widget *w = malloc(sizeof(Widget)); w->callback = &safe_function; w->value = 42; free(w); // freed -- w still = 0x601000! // ... later ... char *buf = malloc(sizeof(Widget)); memcpy(buf, attacker_payload, sizeof(Widget)); w->callback(); // UAF: calls attacker ptr!
Step 1 / 7
Loading...
allocated / in use freed (dangling) reallocated attacker data hijacked
w = 0x601000 (DANGLING POINTER)
0x601000Widget: callbackfn ptr0x0040_1234
0x601008Widget: value0x0000_002A
0x601010[padding]0x00000000

TOCTOU — Time of Check to Time of Use

A race condition between a security check and the operation it guards. If an attacker can change the resource between the check and the use, the check becomes meaningless. Classic example: a setuid program calls access() then open() — with a symlink swap in between.

// Vulnerable: check-then-use if (access("/tmp/file", R_OK) == 0) { // RACE WINDOW here int fd = open("/tmp/file", O_RDONLY); read(fd, buffer, sizeof(buffer)); }
Step 1 / 6
Loading...
Victim Process
T
Attacker Process
access("/tmp/file", R_OK) ✓
T1
— check passes, file readable
T2
T3
RACE
unlink("/tmp/file")
T4
RACE
symlink("/etc/shadow", "/tmp/file")
open("/tmp/file") → /etc/shadow!
T5
read() → password hashes
T6

Data Race

Two threads concurrently read-modify-write a shared variable without synchronisation. Because counter++ is not atomic (it compiles to READ + ADD + WRITE), threads can interleave and produce incorrect results — from lost updates to corrupted security state.

// Vulnerable: unsynchronised counter int counter = 5; // Thread 1 and Thread 2 both run: counter++; // compiles to 3 instructions! // MOV RAX, [counter] ; read // ADD RAX, 1 ; increment // MOV [counter], RAX ; write back // Fix 1: mutex pthread_mutex_lock(&lock); counter++; pthread_mutex_unlock(&lock); // Fix 2: C11 atomic atomic_fetch_add(&counter, 1);
Step 1 / 7
Loading...
Shared Memory — counter
5
Expected after both threads: 7
Thread 1
RAX
instructionMOV RAX, [counter]
Thread 2
RAX
instructionMOV RAX, [counter]

Knowledge Check — 15 Questions

Test your understanding of stack overflows, heap corruption, use-after-free, TOCTOU races, and data races.

Question 1 of 15
0/15
Loading...