CS 161, Lesson 15, in 50 slides and code mode. It covers the three things canaries do not stop, guessing a canary against leaking one - 24 bits of entropy against 56 - and pointer authentication, which stuffs a PAC into unused address bits. It then subverts ASLR by guessing or leaking a single absolute address, since rip = sfp + 4, and explains why combining ASLR, NX, and canaries forces an attacker to find both a leak AND a write. The examples are toys running in a sandbox, and it is anchored to textbook sections 4.9 to 4.13.
Subject: Computer Security · 88 slides · code lesson
Open the interactive version of this deck · Homework for this lesson
Title
CS 161 · Lesson 15 of 45
canaries · pointer authentication · ASLR · combining defenses · the arms race
Objectives
%n writes) and the two ways to defeat one (guess vs leak).rip = sfp + 4).Warm-up
Discussion prompt
Before we open L15 · Subverting Canaries, Pointer Authentication, ASLR & Combining Mitigations: without looking back, what was the main idea of L14 · Return-Oriented Programming & Stack Canaries, 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 14 (50 slides, code mode): generalizing ret2libc into ROP gadget chains, the book's two-gadget add-4-to-edx example, gadget finding and chaining across rets, stack canaries (§4.8) — a random NULL-containing word between locals and the saved registers, detected on return — and the canary's limits that bridge to L15. Toy/sandbox examples only.
Concept
Every example here is a toy in a sandbox, for authorized security education — exactly how the textbook teaches it. You learn how mitigations fail so you can layer them correctly and know what each one does and does not buy you.
No real shellcode, no real exploits. The skill being built is reasoning about entropy, address bits, and how many bugs an attacker needs.
Counterexample
Discussion prompt
No real shellcode, no real exploits. The skill being built is reasoning about entropy, address bits, and how many bugs an attacker needs.
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.
Section
Part 1 · §4.9
Concept
A stack canary is a random value placed between the locals and the saved sfp/rip. On ret, the code checks it; a contiguous overflow that reaches the rip must first overwrite the canary, and the mismatch aborts the program.
But the canary only guards one path: a contiguous write, on the stack, that crosses it. Anything that doesn't cross it is invisible to the canary.
| Attack | Crosses the canary? | Canary helps? |
|---|---|---|
| contiguous stack overflow to rip | yes | yes — aborts |
| heap overflow | no (not the stack) | no |
| flip an adjacent local flag | no (below canary) | no |
| %n write straight to rip | no (non-contiguous) | no |
Discrimination
Sort into buckets
Sort these by Canary helps?, from memory, without looking back at What a canary does — and where it can't reach. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.
Concept
authenticated flag that sits below the canary never reaches it.%n writes directly to the rip, 'around' the canary, without changing it.The common thread: the canary only catches a contiguous stack write that crosses it. Defeat that assumption and the canary is blind.
Analogy
Discussion prompt
Explain Three things a canary does NOT stop 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:
The common thread: the canary only catches a contiguous stack write that crosses it. Defeat that assumption and the canary is blind.
Intuition
Picture a tripwire strung across one hallway (the path from buf up to the rip). Walk that hallway and you trip it. But the building has other rooms (the heap) and other doors (the authenticated flag below it).
And %n is teleportation: it drops the attacker directly on the rip without ever walking the hallway. The tripwire is real, but it covers exactly one route.
Estimation
Predict first
A format-string bug lets %n write a value to any address the attacker chooses — including the rip — without writing the bytes in between. Trace what the canary sees.
Commit before you compute: what does Why %n sails past the canary come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: The canary is never modified, so the check passes
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. On return the prologue compares the canary to its saved value; they still match because nothing touched it.
Worked example
A format-string bug lets %n write a value to any address the attacker chooses — including the rip — without writing the bytes in between. Trace what the canary sees.
// vulnerable: attacker controls the format string
printf(user_input);
// user_input contains a crafted %n that writes
// directly to &rip (ebp+4) — NOT a byte-by-byte overflow%n computes a target address and writes there
Why: Unlike gets, %n does not march byte-by-byte from buf upward — it stores to one chosen address. The canary at ebp+0 is simply skipped over.
The canary is never modified, so the check passes
Why: On return the prologue compares the canary to its saved value; they still match because nothing touched it. The hijacked rip is used anyway.
| Slot | Touched by gets overflow? | Touched by %n write? |
|---|---|---|
| buf | yes | no |
| canary | yes — must cross | no — skipped |
| sfp | yes | no |
| rip | yes (the goal) | yes — written directly |
Discrimination
Sort into buckets
Sort these by Touched by gets overflow?, from memory, without looking back at Why %n sails past the canary. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.
Anomaly
Predict first
A student writes this, and it looks reasonable:
Does a stack canary stop a heap overflow?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: It's called a 'stack' canary for a reason — it sits in stack frames only.
Does a stack canary stop a heap overflow?
Why: It's called a 'stack' canary for a reason — it sits in stack frames only. A heap chunk overflow has no canary above it to cross, so nothing aborts.
Trap
Does a stack canary stop a heap overflow?
Assume the canary guards all of memory
Why: It's called a 'stack' canary for a reason — it sits in stack frames only. A heap chunk overflow has no canary above it to cross, so nothing aborts.
Does a stack canary stop a heap overflow?
No — canaries do nothing outside the stack
Why: §4.9: a canary only guards the path from locals to the saved rip in a stack frame. Heap corruption, use-after-free, and adjacent-local writes are entirely outside its reach.
Trap
A format string lets %n write to the rip. Canary present — safe?
Assume crossing into the rip always trips the canary
Why: Confuses 'reaching the rip' with 'crossing the canary'. %n writes to one address; it never writes the canary's slot, so there is nothing to detect.
A format string lets %n write to the rip. Canary present — safe?
No — %n is a non-contiguous write that goes around the canary
Why: §4.9: the canary only catches a contiguous overflow that overwrites it on the way up. %n stores directly to the rip and leaves the canary unchanged, so the check passes.
Break the constraint
Discussion prompt
The rule this trap just fixed:
A format string lets %n write to the rip.
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:
Confuses 'reaching the rip' with 'crossing the canary'. %n writes to one address; it never writes the canary's slot, so there is nothing to detect.
Concept
Even on the contiguous-overflow path, an attacker can win by writing the canary's correct value back, so the check passes. Two techniques get that value:
guess the canary — Brute-force the canary value — feasible only when entropy is low and each wrong guess is cheap.
leak the canary — Use a separate read vulnerability to print the canary, copy it, and write it back within the same run.
Explain it
Discussion prompt
Explain Two ways to defeat a canary on its own path 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:
Even on the contiguous-overflow path, an attacker can win by writing the canary's correct value back, so the check passes. Two techniques get that value:
Ranking
Put in order
Put the moves of Guessing: 32-bit vs 64-bit entropy 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. 2^24 ≈ 16.7 million tries; at 1/sec that is over 100 days.
Worked example
On 32-bit, the canary is 4 bytes but one byte is a null terminator (to stop string-copy leaks), leaving ~24 bits of entropy — about 1 in 2^24 per try. On 64-bit it's ~56 bits.
32-bit canary: 4 bytes, 1 null byte fixed -> 24 random bits
64-bit canary: 8 bytes, 1 null byte fixed -> 56 random bits
expected tries ~ 2^bits ; time = tries / (guesses per second)32-bit at 1 guess/sec is impractical
Why: 2^24 ≈ 16.7 million tries; at 1/sec that is over 100 days.
But at thousands/sec it's just hours
Why: 2^24 / a few thousand per second ≈ a few hours. Low entropy + a cheap, fast retry loop = guessable.
64-bit is out of reach
Why: 2^56 ≈ 7.2×10^16; even at 1000/sec that's ~2 million years. Entropy is the whole defense.
| Platform | Entropy | ~Tries | Time @ 1000/sec |
|---|---|---|---|
| 32-bit | 24 bits | 2^24 ≈ 1.7×10^7 | ~a few hours |
| 32-bit | 24 bits | 2^24 ≈ 1.7×10^7 | @ 1/sec: >100 days |
| 64-bit | 56 bits | 2^56 ≈ 7.2×10^16 | ~2 million years |
Comparison
Comparison matrix
From Guessing: 32-bit vs 64-bit entropy: refill the Time @ 1000/sec column from what you know. The rest of the table is as it appeared.
| Platform | Entropy | ~Tries | Time @ 1000/sec |
|---|---|---|---|
| 32-bit | 24 bits | 2^24 ≈ 1.7×10^7 | ~a few hours |
| 32-bit | 24 bits | 2^24 ≈ 1.7×10^7 | @ 1/sec: >100 days |
| 64-bit | 56 bits | 2^56 ≈ 7.2×10^16 | ~2 million years |
Estimation
Predict first
A read vulnerability (e.g. a format string with %x) can print the canary. The attacker reads it, then in the same run overflows the buffer but writes the real canary into the canary slot.
Commit before you compute: what does Leaking: copy the canary, write it back come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: Overflow, writing the leaked canary into its own slot
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. The canary is overwritten — but with itself, so the on-return check still passes.
Worked example
A read vulnerability (e.g. a format string with %x) can print the canary. The attacker reads it, then in the same run overflows the buffer but writes the real canary into the canary slot.
step 1: read bug leaks canary = 0x00ab12cd (note the null byte)
step 2: payload = [buf filler]
+ 0x00ab12cd # canary slot: exact value
+ [sfp filler]
+ [target address] # ripLeak the canary first
Why: A read primitive prints the canary value; the attacker now knows it for this process instance.
Overflow, writing the leaked canary into its own slot
Why: The canary is overwritten — but with itself, so the on-return check still passes. The overflow continues past it into the rip.
| Payload region | Overwrites | Value |
|---|---|---|
| filler | buf | garbage |
| canary slot | canary | 0x00ab12cd (leaked, unchanged) |
| filler | sfp | garbage |
| address | rip | attacker target |
Trade off
Comparison matrix
From Leaking: copy the canary, write it back: every row here is a choice with a cost. Fill the Overwrites column, then say which row you would actually pick and what you give up for it.
| Payload region | Overwrites | Value |
|---|---|---|
| filler | buf | garbage |
| canary slot | canary | 0x00ab12cd (leaked, unchanged) |
| filler | sfp | garbage |
| address | rip | attacker target |
Anomaly
Predict first
A student writes this, and it looks reasonable:
Stack canary is on. Is the overflow dead?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Ignores leaks and low entropy. A read primitive hands over the canary; on 32-bit even brute force is hours, not eons.
Stack canary is on. Is the overflow dead?
Why: Ignores leaks and low entropy. A read primitive hands over the canary; on 32-bit even brute force is hours, not eons. A canary raises cost, it does not close the door.
Trap
Stack canary is on. Is the overflow dead?
Conclude the bug can't be exploited
Why: Ignores leaks and low entropy. A read primitive hands over the canary; on 32-bit even brute force is hours, not eons. A canary raises cost, it does not close the door.
Stack canary is on. Is the overflow dead?
No — it can be guessed (low entropy) or leaked (read bug)
Why: §4.9: write back the correct canary and the check passes. Canaries are one layer; assume the bug is exploitable until other layers also hold.
Intuition
Brute-forcing isn't about entropy alone — it's entropy times the cost of each try. 24 bits is small; if a wrong guess is free and fast (thousands/sec), 24 bits falls in hours.
But if every wrong guess crashes the process and respawning is slow — or a rate-limiter kicks in — the effective cost per try explodes and the same 24 bits becomes impractical. Always ask both questions.
Section
Part 2 · §4.10
Concept
Pointer authentication (PAC) generalizes the canary to all pointers, not just the saved rip. It's a 64-bit feature: it exploits that on 64-bit, many address bits are unused.
A full 64-bit space is 18 exabytes, but a real CPU might address only ~4 TB (≈ 42 bits), leaving ~22 unused top bits that are always 0.
| Quantity | Value |
|---|---|
| 64-bit address space | 18 exabytes (2^64) |
| Typical CPU reach | ~4 TB (~42 address bits) |
| Unused top bits | ~22 bits, always 0 — free real estate |
Intuition
A pointer is a 64-bit number, but the top bits are dead weight — always zero. PAC's trick: stuff a secret into those wasted bits before pushing the pointer, then verify and strip it on read.
If an attacker overwrites the pointer, they wipe out the secret in the top bits. On read, the PAC is wrong → the CPU crashes instead of using a forged pointer. No copy of the pointer is stored anywhere — the secret rides inside the same 64 bits.
Step zero
Discussion prompt
Worked example: inserting a PAC — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: Identify the unused bits
Answer:
Worked example
Take a saved rip of 0x0000001234567899 on a 40-bit address space — the top 24 bits (3 bytes) are always 0. PAC fills those with a secret.
address = 0x0000001234567899 # top 3 bytes = 0x000000
PAC = 0xABCDEF # 24-bit secret
stored pointer = 0xABCDEF1234567899 # PAC packed into the unused bitsIdentify the unused bits
Why: On a 40-bit space the low 40 bits are the real address; the top 24 bits are guaranteed 0, so they carry no address information.
Pack the PAC into the top 24 bits
Why: 0x000000... becomes 0xABCDEF..., giving 0xABCDEF1234567899. The low 40 bits — the real address — are untouched.
On read, verify then strip
Why: The CPU checks the top bits hold the right PAC, then zeroes them back to recover 0x0000001234567899. Wrong PAC → crash.
| Stage | Top 24 bits | Low 40 bits (address) |
|---|---|---|
| original | 0x000000 | 0x1234567899 |
| stored with PAC | 0xABCDEF | 0x1234567899 |
| after verify+strip | 0x000000 | 0x1234567899 |
Error analysis
Annotate
Walk the callouts on Worked example: inserting a PAC. Each one is a place this is easy to get subtly wrong.
Concept
A fixed secret in every pointer is weak — leak it once and forge forever. The strong version makes the PAC per-address: PAC = f(KEY, ADDRESS), deterministic and secure.
The book notes f is a MAC (from the crypto unit): without KEY, an attacker cannot forge a valid PAC for any address. Because the PAC depends on the address, changing the address invalidates the old PAC — and a new one can't be computed without KEY. For a 20-bit secret, that's a 1-in-2^20 guess.
| PAC design | Attacker who changes the address must… |
|---|---|
| fixed secret | reuse the same secret — leak it once, forge anything |
| per-address f(KEY, ADDR) | compute a NEW PAC — impossible without KEY |
Concept
Note what's not on the list: directly overwriting a pointer and keeping the old PAC. With a per-address PAC that fails — the changed address needs a new PAC you can't compute.
Anomaly
Predict first
A student writes this, and it looks reasonable:
How does PAC remember the real pointer to check it?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: There is no side table. That would cost extra memory per pointer — the whole appeal of PAC is that it's free.
How does PAC remember the real pointer to check it?
Why: There is no side table. That would cost extra memory per pointer — the whole appeal of PAC is that it's free.
Trap
How does PAC remember the real pointer to check it?
Assume PAC saves a backup copy of the pointer in a side table
Why: There is no side table. That would cost extra memory per pointer — the whole appeal of PAC is that it's free.
How does PAC remember the real pointer to check it?
It reuses the UNUSED top bits of the same 64-bit value
Why: §4.10: the PAC rides inside the pointer's wasted high bits. Verify+strip recomputes from the address itself; nothing is stored separately.
Trap
Overwrite a PAC'd pointer's address but leave its top-bit PAC intact?
Reuse the old PAC bits with a new low-address
Why: Works only for a fixed-secret PAC. With f(KEY, ADDRESS) the PAC is tied to the OLD address; against the new address it verifies as wrong → crash.
Overwrite a PAC'd pointer's address but leave its top-bit PAC intact?
A per-address PAC won't validate — you'd need a new one
Why: §4.10: PAC = f(KEY, ADDRESS). Change ADDRESS and the correct PAC changes; computing it needs KEY. Best you can do is a 1-in-2^20 guess.
Intuition
A canary works because the value is random and secret. PAC needs more: the secret must be unforgeable per address, so it's a MAC — a keyed function f(KEY, ADDRESS) you can verify but can't reproduce without KEY.
That's exactly the property the crypto unit builds. Without it, an attacker who saw one valid PAC could mint PACs for other addresses; with a MAC, each address's PAC is independent and unguessable beyond ~1-in-2^20.
Section
Part 3 · §4.12
Concept
ASLR randomizes where sections (stack, heap, code) load, so the attacker doesn't know absolute addresses. Same two outs as the canary: guess or leak.
guess (ASLR) — Brute-force the random offset; feasible only when entropy is low and retries are cheap.
leak (ASLR) — Use a read bug to obtain one absolute address, then derive the rest from fixed relative offsets.
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 guess the canary, leak the canary, guess (ASLR), leak (ASLR) as L15 · Subverting Canaries, Pointer Authentication, ASLR & Combining Mitigations uses them. Pairing them correctly is the test of whether you could state each one with the slide switched off.
Step zero
Discussion prompt
Guessing: only ~16 bits on 32-bit — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: 16 bits is brute-forceable if retries are cheap
Answer:
Worked example
Page-alignment constraints mean the low bits of a section base aren't random — they're fixed by the page size. On a 32-bit system that leaves only ~16 bits of entropy, so ~1 in 2^16.
32-bit ASLR entropy ~ 16 bits -> 2^16 = 65,536 possibilities
64-bit ASLR entropy >> 16 bits -> far more, much harder16 bits is brute-forceable if retries are cheap
Why: 2^16 = 65,536; at 1 try/sec that's well under a day. Shacham et al. (2004) demonstrated this against 32-bit ASLR.
…but infeasible if each crash costs exponentially more
Why: If a wrong guess crashes a process that's slow to respawn (or trips rate-limiting), the effective cost per try explodes and the same 2^16 becomes impractical.
64-bit has far more entropy
Why: More address bits → many more random bits in the base → brute force is not practical.
| Platform | ASLR entropy | Possibilities | Brute force? |
|---|---|---|---|
| 32-bit | ~16 bits | 2^16 = 65,536 | feasible if retries cheap |
| 32-bit | ~16 bits | 2^16 = 65,536 | infeasible if each crash is costly |
| 64-bit | >> 16 bits | very large | not practical |
Reverse engineer
Discussion prompt
Work backwards. The example finished here:
64-bit has far more entropy
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:
Page-alignment constraints mean the low bits of a section base aren't random — they're fixed by the page size. On a 32-bit system that leaves only ~16 bits of entropy, so ~1 in 2^16.
Concept
ASLR randomizes absolute addresses — the base of each section. It does NOT randomize relative offsets within a section.
The frame layout is fixed: the rip is still 4 bytes above the sfp, regardless of where the stack landed this run. So leaking one absolute address lets the attacker deduce all the others.
| What ASLR randomizes | What it does NOT randomize |
|---|---|
| stack base (where the frame lands) | rip-to-sfp distance (always 4 bytes) |
| heap base | field offsets within a struct |
| code/library base | instruction offsets within the code |
Comparison
Comparison matrix
From The crucial subtlety: absolute vs relative: refill the What it does NOT randomize column from what you know. The rest of the table is as it appeared.
| What ASLR randomizes | What it does NOT randomize |
|---|---|
| stack base (where the frame lands) | rip-to-sfp distance (always 4 bytes) |
| heap base | field offsets within a struct |
| code/library base | instruction offsets within the code |
Intuition
ASLR slides the whole map to a random spot, but the streets keep their layout. If a friend tells you 'I'm at the corner store' (one absolute address), and you know the post office is always two blocks north (a fixed relative offset), you instantly know where the post office is.
That's why a single leak is so dangerous: ASLR's secret is one number (the base), and one landmark reveals it.
Ranking
Put in order
Put the moves of Leak the sfp → compute rip = sfp + 4 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. A single read primitive defeats ASLR's secret — the random stack base — because the leaked value already encodes it.
Worked example
A read bug leaks a saved register on the stack — say the absolute address of the sfp. From the fixed frame layout, the rip is exactly 4 bytes above it (32-bit).
leaked: address_of_sfp = 0xbf8e2a40 # absolute, ASLR-randomized
# relative offset is NOT randomized:
address_of_rip = address_of_sfp + 4 = 0xbf8e2a44
# now the attacker knows exactly where to aim a writeLeak one absolute address (the sfp)
Why: A single read primitive defeats ASLR's secret — the random stack base — because the leaked value already encodes it.
Add the fixed offset to reach the rip
Why: rip = sfp + 4 in 32-bit; this offset is the same every run because ASLR doesn't randomize within-frame layout.
Derive further addresses the same way
Why: Code and library addresses sit at known offsets from leaked pointers too, so one leak unrolls the entire address map.
| Known | Offset | Derived |
|---|---|---|
| sfp = 0xbf8e2a40 (leaked) | +4 | rip = 0xbf8e2a44 |
| rip = 0xbf8e2a44 | −12 | buf = 0xbf8e2a38 |
| any leaked code ptr | fixed | any other code address |
Reverse engineer
Discussion prompt
Work backwards. The example finished here:
Derive further addresses the same way
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:
A read bug leaks a saved register on the stack — say the absolute address of the sfp. From the fixed frame layout, the rip is exactly 4 bytes above it (32-bit).
Anomaly
Predict first
A student writes this, and it looks reasonable:
If I leak the sfp, do I still not know where the rip is?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Confuses randomizing the section BASE with randomizing the LAYOUT inside it.
If I leak the sfp, do I still not know where the rip is?
Why: Confuses randomizing the section BASE with randomizing the LAYOUT inside it. If offsets moved, the compiler couldn't emit fixed frame code at all.
Trap
If I leak the sfp, do I still not know where the rip is?
Assume ASLR scrambles the gap between sfp and rip
Why: Confuses randomizing the section BASE with randomizing the LAYOUT inside it. If offsets moved, the compiler couldn't emit fixed frame code at all.
If I leak the sfp, do I still not know where the rip is?
No — relative offsets are fixed; rip = sfp + 4
Why: §4.12: ASLR randomizes absolute addresses only. The rip is always 4 bytes above the sfp, so one leaked absolute address yields the rest.
Anomaly
Predict first
A student writes this, and it looks reasonable:
A bug leaks just a single stack pointer. No big deal, right?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Underestimates the chain. ASLR's whole secret is the base; one absolute address plus fixed offsets reconstructs every other address in the section.
A bug leaks just a single stack pointer. No big deal, right?
Why: Underestimates the chain. ASLR's whole secret is the base; one absolute address plus fixed offsets reconstructs every other address in the section.
Trap
A bug leaks just a single stack pointer. No big deal, right?
Dismiss it because it's only one value
Why: Underestimates the chain. ASLR's whole secret is the base; one absolute address plus fixed offsets reconstructs every other address in the section.
A bug leaks just a single stack pointer. No big deal, right?
One leak defeats ASLR — it reveals the base
Why: §4.12: from one absolute address the attacker derives the rip, buf, code, and library addresses via known offsets. A single read bug can unravel the whole map.
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.
Concept
Notice the symmetry: both the canary and ASLR keep a secret (the canary value; the section base), and both fall to the same two attacks — guess it when entropy is low, or leak it with a read bug.
| Defense | Secret kept | Guess (entropy) | Leak (read bug) |
|---|---|---|---|
| stack canary | the canary value | 24-bit (32) / 56-bit (64) | print it, write it back |
| ASLR | the section base | ~16-bit (32-bit) | one absolute address → all |
This is why a read primitive is so valuable to an attacker: a single leak can defeat both the canary and ASLR at once.
Section
Part 4 · §4.13
Concept
No single mitigation is sufficient — each has an out. The power is synergy: combine them and the attacker needs to defeat all of them at once, which often requires multiple separate bugs.
ASLR + NX is the classic pair: NX means you can't inject shellcode (the stack/heap isn't executable); ASLR means you can't reuse existing code (you don't know its address). Each blocks the other's escape.
Step zero
Discussion prompt
Why NX alone or ASLR alone isn't enough — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: NX alone is beaten by code reuse
Answer:
Worked example
Each mitigation, by itself, leaves an escape. It's only the pair that closes both escapes at once.
NX only: inject shellcode? NO (not executable)
reuse existing code? YES — you know its address -> WIN
ASLR only: reuse existing code? NO (address hidden)
inject shellcode? YES — stack is executable -> WIN
NX + ASLR: inject? NO reuse? NO (address hidden) -> need a LEAK firstNX alone is beaten by code reuse
Why: If only NX is on, the attacker can't run injected bytes — but existing code sits at a known, fixed address, so ROP/ret2libc still works.
ASLR alone is beaten by injection
Why: If only ASLR is on, addresses are hidden — but the stack is executable, so the attacker just injects shellcode into buf and jumps to it (with a NOP sled for slop).
Together they force a leak
Why: NX kills injection AND ASLR hides reusable code, so neither solo escape works. The attacker must first leak addresses — a second, separate bug.
| Enabled | Inject shellcode? | Reuse code? | Result |
|---|---|---|---|
| NX only | no | yes (known addr) | exploitable |
| ASLR only | yes (exec stack) | no | exploitable |
| NX + ASLR | no | no (addr hidden) | needs a leak first |
Error analysis
Annotate
Walk the callouts on Why NX alone or ASLR alone isn't enough. Each one is a place this is easy to get subtly wrong.
Intuition
NX is a lock that says 'this memory can't run as code'. The way around it is ROP — reuse code already in the program. But ASLR is a second lock that hides where that code is.
To pick both, you need two different keys: a leak (read bug) to find the addresses ASLR hid, and a write (the overflow / %n) to place the ROP chain NX forced you into. One bug isn't enough.
Explain it
Discussion prompt
Explain Two locks that need two different keys 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:
NX is a lock that says 'this memory can't run as code'. The way around it is ROP — reuse code already in the program. But ASLR is a second lock that hides where that code is.
Ranking
Put in order
Put the moves of The attack chain against ASLR + NX + canaries 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. Leaking one absolute address reveals the section base (ASLR); leaking the canary reveals the value to write back.
Worked example
Walk the full chain. Each mitigation forces the attacker to add a step — and crucially, a leak and a write are two different vulnerabilities.
phase 1 (READ bug): leak an absolute address -> defeat ASLR
leak the canary -> defeat the canary
phase 2 (compute): addr_rip = leaked + fixed offsets
phase 3 (WRITE bug): overflow, writing the real canary back,
then a ROP chain -> defeat NX
phase 4: ret runs the ROP chain -> controlPhase 1 — a read bug defeats ASLR and the canary at once
Why: Leaking one absolute address reveals the section base (ASLR); leaking the canary reveals the value to write back. Both come from a read primitive.
Phase 2 — compute targets from fixed offsets
Why: Relative offsets aren't randomized, so the leaked address yields the rip and the code addresses the ROP chain needs.
Phase 3 — a write bug places the ROP chain (defeats NX)
Why: NX blocks injected shellcode, so the attacker reuses existing code via ROP. This needs a separate WRITE primitive, and it must also restore the leaked canary so the check passes.
| Mitigation | What it blocks | Extra step it forces |
|---|---|---|
| NX | running injected shellcode | build a ROP chain (reuse code) |
| ASLR | knowing code addresses | a LEAK (read bug) |
| stack canary | contiguous overflow to rip | leak/guess + write canary back |
| all three | any single-bug exploit | need a READ bug AND a WRITE bug |
Trade off
Comparison matrix
From The attack chain against ASLR + NX + canaries: every row here is a choice with a cost. Fill the Extra step it forces column, then say which row you would actually pick and what you give up for it.
| Mitigation | What it blocks | Extra step it forces |
|---|---|---|
| NX | running injected shellcode | build a ROP chain (reuse code) |
| ASLR | knowing code addresses | a LEAK (read bug) |
| stack canary | contiguous overflow to rip | leak/guess + write canary back |
| all three | any single-bug exploit | need a READ bug AND a WRITE bug |
Concept
Defenders don't stop at NX + ASLR + canaries. Further mitigations (⊕, beyond this textbook section) raise the cost again:
ret.Analogy
Discussion prompt
Explain Beyond the book: more layers (⊕) 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:
Defenders don't stop at NX + ASLR + canaries. Further mitigations (⊕, beyond this textbook section) raise the cost again:
Intuition
None of these is a wall. Each one multiplies the cost of an exploit — more bugs needed, more entropy to guess, more constraints on the control flow. Security here is economics: make the attack cost more than the prize.
This 'force multiple bugs, raise the cost' logic isn't unique to memory safety — it recurs in the crypto and web units, where defenses similarly stack to make any single flaw insufficient.
Section
Part 5 · §4.13 / §4.4
Concept
The practical baseline: enable NX + ASLR + PIE (⊕) + stack canaries together. Each is cheap; the synergy is what protects — the combination forces an attacker to find and chain multiple bugs.
| Mitigation | Stops | Defeated alone by |
|---|---|---|
| NX | injected shellcode | ROP (code reuse) |
| ASLR + PIE (⊕) | knowing addresses | a leak or a guess |
| stack canary | contiguous stack overflow | a leak or low-entropy guess |
| all together | single-bug exploits | needs leak + write (multiple bugs) |
Comparison
Comparison matrix
From Baseline defense recommendation: refill the Defeated alone by column from what you know. The rest of the table is as it appeared.
| Mitigation | Stops | Defeated alone by |
|---|---|---|
| NX | injected shellcode | ROP (code reuse) |
| ASLR + PIE (⊕) | knowing addresses | a leak or a guess |
| stack canary | contiguous stack overflow | a leak or low-entropy guess |
| all together | single-bug exploits | needs leak + write (multiple bugs) |
Concept
The history is a back-and-forth: a mitigation ships, attackers find a way around it (leak, guess, ROP), defenders add another layer. No single mitigation is ever sufficient.
Which loops back to §4.4's thesis: mitigations only raise the cost of exploiting a bug — the only true fix is to remove the bug class entirely with a memory-safe language (L11).
Counterexample
Discussion prompt
The history is a back-and-forth: a mitigation ships, attackers find a way around it (leak, guess, ROP), defenders add another layer. No single mitigation is ever sufficient.
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:
Which loops back to §4.4's thesis: mitigations only raise the cost of exploiting a bug — the only true fix is to remove the bug class entirely with a memory-safe language (L11).
Section
Part 6
Constraint
Discussion prompt
Run Pattern: reason about a layered defense with this step confiscated:
Count the bugs the combination forces: ASLR + NX + canary ⇒ a read (leak) AND a write — two vulnerabilities.
Is it still possible? If it is, say what takes its place and what it costs you. If it is not, say exactly what that step was providing that nothing else does.
Hint: A step you can drop for free was never load-bearing. If you cannot drop it, name the thing that goes wrong the moment it is gone.
Answer:
%n; ASLR → a leak or a guess; NX → ROP; PAC → a signed-pointer bug, key…Pattern
%n; ASLR → a leak or a guess; NX → ROP; PAC → a signed-pointer bug, key discovery, or breaking f.Edge cases
Discussion prompt
Pattern: reason about a layered defense 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:
%n; ASLR → a leak or a guess; NX → ROP; PAC → a signed-pointer bug, key…Elimination
Eliminate the wrong options
To exploit this overflow against ASLR + NX, which combination of vulnerabilities does the attacker most need?
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: B
Why: NX blocks injected shellcode, forcing code reuse (ROP); ASLR hides where that code is, so the attacker first needs a read/leak bug to learn addresses, then a write bug to place the ROP chain. That's two distinct vulnerabilities — a leak AND a write.
Check
A 32-bit program has a buffer overflow but ships with ASLR + NX enabled. The attacker has no other information about the address layout. Reason it through before clicking.
Check your understanding
To exploit this overflow against ASLR + NX, which combination of vulnerabilities does the attacker most need?
Answer: B
Why: NX blocks injected shellcode, forcing code reuse (ROP); ASLR hides where that code is, so the attacker first needs a read/leak bug to learn addresses, then a write bug to place the ROP chain. That's two distinct vulnerabilities — a leak AND a write.
Concept
%n writes directly to the rip, around the canary.Concept
Concept
-fstack-protector and ASLR on, leak the canary and a stack address in gdb, and confirm rip = sfp + 4 holds across runs while the absolute address changes.Connect it up
Draw it
One page, no notation unless you need it: draw how these connect — Subverting the Canary · Pointer Authentication · Subverting ASLR · Combining Mitigations · Defense & The Arms Race · Consolidate. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.
Recap
You can name the three things a canary never stops, estimate guessing cost from entropy and retry cost, explain how PAC hides a MAC in unused address bits, use rip = sfp + 4 to turn one ASLR leak into the whole map, and explain why ASLR + NX + canaries force an attacker to find both a leak and a write.
| Idea | The one fact |
|---|---|
| Canary blind spots | heap · other locals · %n (non-contiguous) |
| Defeat a canary | guess (24-bit / 56-bit) or leak, then write it back |
| PAC | MAC f(KEY,ADDR) packed into unused top bits; per-address |
| ASLR subtlety | randomizes absolute bases, NOT relative offsets (rip = sfp+4) |
| Combining mitigations | ASLR+NX+canary ⇒ need a leak AND a write |
| Real fix | memory-safe language (L11); mitigations raise cost (§4.4) |
Want this taught 1-on-1? Alexander tutors Computer Security — $55/session, free consultation.