L15 · Subverting Canaries, Pointer Authentication, ASLR & Combining Mitigations

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

What this lesson covers

The lesson, slide by slide

1. Subverting the Mitigations

Title

CS 161 · Lesson 15 of 45

canaries · pointer authentication · ASLR · combining defenses · the arms race

2. By the end of this lesson you can…

Objectives

  1. Name the three things a stack canary never stops (heap, other locals, non-contiguous %n writes) and the two ways to defeat one (guess vs leak).
  2. Compute canary brute-force feasibility from entropy: ~24 bits on 32-bit, ~56 bits on 64-bit.
  3. Explain how pointer authentication (PAC) stuffs a secret into the unused top bits of a 64-bit address and why a per-address PAC can't be reused.
  4. Subvert ASLR two ways and use the fact that it randomizes absolute addresses but not relative offsets (leak the sfp → rip = sfp + 4).
  5. Explain why combining ASLR + NX + canaries forces an attacker to find two bugs — a leak and a write.

3. What survived from L14 · Return-Oriented Programming & Stack Canaries?

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.

4. A note before we start: this is defensive

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.

5. Break it if you can: A note before we start: this is defensive

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.

6. Subverting the Canary

Section

Part 1 · §4.9

7. What a canary does — and where it can't reach

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.

AttackCrosses the canary?Canary helps?
contiguous stack overflow to ripyesyes — aborts
heap overflowno (not the stack)no
flip an adjacent local flagno (below canary)no
%n write straight to ripno (non-contiguous)no

8. Which is which, by Canary helps?

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.

yes — aborts
contiguous stack overflow to rip
no
heap overflow; flip an adjacent local flag; %n write straight to rip
g1
Canary helps? is "yes — aborts" for contiguous stack overflow to rip — that is what the table on "What a canary does — and where it can't…" records, and it is the single property separating this group from the rest.
g2
Canary helps? is "no" for heap overflow, flip an adjacent local flag, %n write straight to rip — that is what the table on "What a canary does — and where it can't…" records, and it is the single property separating this group from the rest.

9. Three things a canary does NOT stop

Concept

  1. Attacks outside the stack — a canary does nothing for heap memory; a heap overflow never touches it.
  2. Overwriting OTHER local variables — overflowing a buffer to flip an authenticated flag that sits below the canary never reaches it.
  3. Non-contiguous writes — a format-string %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.

10. By analogy: Three things a canary does NOT stop

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.

11. The canary is a tripwire on one hallway

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.

12. Guess the shape of the answer: Why %n sails past the canary

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.

13. Why %n sails past the canary

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.

SlotTouched by gets overflow?Touched by %n write?
bufyesno
canaryyes — must crossno — skipped
sfpyesno
ripyes (the goal)yes — written directly

14. Which is which, by Touched by gets overflow?

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.

yes
buf; sfp
yes — must cross
canary
yes (the goal)
rip
g1
Touched by gets overflow? is "yes" for buf, sfp — that is what the table on "Why %n sails past the canary" records, and it is the single property separating this group from the rest.
g2
Touched by gets overflow? is "yes — must cross" for canary — that is what the table on "Why %n sails past the canary" records, and it is the single property separating this group from the rest.
g3
Touched by gets overflow? is "yes (the goal)" for rip — that is what the table on "Why %n sails past the canary" records, and it is the single property separating this group from the rest.

15. Something is wrong here: 'the canary protects the heap too'

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.

16. Trap: 'the canary protects the heap too'

Trap

The 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.

The fix

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.

17. Trap: 'the canary stops a %n write'

Trap

The 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.

The fix

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.

18. Break it on purpose: 'the canary stops a %n write'

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.

19. Two ways to defeat a canary on its own path

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.

20. Teach it back: Two ways to defeat a canary on its own path

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:

21. What has to happen first: Guessing: 32-bit vs 64-bit entropy

Ranking

Put in order

Put the moves of Guessing: 32-bit vs 64-bit entropy into the order they have to happen.

  1. 32-bit at 1 guess/sec is impractical
  2. But at thousands/sec it's just hours
  3. 64-bit is out of reach

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.

22. Guessing: 32-bit vs 64-bit entropy

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.

