Every defence in this lesson exists because of one uncomfortable fact: the
This is the story of an arms race. For each class of bug, defenders invented a mitigation; for each mitigation, attackers found a bypass, which provoked the next mitigation. Nobody has "won" — modern exploits chain several bypasses together — but each round raised the cost of an attack from an afternoon's work to a research project. Understanding the sequence is understanding how a hardened system is actually built, layer on grudging layer.
The archetypal memory-safety bug is the stack buffer overflow. A function allocates a
fixed-size array on its stack frame and then copies attacker-controlled data into it without checking the
length (
When the function executes
Its close cousin, the use-after-free (UAF), is today's dominant bug class in kernels and
browsers alike. A chunk of memory is
The first cheap defence: place a secret random value — a canary, after the coal-mine
bird — between the local buffers and the saved return address when the function is entered. A contiguous
overflow that reaches the return address must pass through the canary and corrupt it. The
function's epilogue re-reads the canary before
GCC/Clang emit this automatically under
The 1990s exploit ran shellcode inside the stack buffer. So why is the stack executable at all? It need not be. W^X ("write XOR execute", also called NX / DEP) is the page-table policy that no page is ever both writable and executable at once: code pages are read-execute, data pages (stack, heap) are read-write-no-execute. The MMU's NX bit enforces it in hardware. Injected shellcode on the stack now faults the instant the CPU tries to execute it.
The bypass was ingenious and is now the standard technique:
return-oriented programming (ROP). If you cannot inject new code, reuse the
code already present. The attacker's overflow writes not one return address but a whole chain of
them, each pointing at a short "gadget" — a handful of existing instructions ending in
ROP needs to know the addresses of its gadgets. Address-space layout randomisation (ASLR) attacks that assumption: at every execution the loader places the stack, heap, libraries, and (with PIE) the executable itself at random offsets. The attacker's pre-computed gadget addresses now point at garbage. KASLR is the same idea for the kernel — the kernel image and its modules load at a randomised base, so a userspace attacker who finds a kernel bug still does not know where the kernel is.
Randomisation's strength is measured in bits of entropy: if the base can land in one of
The bypass: entropy only helps if the layout stays secret. Any
information-leak bug that discloses a single real pointer defeats the whole scheme — subtract the
known offset and the entire map is recovered. Low-entropy KASLR was also brute-forceable, and 32-bit ASLR
(only
A whole family of kernel exploits used a shortcut: trick the kernel, while running in ring 0, into jumping to or reading from a page the attacker controls in user space (a "ret2usr" attack). The user page is trivially populated with a fake structure or shellcode, and the kernel — same address space, full privilege — happily uses it. Two CPU features slam this shut:
Together SMEP+SMAP force kernel exploits to keep everything — code and data — inside kernel memory, which is far harder to control and, thanks to KASLR, harder to locate. The arms race responded: exploits pivoted to pure kernel-space ROP/JOP and to data-only attacks that never redirect control at all.
ROP and its jump-oriented cousin (JOP) both violate the program's intended control-flow graph:
they return to the middle of functions and call through pointers the compiler never sanctioned.
Control-Flow Integrity (CFI) enforces the graph directly. Forward-edge CFI
checks that every indirect call lands on a valid function entry of the right type; backward-edge
protection (Intel CET's hardware shadow stack, or ARM's PAC
pointer authentication) guarantees a
Rounding out the toolbox: stack-clash protection (a probe on every large stack growth so
an allocation cannot silently leap the guard page into the heap, after the 2017 Stack Clash disclosures);
heap hardening (safe-unlinking, randomised allocators, quarantines against UAF); and
Two of these are pure arithmetic you can feel. First a canary check: a "clean" write leaves the canary intact and the function returns safely; an overflowing write clobbers it and the epilogue aborts. Second, ASLR entropy: how many guesses does randomisation buy? Run it and change the numbers.
A stack overflow is loud and increasingly well-defended — canaries, shadow stacks and CFI all cluster around the return address. A use-after-free is quieter and far more flexible. The trick is heap grooming: the attacker frees an object, then allocates a different object of the same size class so the allocator reuses the exact freed slot, and now a dangling pointer of the old type overlays a fresh object of the new type. By choosing the types carefully — say, overlaying a structure that contains a length field or a function pointer — a mere "used a stale pointer" bug becomes a precise read/write primitive, and from there, full control. This is why modern kernel and browser exploits are overwhelmingly UAF-driven, why allocators now add type-segregated heaps and delayed reuse (quarantines), and why Google and Microsoft both report that ~70% of their severe CVEs are memory-safety bugs — the number that finally tipped the industry toward Rust.
It is tempting to treat ASLR (or KASLR) as if it hides the kernel the way a password hides an account — as though the attacker must somehow "crack" it. It does no such thing. ASLR merely adds a single secret offset to an otherwise fixed layout. Every symbol keeps its usual distance from the base; only the base is random. So the moment any bug discloses one true runtime address — a leaked stack pointer, an uninitialised-memory read, a speculative side channel — the attacker subtracts the known static offset and recovers the base, and with it the address of everything. That is why real exploit chains are almost always "info-leak then control-flow hijack", and why defending against information disclosure matters as much as defending against the corruption itself. Entropy buys you nothing once the secret has leaked even once.