CS 161, Lesson 12, in 50 slides and code mode. It explains why mitigations exist at all when you are stuck writing C, then covers non-executable pages - NX, W^X, and DEP - which stop injected shellcode; stack canaries, which detect a contiguous overwrite of the saved rip; and ASLR, which randomizes absolute addresses but not relative offsets. It gives the consolidated table mapping each mitigation to its attack and its bypass, and explains why combining all three is synergistic. This is an overview only, since the deep bypasses come in Week 5. The examples are toys running in a sandbox, and it is anchored to textbook sections 4.4, 4.5, 4.8, and 4.11.
Subject: Computer Security · 86 slides · code lesson
Open the interactive version of this deck · Homework for this lesson
Title
CS 161 · Lesson 12 of 45
exploit mitigations · NX / W^X · stack canaries · ASLR · defense-in-depth · the Week 5 preview
Objectives
Warm-up
Discussion prompt
Before we open L12 · Exploit Mitigations Overview: NX, Stack Canaries, ASLR: without looking back, what was the main idea of L11 · Memory-Safe Languages, Safer C, and the Secure Software Process, and what could you do by the end of it that you could not do before?
Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.
Answer:
CS 161, Lesson 11, in 50 slides and code mode. It lays out the spectrum of memory-safety fixes: memory-safe languages, which are the only 100% guarantee; Rust's ownership and borrow model, which achieves that without a garbage collector; safer C through pre- and post-conditions and bounds-checked library calls, replacing gets with fgets, strcpy with strncpy or strlcpy, and sprintf with snprintf; and the secure-development process, covering controlled crashes, code review, fuzzing, Valgrind, sanitizers, static analyzers, and the SDL. It is anchored to textbook sections 4.1 to 4.3.
Concept
Every example here is a toy in a sandbox, for authorized security education — exactly how the textbook teaches it. We study the mitigations so we can deploy and reason about them.
Shellcode stays abstract as [shellcode]; we never write real attack bytes. The skill being built is knowing which defense stops which step of the L8 attack.
Counterexample
Discussion prompt
Every example here is a toy in a sandbox, for authorized security education — exactly how the textbook teaches it. We study the mitigations so we can deploy and reason about them.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Answer:
Shellcode stays abstract as [shellcode]; we never write real attack bytes. The skill being built is knowing which defense stops which step of the L8 attack.
Section
Part 1 · §4.4
Concept
The clean fix from L8 was: use a memory-safe language. But in the real world you often can't — you inherit a huge legacy C codebase (a kernel, a daemon, firmware) that you cannot fully audit or rewrite this quarter.
mitigation — A code-hardening defense that makes a known class of exploit HARDER to pull off — typically by making a failed attempt CRASH instead of succeed.
Mitigations assume the bug is still there and try to stop the exploitation anyway.
Analogy
Discussion prompt
Explain Sometimes you're stuck in C by analogy to something with no Computer Security in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
Mitigations assume the bug is still there and try to stop the exploitation anyway.
Concept
A mitigation raises the bar; it does not remove the vulnerability. The book is blunt about the limit.
the thesis — The only way to prevent all memory safety exploits is to use a memory-safe language.
| Mitigations DO | Mitigations DON'T |
|---|---|
| make common exploits harder | remove the underlying bug |
| turn many exploits into crashes | make C memory-safe |
| buy defense-in-depth | stop a determined, well-resourced attacker forever |
Comparison
Comparison matrix
From Mitigations are not a cure: refill the Mitigations DON'T column from what you know. The rest of the table is as it appeared.
| Mitigations DO | Mitigations DON'T |
|---|---|
| make common exploits harder | remove the underlying bug |
| turn many exploits into crashes | make C memory-safe |
| buy defense-in-depth | stop a determined, well-resourced attacker forever |
Intuition
No single mitigation is foolproof, so we stack them — like a castle with a moat AND walls AND guards. If one is bypassed, the next still stands. That layering is defense-in-depth.
Each mitigation provokes a new bypass, which provokes a new mitigation: an ongoing arms race. We're previewing one round of it; Week 5 is the rematch.
Explain it
Discussion prompt
Explain Defense-in-depth and the arms race to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
No single mitigation is foolproof, so we stack them — like a castle with a moat AND walls AND guards. If one is bypassed, the next still stands. That layering is defense-in-depth.
Intuition
Here's the key payoff. Each mitigation forces the attacker to solve a different problem. NX forces them to reuse existing code. A canary forces them to know a secret value. ASLR forces them to know secret addresses.
Combined, the attacker must defeat all of them at once — usually meaning they must find multiple, distinct vulnerabilities (e.g. one to leak memory, one to write). That multiplication of difficulty is what we mean by synergistic.
Ranking
Put in order
Put the moves of Recall: the L8 attack chain we're defending against into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. The shellcode sits in buf, a region the program writes to.
Worked example
Every mitigation today targets one link of the classic stack smash from L8. Keep this chain on screen.
// classic stack smash (L8), char buf[8]:
payload = [shellcode (8B)] // 1. attacker-supplied code
+ [4 garbage bytes] // 2. spacer over the sfp
+ [address of buf] // 3. overwrite rip (contiguous!)
// on return: eip = &buf -> CPU EXECUTES the buffer bytesLink 1 — the code is in a writable buffer
Why: The shellcode sits in buf, a region the program writes to. NX will attack this assumption.
Link 2 — the overwrite is one contiguous run
Why: Bytes climb from buf, across the sfp, into the rip with no gaps. The stack canary will attack this.
Link 3 — the attacker hardcodes &buf
Why: The payload contains a fixed absolute address. ASLR will attack this.
| Attack link | Assumption | Mitigation that breaks it |
|---|---|---|
| execute injected code | writable memory is executable | NX / W^X |
| contiguous rip overwrite | no guard between buf and rip | stack canary |
| hardcoded &buf | addresses are predictable | ASLR |
Trade off
Comparison matrix
From Recall: the L8 attack chain we're defending against: every row here is a choice with a cost. Fill the Assumption column, then say which row you would actually pick and what you give up for it.
| Attack link | Assumption | Mitigation that breaks it |
|---|---|---|
| execute injected code | writable memory is executable | NX / W^X |
| contiguous rip overwrite | no guard between buf and rip | stack canary |
| hardcoded &buf | addresses are predictable | ASLR |
Anomaly
Predict first
A student writes this, and it looks reasonable:
We turned on NX, canaries, and ASLR. Is the program now memory-safe?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Tempting — but the buffer overflow bug is still in the code.
We turned on NX, canaries, and ASLR. Is the program now memory-safe?
Why: Tempting — but the buffer overflow bug is still in the code. Mitigations only make this particular exploit harder; a strong-enough attacker (or a new bug) gets through.
Trap
We turned on NX, canaries, and ASLR. Is the program now memory-safe?
Conclude the C program is now memory-safe
Why: Tempting — but the buffer overflow bug is still in the code. Mitigations only make this particular exploit harder; a strong-enough attacker (or a new bug) gets through.
We turned on NX, canaries, and ASLR. Is the program now memory-safe?
It is only HARDER to exploit, not safe
Why: §4.4: the only way to prevent ALL memory-safety exploits is a memory-safe language. Mitigations are defense-in-depth — they raise the bar, they don't erase the vulnerability.
Section
Part 2 · §4.5
Intuition
The injected-shellcode attack relies on one quiet assumption: the bytes you wrote into a buffer can later be run as instructions. NX simply forbids that combination.
Think of every memory page as a room with a sign: it's either a writing room (you can change what's inside) or a performance stage (the CPU can execute it) — never both at once.
Concept
non-executable pages (NX) — Each memory page is set writable XOR executable, never both. If a page is writable, the CPU refuses to execute its bytes as instructions; if executable, it can't be written.
Another way to say the same rule: don't allow eip to ever hold the address of a non-executable page. The instant ret would jump into a writable page, the CPU faults.
| Name | Stands for | Where you'll see it |
|---|---|---|
| W^X | Write XOR Execute | OpenBSD, conceptual name |
| DEP | Data Execution Prevention | Windows |
| NX bit | No-eXecute page-table bit | x86 hardware / Linux |
Definition probe
Sort into buckets
Every line below is part of the definition of mitigation or of non-executable pages (NX) — one or the other, never both. Put each where it belongs.
Intuition
The permission lives on the page, not on individual bytes. Memory is carved into pages (typically 4096 bytes), and each page carries permission bits the hardware enforces: read, write, execute.
W^X is just a policy on those bits: for every page, the execute bit and the write bit are never both set. The stack and heap get write-but-not-execute; the code section gets execute-but-not-write.
Step zero
Discussion prompt
Why the L8 shellcode injection now faults — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: The overflow still succeeds — rip is overwritten
Answer:
Worked example
Re-run the L8 'inject into buf' attack (Strategy 2) on a system with NX enabled, and watch exactly where it dies.
payload = [shellcode (8B)] // buf[0..7] -- on a WRITABLE stack page
+ [4 garbage bytes] // sfp
+ [address of buf] // rip = &buf
// ret: eip <- &buf --> &buf is on a writable (==non-executable) pageThe overflow still succeeds — rip is overwritten
Why: NX does nothing to stop the write. The buffer overflows and the rip becomes &buf exactly as before.
ret loads &buf into eip
Why: The CPU is now about to fetch its next instruction from the stack page that holds buf.
The CPU faults instead of executing
Why: buf lives on a writable stack page, so under W^X that page is non-executable. Fetching an instruction from it raises a fault and the program CRASHES — the exploit fails.
| Step | Without NX | With NX |
|---|---|---|
| overflow buf, overwrite rip | succeeds | succeeds |
| ret sets eip = &buf | succeeds | succeeds |
| execute bytes at buf | runs shellcode | FAULT — crash |
Error analysis
Annotate
Walk the callouts on Why the L8 shellcode injection now faults. Each one is a place this is easy to get subtly wrong.
Concept
NX stops you from running code you placed in a writable buffer. It does nothing about code that is already loaded and already executable — the program's own functions, and the C library.
So the attacker stops injecting and starts reusing: point the rip at system() in libc (return-to-libc, L13) or chain together existing instruction fragments (ROP, L14). The deep mechanics are Week 5 — for now, just know NX is defeated by code reuse.
Estimation
Predict first
Under W^X, every region a process maps is either writable or executable. Walk the typical layout and decide which exploit step each region blocks.
Commit before you compute: what does Reading a process's page permissions come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: Code and libc are r-x (not writable)
Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. These are executable, so the rip CAN usefully point here — which is exactly the door code-reuse attacks walk through.
Worked example
Under W^X, every region a process maps is either writable or executable. Walk the typical layout and decide which exploit step each region blocks.
// simplified /proc/<pid>/maps under W^X (perms column):
// code (.text) r-x executable, NOT writable
// data/.bss rw- writable, NOT executable
// heap rw- writable, NOT executable
// stack rw- writable, NOT executable
// libc .text r-x executable, NOT writableStack and heap are rw- (not executable)
Why: Any shellcode the attacker writes into a buffer lands here, so the CPU faults the moment eip points at it — injection is dead.
Code and libc are r-x (not writable)
Why: These are executable, so the rip CAN usefully point here — which is exactly the door code-reuse attacks walk through.
| Region | Perms | Inject here? | Reuse here? |
|---|---|---|---|
| stack / heap | rw- | blocked (NX faults) | n/a — not executable |
| program .text | r-x | blocked (not writable) | yes — ret2existing-code |
| libc .text | r-x | blocked (not writable) | yes — ret2libc |
Reverse engineer
Discussion prompt
Work backwards. The example finished here:
Code and libc are r-x (not writable)
What was it asked to do, and what must it have been given? Reconstruct the problem from its answer.
Hint: Every quantity in the result had to enter somewhere. Account for each one.
Answer:
Under W^X, every region a process maps is either writable or executable. Walk the typical layout and decide which exploit step each region blocks.
Anomaly
Predict first
A student writes this, and it looks reasonable:
With NX on, can the attacker still hijack control flow?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: NX only blocks executing injected bytes on writable pages.
With NX on, can the attacker still hijack control flow?
Why: NX only blocks executing injected bytes on writable pages. The attacker can still overwrite the rip and jump — they just have to aim at code that's already executable.
Trap
With NX on, can the attacker still hijack control flow?
Conclude NX makes control-flow hijacking impossible
Why: NX only blocks executing injected bytes on writable pages. The attacker can still overwrite the rip and jump — they just have to aim at code that's already executable.
With NX on, can the attacker still hijack control flow?
NX stops INJECTION; code reuse defeats it
Why: §4.5: ret2libc / ROP redirect to existing executable code (libc, the binary). The rip overwrite still works — NX only changes where the rip can usefully point.
Section
Part 3 · §4.8
Intuition
Miners once carried a canary underground: the bird is more sensitive to toxic gas, so if it died, the miners knew to flee before the danger reached them. The bird is sacrificial — an early warning.
A stack canary is the same idea in software: a sacrificial value placed in the line of fire. If a contiguous overflow corrupts it, we know the rip is about to be corrupted too — so we bail out before returning.
Concept
stack canary — A known random value the compiler places on the stack on function entry and re-checks just before return; if it changed, the program crashes BEFORE the ret runs.
Placement is the whole trick: the canary goes directly above the local variables and directly below the saved registers (the sfp and rip).
Because a contiguous overflow from a local buffer up to the rip must cross the canary first, overwriting the rip necessarily corrupts the canary — and that corruption is detected on return.
Picture it
Figure (svg): Stack frame from high to low: rip at ebp+4, sfp at ebp+0, a 4-byte canary directly below the sfp, then the 8-byte buf below that. A contiguous overflow climbing from buf to the rip must overwrite the canary on the way.
Discussion prompt
Read the picture before the words. What is this showing, and what is the one thing it is built to make obvious? Commit to an answer, then read on.
Hint: Name the parts, then say what changes between them — and if nothing changes, say what is being held still.
Answer:
Same vulnerable() frame from L8, now with the canary wedged between buf and the saved registers. From high to low addresses: rip, sfp, canary, then buf.
Concept
Same vulnerable() frame from L8, now with the canary wedged between buf and the saved registers. From high to low addresses: rip, sfp, canary, then buf.
Figure (svg): Stack frame from high to low: rip at ebp+4, sfp at ebp+0, a 4-byte canary directly below the sfp, then the 8-byte buf below that. A contiguous overflow climbing from buf to the rip must overwrite the canary on the way.
| Slot (high→low) | Size | Role |
|---|---|---|
| rip (saved return addr) | 4 B | the attacker's prize |
| sfp (saved ebp) | 4 B | saved register |
| canary | 4 B | guard — checked on return |
| buf[0..7] | 8 B | attacker-fillable local |
Discrimination
Sort into buckets
Sort these by Size, from memory, without looking back at The frame, with a canary inserted. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.
Concept
The book pins down five facts you must remember about the canary value itself.
strcpy stops at the null.Ranking
Put in order
Put the moves of Tracing the canary catching an overflow into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. buf[0..7] = the eight 'A's — in bounds, the canary is untouched so far.
Worked example
Run the L8 16-byte overflow into the canary-protected frame and watch the check fire.
input = "AAAAAAAA" "BBBB" "CCCC" "DDDD" // 8 + 4 + 4 + 4 = 20 bytes
// frame (low->high): buf[8] | canary[4] | sfp[4] | rip[4]
// on return the compiler runs: if (canary != saved_canary) abort();First 8 bytes fill buf
Why: buf[0..7] = the eight 'A's — in bounds, the canary is untouched so far.
Next 4 bytes overwrite the canary
Why: To keep climbing toward the rip, the 'B's must land on the canary. Its value is now 0x42424242 — no longer the random secret.
Bytes 13–20 reach sfp and rip
Why: The 'C's hit the sfp and the 'D's hit the rip — exactly the hijack from L8.
On return, the canary check FAILS first
Why: Before ret runs, the compiler compares the on-stack canary (0x42424242) to the saved secret. They differ, so the program aborts — the corrupted rip is never used.
| Input bytes | Lands on | Canary intact? |
|---|---|---|
| AAAAAAAA (1–8) | buf[0..7] | yes |
| BBBB (9–12) | canary | NO — corrupted |
| CCCC (13–16) | sfp | — |
| DDDD (17–20) | rip | — (but never reached: abort) |
Error analysis
Annotate
Walk the callouts on Tracing the canary catching an overflow. Each one is a place this is easy to get subtly wrong.
Intuition
It looks like a waste to spend 8 of 32 random bits on a fixed 0x00. But that null is a second line of defense aimed squarely at string-copy overflows.
strcpy and friends stop at the first null byte. If the attacker tries to overflow through the canary with a string, their copy halts the instant it must emit the canary's null — they can't write past it to reach the rip at all. The null trades a little entropy for blocking a whole copy primitive.
Concept
The canary defeats a contiguous overflow of the rip. Three ideas get around it (the deep versions are L15).
%n writes straight to the rip, leaving the canary untouched.Estimation
Predict first
How hard is a blind guess of the canary on a 32-bit system? Account for the NULL byte the compiler bakes in.
Commit before you compute: what does Counting the canary's entropy come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: Blind guess space is 2^24
Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. About 16.7 million possibilities — large, but the null byte cost the defender a factor of 256 versus a full 2^32 secret.
Worked example
How hard is a blind guess of the canary on a 32-bit system? Account for the NULL byte the compiler bakes in.
// 32-bit canary = 4 bytes = 32 bits total
// 1 byte (often the first) is fixed 0x00
// remaining 3 bytes = 24 bits are random per run
guess_space = 2 ** 24 // ~16.7 million
// vs a full 32-bit secret: 2 ** 32 (~4.3 billion)Start from 32 bits, subtract the null byte
Why: The NULL byte is known to the attacker, so it contributes 0 bits of secrecy: 32 - 8 = 24 random bits remain.
Blind guess space is 2^24
Why: About 16.7 million possibilities — large, but the null byte cost the defender a factor of 256 versus a full 2^32 secret.
| Canary detail | Bits | Guess space |
|---|---|---|
| full word | 32 | 2^32 ≈ 4.3 billion |
| minus fixed NULL byte | −8 | — |
| effective entropy | 24 | 2^24 ≈ 16.7 million |
Comparison
Comparison matrix
From Counting the canary's entropy: refill the Guess space column from what you know. The rest of the table is as it appeared.
| Canary detail | Bits | Guess space |
|---|---|---|
| full word | 32 | 2^32 ≈ 4.3 billion |
| minus fixed NULL byte | −8 | — |
| effective entropy | 24 | 2^24 ≈ 16.7 million |
Trap
Does a stack canary protect a heap buffer overflow?
Assume the canary guards heap allocations too
Why: The name says 'stack' for a reason. There is no canary sitting next to your malloc'd buffer — a heap overflow corrupts heap metadata or adjacent objects with no canary in the path.
Does a stack canary protect a heap buffer overflow?
Stack only — the heap has no canary
Why: §4.8: the compiler inserts the canary into the stack frame, between locals and the saved registers. Heap overflows are a separate problem with separate (allocator-level) defenses.
Anomaly
Predict first
A student writes this, and it looks reasonable:
A %n format-string bug lets the attacker write to the rip directly. Does the canary catch it?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: The canary only changes if it is overwritten.
A %n format-string bug lets the attacker write to the rip directly. Does the canary catch it?
Why: The canary only changes if it is overwritten. But %n writes to an arbitrary address — straight to the rip — without touching anything in between.
Trap
A %n format-string bug lets the attacker write to the rip directly. Does the canary catch it?
Assume the canary detects the rip overwrite
Why: The canary only changes if it is overwritten. But %n writes to an arbitrary address — straight to the rip — without touching anything in between.
A %n format-string bug lets the attacker write to the rip directly. Does the canary catch it?
No — the write goes AROUND the canary
Why: §4.8: the canary only detects a contiguous overflow that crosses it. A non-contiguous %n write hits the rip and leaves the canary unchanged, so the check passes and the exploit succeeds.
Break the constraint
Discussion prompt
The rule this trap just fixed:
A %n format-string bug lets the attacker write to the rip directly.
Now break it on purpose. Build a case that violates it and follow the consequences until something visibly fails. Where does the failure first show up — and would you have noticed it if you had not been looking?
Hint: The dangerous rules are the ones whose violation still produces an answer. If yours fails loudly, try to find one that fails quietly.
Answer:
The canary only changes if it is overwritten. But %n writes to an arbitrary address — straight to the rip — without touching anything in between.
Section
Part 4 · §4.11
Intuition
The L8 payload contained a hardcoded address — &buf, or the address of system(). That only works because the attacker knew, in advance, exactly where things would be.
ASLR is like rearranging the furniture in a dark house every single night. A burglar who memorized last night's layout now trips over everything: the addresses they wrote into the payload point at the wrong place.
Concept
ASLR — Address Space Layout Randomization: on each run, randomize the base address of each memory section (and each loaded library).
Because each section starts at a different base every run, the absolute addresses of variables, the sfp, the rip, and the code all differ every run. The attacker can no longer hardcode an address — they must guess.
Intuition
Without ASLR, &buf is the same number every run — the attacker writes it once and it works forever. ASLR turns that one fixed answer into a different answer each run.
Now the hardcoded address in the payload is a stale guess. It was right for some past run and is almost certainly wrong for this one — so ret jumps to garbage and the program crashes instead of running the attacker's code.
Explain it
Discussion prompt
Explain Why guessing an address is now a lottery to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
Without ASLR, &buf is the same number every run — the attacker writes it once and it works forever. ASLR turns that one fixed answer into a different answer each run.
Concept
Randomization isn't unlimited. Sections must start on a page boundary — a multiple of the page size, typically 4096 bytes on a 32-bit system. The low bits of the base are therefore always zero.
\[ \log_2 4096 = 12 \text{ fixed low bits per address} \]
| Quantity | 32-bit value |
|---|---|
| page size | ≈ 4096 bytes |
| fixed low bits (page offset) | 12 |
| typical usable entropy | ≈ 16 bits → 1 in 2^16 guess |
Concept
ASLR randomizes the base of each section, which shifts every absolute address inside it by the same amount. But it does not change the relative offsets between things in the same section.
The rip is still 4 bytes above the sfp, every run. The layout within a frame is fixed; only the whole block slides.
Consequence: if the attacker can leak one absolute address (say, the sfp), they can add the known fixed offset and deduce the others (the rip, locals, etc.). One leak unlocks the section.
Analogy
Discussion prompt
Explain The key subtlety: absolute, not relative by analogy to something with no Computer Security in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
Consequence: if the attacker can leak one absolute address (say, the sfp), they can add the known fixed offset and deduce the others (the rip, locals, etc.). One leak unlocks the section.
Step zero
Discussion prompt
One leak defeats the randomization — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: Leak one absolute address
Answer:
Worked example
Suppose a bug leaks the runtime value of the sfp. Because relative offsets are fixed, the attacker recovers the rip's address by arithmetic — no guessing.
// relative layout is FIXED every run:
// addr(rip) = addr(sfp) + 4
// run A: sfp leaked = 0xbf8e1240 -> rip = 0xbf8e1244
// run B: sfp leaked = 0xbfa07c10 -> rip = 0xbfa07c14
// bases differ, but the +4 offset never doesLeak one absolute address
Why: A separate bug (e.g. an info-leak or format-string read) discloses the runtime address of the sfp for THIS run.
Add the fixed relative offset
Why: ASLR didn't touch offsets, so addr(rip) = addr(sfp) + 4 still holds. The attacker computes the rip's address for this run.
Now the hardcoded-address attack works again
Why: With a correct per-run address in hand, the payload from L8 targets the right location. ASLR is defeated by ONE leak — no 2^16 guessing needed.
| Run | Leaked sfp | Derived rip (sfp+4) |
|---|---|---|
| A | 0xbf8e1240 | 0xbf8e1244 |
| B | 0xbfa07c10 | 0xbfa07c14 |
| C | 0xbf02de80 | 0xbf02de84 |
Trade off
Comparison matrix
From One leak defeats the randomization: every row here is a choice with a cost. Fill the Leaked sfp column, then say which row you would actually pick and what you give up for it.
| Run | Leaked sfp | Derived rip (sfp+4) |
|---|---|---|
| A | 0xbf8e1240 | 0xbf8e1244 |
| B | 0xbfa07c10 | 0xbfa07c14 |
| C | 0xbf02de80 | 0xbf02de84 |
Concept
ASLR can randomize the stack, the heap, and shared libraries freely. But the program's own code can only be moved if it was compiled to be relocatable.
⊕ PIE (Position-Independent Executable) — A build flag that makes the executable's own code position-independent, so ASLR can randomize the binary's base address too — not just libraries and the stack.
Without PIE, the executable loads at a fixed address and the attacker can hardcode its functions even with ASLR on. (Flagged ⊕ — a build-time companion to ASLR.)
Matching
Match the pairs
Match each term to the definition this lesson gave it — not the one you would guess from the word.
Why: These are the working definitions of mitigation, the thesis, stack canary, ASLR, ⊕ PIE (Position-Independent Executable) as L12 · Exploit Mitigations Overview: NX, Stack Canaries, ASLR uses them. Pairing them correctly is the test of whether you could state each one with the slide switched off.
Concept
Two ideas get past ASLR; both are L15.
Anomaly
Predict first
A student writes this, and it looks reasonable:
With ASLR on, is the rip still a fixed distance from the sfp?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: If offsets were randomized, leaking the sfp would tell you nothing about the rip.
With ASLR on, is the rip still a fixed distance from the sfp?
Why: If offsets were randomized, leaking the sfp would tell you nothing about the rip. But that's not how ASLR works — it slides whole sections, it doesn't reshuffle their insides.
Trap
With ASLR on, is the rip still a fixed distance from the sfp?
Assume ASLR scrambles the within-frame layout too
Why: If offsets were randomized, leaking the sfp would tell you nothing about the rip. But that's not how ASLR works — it slides whole sections, it doesn't reshuffle their insides.
With ASLR on, is the rip still a fixed distance from the sfp?
Yes — rip stays 4 bytes above sfp
Why: §4.11: ASLR randomizes ABSOLUTE addresses (the section base), NOT relative offsets. The internal layout is unchanged, which is exactly why a single leaked address compromises the whole section.
Section
Part 5 · §4.4–§4.11
Concept
Three mitigations, three attack links, three previewed bypasses. This single table previews all of Week 5.
| Mitigation | Attack it stops | How it's bypassed |
|---|---|---|
| NX / W^X | shellcode INJECTION (run bytes in a writable buffer) | code reuse — ret2libc / ROP (L13–L14) |
| stack canary | contiguous overflow of the rip | guess (~2^24) / leak the canary / non-contiguous write (format string) |
| ASLR | hardcoded addresses | guess (~2^16) or leak an address (L15) |
Notice each bypass requires the attacker to learn or reuse something they didn't have before — that's the cost the mitigation imposes.
Counterexample
Discussion prompt
Three mitigations, three attack links, three previewed bypasses. This single table previews all of Week 5.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Answer:
Notice each bypass requires the attacker to learn or reuse something they didn't have before — that's the cost the mitigation imposes.
Intuition
Alone, each is bypassable. Together they compose: to win against all three, the attacker must, in one exploit, leak an address (beat ASLR) and read the canary (beat the canary) and reuse existing code (beat NX).
That usually means finding two distinct bugs — a read/leak primitive and a write primitive. Doubling the bugs needed is the synergy: the whole is harder than the sum of the parts.
Ranking
Put in order
Put the moves of What the attacker needs against all three into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. Both ASLR and the canary are beaten by disclosing a secret (an address, a value) — so the attacker needs a read/info-leak primitive.
Worked example
Tally the prerequisites an exploit must satisfy to land on a system with NX + canary + ASLR all on. Each row is a separate thing the attacker must obtain.
// requirements to win against NX + canary + ASLR:
// 1. defeat ASLR -> learn a real runtime address (leak)
// 2. defeat canary -> learn the secret canary value (leak)
// 3. defeat NX -> reuse existing code via ROP (no injection)
// (1) and (2) need a READ/leak bug; (3) needs a WRITE bug
// => typically TWO distinct vulnerabilities, chainedTwo of three needs are leaks
Why: Both ASLR and the canary are beaten by disclosing a secret (an address, a value) — so the attacker needs a read/info-leak primitive.
The third need is a controlled write via reuse
Why: NX forces ROP: the attacker builds the payload only from existing executable code, then needs a write primitive to place the ROP chain on the stack.
Conclusion: chain a leak with a write
Why: One bug rarely supplies both. Combining the mitigations typically forces the attacker to find and chain MULTIPLE distinct vulnerabilities — that is the synergy in concrete terms.
| Mitigation | What must be learned/done | Bug primitive |
|---|---|---|
| ASLR | a real runtime address | read / leak |
| stack canary | the secret canary value | read / leak |
| NX | a ROP chain from existing code | write |
Comparison
Comparison matrix
From What the attacker needs against all three: refill the Bug primitive column from what you know. The rest of the table is as it appeared.
| Mitigation | What must be learned/done | Bug primitive |
|---|---|---|
| ASLR | a real runtime address | read / leak |
| stack canary | the secret canary value | read / leak |
| NX | a ROP chain from existing code | write |
Anomaly
Predict first
A student writes this, and it looks reasonable:
We enabled ASLR. Can we skip the canary and NX?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Each mitigation has a known, cheap bypass: ASLR alone falls to one leak, NX alone falls to ret2libc, the canary alone falls to a format-string %n.
We enabled ASLR. Can we skip the canary and NX?
Why: Each mitigation has a known, cheap bypass: ASLR alone falls to one leak, NX alone falls to ret2libc, the canary alone falls to a format-string %n. Any single layer is a thin wall.
Trap
We enabled ASLR. Can we skip the canary and NX?
Ship with only one mitigation
Why: Each mitigation has a known, cheap bypass: ASLR alone falls to one leak, NX alone falls to ret2libc, the canary alone falls to a format-string %n. Any single layer is a thin wall.
We enabled ASLR. Can we skip the canary and NX?
Enable all three — they're synergistic
Why: §4.4: the point is defense-in-depth. Combined, the attacker must defeat every layer at once (a leak AND a write), typically forcing them to chain multiple distinct vulnerabilities.
Two truths and a lie
Sort into buckets
Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.
[shellcode]; we never write real attack bytes. The skill being built is knowing which defense stops which step of the L8 attack.; Mitigations assume the bug is still there and try to stop the exploitation anyway.; A mitigation raises the bar; it does not remove the vulnerability. The book is blunt about the limit.Pattern
Edge cases
Discussion prompt
Pattern: match a mitigation to the attack step works on the cases you have just seen. Push it to the edge: what is the most degenerate input it still handles — empty, zero, one item, everything equal — and what is the first case where it stops being true? Name the case, not just "it breaks".
Hint: Try the smallest legal input, then the largest, then the one where two things collide. Methods are specified at their edges; the middle takes care of itself.
Answer:
Elimination
Eliminate the wrong options
Why does the stack canary FAIL to stop this format-string write to the rip?
3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.
Survives elimination: A
Why: A stack canary only detects a CONTIGUOUS overflow that climbs from a local buffer up through the canary to the rip. A format-string %n write goes straight to an arbitrary address — the rip — without ever touching the canary, so the on-return comparison still matches and the program returns into attacker control.
Check
A 32-bit program has a stack canary, NX, and ASLR all enabled. The attacker finds a format-string bug that uses %n to write 4 bytes to an arbitrary address of their choosing, and they point it at the rip. Solve on paper before clicking.
Check your understanding
Why does the stack canary FAIL to stop this format-string write to the rip?
Answer: A
Why: A stack canary only detects a CONTIGUOUS overflow that climbs from a local buffer up through the canary to the rip. A format-string %n write goes straight to an arbitrary address — the rip — without ever touching the canary, so the on-return comparison still matches and the program returns into attacker control.
Concept
%n write goes around the canary to the rip.Concept
Concept
Connect it up
Draw it
One page, no notation unless you need it: draw how these connect — Why Mitigations Exist · Non-Executable Pages · Stack Canaries · ASLR · Putting It Together. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.
Recap
You can explain why mitigations exist (and that only a memory-safe language is a true cure), describe NX/W^X stopping shellcode injection, place a stack canary and trace it catching a contiguous overflow, describe ASLR and the absolute-vs-relative subtlety, and fill in the mitigation → attack → bypass table — arguing why combining all three is synergistic.
| Mitigation | Stops | Bypassed by |
|---|---|---|
| NX / W^X | shellcode injection | code reuse (ret2libc / ROP) |
| stack canary | contiguous rip overwrite | guess / leak / non-contiguous %n write |
| ASLR | hardcoded addresses | guess (~2^16) or leak one address |
| the thesis | — | only a memory-safe language prevents ALL exploits |
Want this taught 1-on-1? Alexander tutors Computer Security — $55/session, free consultation.