PlatformEntropy~TriesTime @ 1000/sec
32-bit24 bits2^24 ≈ 1.7×10^7~a few hours
32-bit24 bits2^24 ≈ 1.7×10^7@ 1/sec: >100 days
64-bit56 bits2^56 ≈ 7.2×10^16~2 million years

23. Fill in: Time @ 1000/sec for Guessing: 32-bit vs 64-bit entropy

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.

PlatformEntropy~TriesTime @ 1000/sec
32-bit24 bits2^24 ≈ 1.7×10^7~a few hours
32-bit24 bits2^24 ≈ 1.7×10^7@ 1/sec: >100 days
64-bit56 bits2^56 ≈ 7.2×10^16~2 million years

24. Guess the shape of the answer: Leaking: copy the canary, write it back

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.

25. Leaking: copy the canary, write it back

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]  # rip

Leak 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 regionOverwritesValue
fillerbufgarbage
canary slotcanary0x00ab12cd (leaked, unchanged)
fillersfpgarbage
addressripattacker target

26. What each one costs: Leaking: copy the canary, write it back

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 regionOverwritesValue
fillerbufgarbage
canary slotcanary0x00ab12cd (leaked, unchanged)
fillersfpgarbage
addressripattacker target

27. Something is wrong here: 'a canary makes the program unexploitable'

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.

28. Trap: 'a canary makes the program unexploitable'

Trap

The 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.

The fix

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.

29. Entropy and retry cost, together

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.

30. Pointer Authentication

Section

Part 2 · §4.10

31. PAC: a canary for every pointer

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.

QuantityValue
64-bit address space18 exabytes (2^64)
Typical CPU reach~4 TB (~42 address bits)
Unused top bits~22 bits, always 0 — free real estate

32. Hiding the secret in the bits nobody uses

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.

33. Plan first: Worked example: inserting a PAC

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:

  1. Identify the unused bits
  2. Pack the PAC into the top 24 bits
  3. On read, verify then strip

34. Worked example: inserting a PAC

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 bits

Identify 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.

StageTop 24 bitsLow 40 bits (address)
original0x0000000x1234567899
stored with PAC0xABCDEF0x1234567899
after verify+strip0x0000000x1234567899

35. Inspect it line by line: Worked example: inserting a PAC

Error analysis

Annotate

Walk the callouts on Worked example: inserting a PAC. Each one is a place this is easy to get subtly wrong.

  • 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.
  • 0x000000... becomes 0xABCDEF..., giving 0xABCDEF1234567899. The low 40 bits — the real address — are untouched.
  • The CPU checks the top bits hold the right PAC, then zeroes them back to recover 0x0000001234567899. Wrong PAC → crash.

36. A stronger PAC: f(KEY, ADDRESS)

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 designAttacker who changes the address must…
fixed secretreuse the same secret — leak it once, forge anything
per-address f(KEY, ADDR)compute a NEW PAC — impossible without KEY

37. Three ways to subvert PAC

Concept

  1. Find a separate bug that tricks the program into creating a validated pointer for you (the program signs it, so the PAC is correct).
  2. Discover the CPU's secret KEY — then you can sign any address yourself.
  3. Break f — defeat the MAC itself (a cryptographic attack).

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.

38. Something is wrong here: 'PAC stores a copy of the pointer elsewhere'

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.

39. Trap: 'PAC stores a copy of the pointer elsewhere'

Trap

The 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.

The fix

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.

40. Trap: 'keep the old PAC when changing the address'

Trap

The 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.

The fix

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.

41. Why PAC needs the crypto unit

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.

42. Subverting ASLR

Section

Part 3 · §4.12

43. Two ways to beat ASLR — mirroring canaries

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.

44. Term to definition: L15 · Subverting Canaries, Pointer Authentication, ASLR & Combining Mitigations

Matching

Match the pairs

Match each term to the definition this lesson gave it — not the one you would guess from the word.

  • t1. guess the canary
  • t2. leak the canary
  • t3. guess (ASLR)
  • t4. leak (ASLR)
  • d1. Brute-force the canary value — feasible only when entropy is low and each wrong guess is cheap.
  • d2. Use a separate read vulnerability to print the canary, copy it, and write it back within the same run.
  • d3. Brute-force the random offset; feasible only when entropy is low and retries are cheap.
  • d4. Use a read bug to obtain one absolute address, then derive the rest from fixed relative offsets.

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.

