ASLR: why we shuffle memory before every run
Attackers used to know exactly where your code lived in memory. ASLR makes them guess — and guessing wrong tends to crash the process.
Why it exists
Imagine a castle where the king sleeps in the same bedroom every night. An assassin only has to learn that fact once — memorize “third door on the left” — and any night will do. Now imagine the staff secretly shuffle the rooms every evening. The vulnerability (the king has to sleep somewhere) is unchanged. But the attacker now has to figure out which room he’s in, every single time, and a wrong guess sets off an alarm. ASLR is exactly that shuffle, but for the addresses where code and data live in your computer’s memory. The bug — say, a program that lets attackers redirect execution to a chosen address — is still there. ASLR makes “a chosen address” a moving target.
For decades, exploiting a memory bug followed a depressingly reliable script. Find a buffer overflow. Overwrite the saved return address on the stack. Point it at a known function — say, system() in libc — sitting at a fixed address that was the same on every machine running the same binary on the same OS. Hit run. Shell.
The fixed-address part is the hidden assumption that made the whole pipeline work. If the attacker can write down, on paper, “system lives at 0x7ffff7a52390,” exploitation reduces to plumbing: get the target to jump there. The bug is the foothold; the predictable layout is what turns the foothold into code execution.
ASLR attacks that assumption directly. Instead of putting libc at the same virtual address every time, the kernel rolls dice at process start and slides it somewhere random. Same for the stack, the heap, and (with PIE) the main executable itself. The bug still exists. The hardcoded 0x7ffff7a52390 is now wrong. Jumping to it lands in the middle of nothing, and the process dies instead of spawning a shell.
That’s the entire pitch: turn “exploit-once, exploit-everywhere” into “you have to leak the layout first, every time.”
Why it matters now
ASLR is on by default in every mainstream OS — Linux, macOS, Windows, iOS, Android — and every mainstream browser, runtime, and JIT relies on it. It’s one of the load-bearing assumptions of the modern security model: bugs in C and C++ codebases are still common, and ASLR is much of the reason they don’t all turn into trivial RCEs.
It also matters because attackers adapted. The modern exploit isn’t “jump to a known address” — it’s a two-step dance: first leak a pointer to defeat ASLR, then use that leak to compute the real addresses of the gadgets you wanted. Almost every serious browser or kernel exploit chain in the last decade has an “info leak” stage near the top precisely because ASLR forces one.
The short answer
ASLR = virtual memory + a random base offset per region per process
At process start, the kernel picks random offsets and slides each major memory region — stack, heap, libraries, and (under PIE) the executable — to a fresh location. Code and pointers within a region still work because they’re relative; absolute addresses an attacker wrote down ahead of time don’t.
How it works
The trick rests entirely on virtual memory. Every process already sees its own private address space; the kernel and dynamic loader decide where each chunk of that space gets mapped. ASLR is a small change to that decision: instead of always mapping libc at the same base, pick a random base within some allowed range.
A rough sketch of what gets randomized on a typical Linux process:
- Stack — the kernel adds a random offset (some kilobytes) to the stack base when the process starts.
- mmap region / shared libraries —
ld.somaps libc, libpthread, etc. starting from a randomized base, so every library inside gets shifted as a block. - Heap (brk) — the heap break is offset randomly from the end of the executable’s data segment.
- Main executable — only randomized if it was compiled as a PIE. Older non-PIE binaries sit at a fixed address; this is why distros pushed PIE-by-default over the last ~decade.
Crucially, ASLR randomizes bases, not internals. The offset from libc’s base to system is fixed by the build of libc — once you know the base, every function’s address falls out by addition. That’s why a single leaked libc pointer is usually enough to defeat library ASLR for the rest of the exploit.
Where it leaks
The honest version of the ASLR story is that it’s a probabilistic defense with several well-known holes:
- Entropy. On 32-bit systems there just aren’t enough random bits — a few hundred thousand possible bases means an attacker can sometimes brute-force, especially against a forking server where each child inherits the parent’s layout. 64-bit fixes this for almost all practical purposes.
- Fork inheritance.
fork()copies the parent’s address space, so all children share the parent’s randomization. A crash-and-retry attack against a forking daemon (classic Apache-style prefork) effectively gets unlimited guesses against one layout. The standard mitigation is re-randomizing onexec, not onfork. - Info leaks. Any bug that reveals a pointer — a format-string bug, an out-of-bounds read, an uninitialized struct sent over the wire — collapses ASLR for whatever region that pointer belongs to. This is why “info leak primitive + write primitive” is the standard exploit recipe today.
- Side channels. There’s published research on defeating kernel ASLR via timing and microarchitectural side channels (the broad family that includes KASLR bypasses around the Meltdown era). I’m being deliberately vague on specifics because the details vary by CPU and have been partially mitigated; the shape is “the hardware accidentally tells you where the kernel is.”
So ASLR doesn’t prevent exploitation — it raises the cost. It forces a leak. And forcing a leak forces the attacker to chain two bugs instead of one, which empirically is a big deal.
What I’m not sure about
I don’t have a confidently sourced number for “how many bits of entropy does ASLR give on Linux x86_64 today” — I’ve seen figures in the high 20s of bits for the mmap region quoted in older write-ups, but the exact number depends on kernel version, architecture, and which region you’re asking about. If you need a precise figure, read the current kernel source rather than trusting a blog post (including this one).
I also don’t know the precise dates ASLR shipped in each major OS off the top of my head. The standard account is that PaX on Linux had it first, with Windows, macOS, and mainline Linux following over the mid-to-late 2000s — but I’d verify before quoting specifics.
Famous related terms
- PIE —
PIE = executable + position-independent code— without this, the main binary sits at a fixed address and ASLR only protects libraries. - KASLR —
KASLR ≈ ASLR for the kernel— randomizes where the kernel itself is mapped; bypassed in spirit by Meltdown-class side channels. - ROP —
ROP = gadgets + a crafted stack— the technique ASLR most directly defangs, because gadget addresses are what gets randomized. - DEP / W^X —
DEP ≈ no executing data pages— ASLR’s older sibling; together they’re why “just inject shellcode” stopped working.
Going deeper
- The PaX project’s design notes are the canonical primary source for the original ASLR mechanism.
- Shacham et al.’s 2004 paper On the Effectiveness of Address-Space Randomization is the classic analysis of what ASLR can and can’t do, including the brute-force argument against 32-bit ASLR.
- For the modern leak-then-exploit pattern, any recent browser exploit write-up (e.g. Project Zero blog posts) shows ASLR being defeated in practice rather than in theory.