45. Plan first: Guessing: only ~16 bits on 32-bit

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:

  1. 16 bits is brute-forceable if retries are cheap
  2. …but infeasible if each crash costs exponentially more
  3. 64-bit has far more entropy

46. Guessing: only ~16 bits on 32-bit

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 harder

16 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.

PlatformASLR entropyPossibilitiesBrute force?
32-bit~16 bits2^16 = 65,536feasible if retries cheap
32-bit~16 bits2^16 = 65,536infeasible if each crash is costly
64-bit>> 16 bitsvery largenot practical

47. Work backwards from the answer: Guessing: only ~16 bits on 32-bit

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.

48. The crucial subtlety: absolute vs relative

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 randomizesWhat it does NOT randomize
stack base (where the frame lands)rip-to-sfp distance (always 4 bytes)
heap basefield offsets within a struct
code/library baseinstruction offsets within the code

49. Fill in: What it does NOT randomize for The crucial subtlety: absolute vs relative

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 randomizesWhat it does NOT randomize
stack base (where the frame lands)rip-to-sfp distance (always 4 bytes)
heap basefield offsets within a struct
code/library baseinstruction offsets within the code

50. One landmark fixes the whole map

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.

51. What has to happen first: Leak the sfp → compute rip = sfp + 4

Ranking

Put in order

Put the moves of Leak the sfp → compute rip = sfp + 4 into the order they have to happen.

  1. Leak one absolute address (the sfp)
  2. Add the fixed offset to reach the rip
  3. Derive further addresses the same way

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.

52. Leak the sfp → compute rip = sfp + 4

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 write

Leak 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.

KnownOffsetDerived
sfp = 0xbf8e2a40 (leaked)+4rip = 0xbf8e2a44
rip = 0xbf8e2a44−12buf = 0xbf8e2a38
any leaked code ptrfixedany other code address

53. Work backwards from the answer: Leak the sfp → compute rip = sfp + 4

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).

54. Something is wrong here: 'ASLR randomizes the rip-to-sfp distance'

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.

55. Trap: 'ASLR randomizes the rip-to-sfp distance'

Trap

The 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.

The fix

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.

56. Something is wrong here: 'one address leak is harmless'

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.

57. Trap: 'one address leak is harmless'

Trap

The 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.

The fix

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.

58. Which of these survive contact with L15 · Subverting Canaries, Pointer…?

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.

Holds up
No real shellcode, no real exploits. The skill being built is reasoning about entropy, address bits, and how many bugs an attacker needs.; The common thread: the canary only catches a contiguous stack write that crosses it. Defeat that assumption and the canary is blind.; 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:
Breaks
Does a stack canary stop a heap overflow?; A format string lets %n write to the rip. Canary present — safe?
sound
These are stated as this lesson states them — each one survives the edge cases L15 · Subverting Canaries, Pointer Authentication, ASLR & Combining Mitigations puts it through.
flawed
Each of these is lifted from a trap in this deck: reasonable-sounding, and wrong in a way that only shows up once you rely on it.

59. Canary and ASLR fail the same two ways

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.

DefenseSecret keptGuess (entropy)Leak (read bug)
stack canarythe canary value24-bit (32) / 56-bit (64)print it, write it back
ASLRthe 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.

60. Combining Mitigations

Section

Part 4 · §4.13

61. Why layering is the point

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.

62. Plan first: Why NX alone or ASLR alone isn't enough

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:

  1. NX alone is beaten by code reuse
  2. ASLR alone is beaten by injection
  3. Together they force a leak

63. Why NX alone or ASLR alone isn't enough

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 first

NX 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.

EnabledInject shellcode?Reuse code?Result
NX onlynoyes (known addr)exploitable
ASLR onlyyes (exec stack)noexploitable
NX + ASLRnono (addr hidden)needs a leak first

64. Inspect it line by line: Why NX alone or ASLR alone isn't enough

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.

  • 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.
  • 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).
  • NX kills injection AND ASLR hides reusable code, so neither solo escape works. The attacker must first leak addresses — a second, separate bug.

65. Two locks that need two different keys

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.

66. Teach it back: Two locks that need two different keys

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.

67. What has to happen first: The attack chain against ASLR + NX + canaries

Ranking

Put in order

Put the moves of The attack chain against ASLR + NX + canaries into the order they have to happen.

  1. Phase 1 — a read bug defeats ASLR and the canary at once
  2. Phase 2 — compute targets from fixed offsets
  3. Phase 3 — a write bug places the ROP chain (defeats NX)

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.

68. The attack chain against ASLR + NX + canaries

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     -> control

Phase 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.

MitigationWhat it blocksExtra step it forces
NXrunning injected shellcodebuild a ROP chain (reuse code)
ASLRknowing code addressesa LEAK (read bug)
stack canarycontiguous overflow to ripleak/guess + write canary back
all threeany single-bug exploitneed a READ bug AND a WRITE bug

69. What each one costs: The attack chain against ASLR + NX + canaries

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.

MitigationWhat it blocksExtra step it forces
NXrunning injected shellcodebuild a ROP chain (reuse code)
ASLRknowing code addressesa LEAK (read bug)
stack canarycontiguous overflow to ripleak/guess + write canary back
all threeany single-bug exploitneed a READ bug AND a WRITE bug

70. Beyond the book: more layers (⊕)

Concept

Defenders don't stop at NX + ASLR + canaries. Further mitigations (⊕, beyond this textbook section) raise the cost again:

71. By analogy: Beyond the book: more layers (⊕)

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:

72. Raising cost, not building a wall

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.

73. Defense & The Arms Race

Section

Part 5 · §4.13 / §4.4

74. Baseline defense recommendation

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.

MitigationStopsDefeated alone by
NXinjected shellcodeROP (code reuse)
ASLR + PIE (⊕)knowing addressesa leak or a guess
stack canarycontiguous stack overflowa leak or low-entropy guess
all togethersingle-bug exploitsneeds leak + write (multiple bugs)

75. Fill in: Defeated alone by for Baseline defense recommendation

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.

MitigationStopsDefeated alone by
NXinjected shellcodeROP (code reuse)
ASLR + PIE (⊕)knowing addressesa leak or a guess
stack canarycontiguous stack overflowa leak or low-entropy guess
all togethersingle-bug exploitsneeds leak + write (multiple bugs)

76. The arms race — and the real fix

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).

77. Break it if you can: The arms race — and the real fix

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).

78. Consolidate

Section

Part 6

79. Without one step: Pattern: reason about a layered defense

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:

  1. For each mitigation, name its blind spot: canary → heap / other locals / %n; ASLR → a leak or a guess; NX → ROP; PAC → a signed-pointer bug, key…
  2. Estimate guessing cost from entropy AND retry cost: 32-bit canary ~24 bits, 32-bit ASLR ~16 bits; a costly-per-crash retry can make even low entropy…
  3. Check what a single leak buys: one absolute address + fixed relative offsets (rip = sfp + 4) reconstructs the section.
  4. Count the bugs the combination forces: ASLR + NX + canary ⇒ a read (leak) AND a write — two vulnerabilities.
  5. Recommend the baseline: NX + ASLR + PIE (⊕) + canaries together, plus CFI / shadow stacks (⊕) where available.
  6. Remember the real fix: a memory-safe language (L11); mitigations only raise the cost.

80. Pattern: reason about a layered defense

Pattern

  1. For each mitigation, name its blind spot: canary → heap / other locals / %n; ASLR → a leak or a guess; NX → ROP; PAC → a signed-pointer bug, key discovery, or breaking f.
  2. Estimate guessing cost from entropy AND retry cost: 32-bit canary ~24 bits, 32-bit ASLR ~16 bits; a costly-per-crash retry can make even low entropy infeasible.
  3. Check what a single leak buys: one absolute address + fixed relative offsets (rip = sfp + 4) reconstructs the section.
  4. Count the bugs the combination forces: ASLR + NX + canary ⇒ a read (leak) AND a write — two vulnerabilities.
  5. Recommend the baseline: NX + ASLR + PIE (⊕) + canaries together, plus CFI / shadow stacks (⊕) where available.
  6. Remember the real fix: a memory-safe language (L11); mitigations only raise the cost.

81. Where does it stop working: Pattern: reason about a layered defense

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:

  1. For each mitigation, name its blind spot: canary → heap / other locals / %n; ASLR → a leak or a guess; NX → ROP; PAC → a signed-pointer bug, key…
  2. Estimate guessing cost from entropy AND retry cost: 32-bit canary ~24 bits, 32-bit ASLR ~16 bits; a costly-per-crash retry can make even low entropy…
  3. Check what a single leak buys: one absolute address + fixed relative offsets (rip = sfp + 4) reconstructs the section.
  4. Count the bugs the combination forces: ASLR + NX + canary ⇒ a read (leak) AND a write — two vulnerabilities.
  5. Recommend the baseline: NX + ASLR + PIE (⊕) + canaries together, plus CFI / shadow stacks (⊕) where available.
  6. Remember the real fix: a memory-safe language (L11); mitigations only raise the cost.

82. Rule out three: Checkpoint — what does the attacker need?

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.

  • A. A single write bug is enough — overflow the buffer and jump to injected shellcode.
  • B. A read (leak) bug to defeat ASLR, AND a write bug to place a ROP chain that defeats NX.
  • C. Nothing extra — ASLR randomizes the rip-to-sfp offset, so the overflow already misses; the bug is unexploitable.
  • D. Only a way to overwrite the canary, since the canary is the only thing standing in the way.

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.

83. Checkpoint — what does the attacker need?

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?

  • A. A single write bug is enough — overflow the buffer and jump to injected shellcode.
  • B. A read (leak) bug to defeat ASLR, AND a write bug to place a ROP chain that defeats NX. (correct)
  • C. Nothing extra — ASLR randomizes the rip-to-sfp offset, so the overflow already misses; the bug is unexploitable.
  • D. Only a way to overwrite the canary, since the canary is the only thing standing in the way.

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.

Why A tempts people
Assumes one write bug suffices, but NX makes injected shellcode non-executable and ASLR hides reusable code, so a single overflow cannot win — you also need a leak.
Why C tempts people
ASLR randomizes absolute section bases, NOT relative offsets; the rip is still sfp + 4, so the overflow does not 'miss' — the bug remains exploitable given a leak.
Why D tempts people
No canary was even mentioned here; the obstacles are NX and ASLR. (And a canary wouldn't stop a %n write anyway.)

84. Misconceptions to drop now

Concept

85. Synthesis — closing the memory-safety unit

Concept

86. Primary sources & where to read more

Concept

87. Connect it up: L15 · Subverting Canaries, Pointer Authentication, ASLR & Combining Mitigations

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.

88. Recap — Lesson 15

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.

IdeaThe one fact
Canary blind spotsheap · other locals · %n (non-contiguous)
Defeat a canaryguess (24-bit / 56-bit) or leak, then write it back
PACMAC f(KEY,ADDR) packed into unused top bits; per-address
ASLR subtletyrandomizes absolute bases, NOT relative offsets (rip = sfp+4)
Combining mitigationsASLR+NX+canary ⇒ need a leak AND a write
Real fixmemory-safe language (L11); mitigations raise cost (§4.4)

Sources

  1. CS 161 Computer Security Textbook §4.9 (Subverting canaries), §4.10 (Pointer authentication), §4.12 (Subverting ASLR), §4.13 (Combining mitigations) — Wagner, Weaver, Kao, Shakir, Law & Ngai, UC Berkeley
  2. Shacham, Page, Pfaff, Goh, Modadugu & Boneh, 'On the Effectiveness of Address-Space Randomization', ACM CCS 2004 — ASLR entropy and brute-force
  3. Qualcomm Technologies, 'Pointer Authentication on ARMv8.3', White Paper (2017)
  4. Abadi, Budiu, Erlingsson & Ligatti, 'Control-Flow Integrity', ACM CCS 2005 — CFI (⊕ beyond the book)

Want this taught 1-on-1? Alexander tutors Computer Security — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108