CS 161, Lesson 14, in 50 slides and code mode. It generalizes ret2libc into ROP gadget chains, works the book's two-gadget example that adds 4 to edx, and covers finding gadgets and chaining them across rets. It then covers stack canaries from section 4.8 - a random word containing a NULL, placed between the locals and the saved registers and checked on return - and the limits of the canary that lead into Lesson 15. The examples are toys running in a sandbox, and it is anchored to textbook sections 4.7 to 4.8.
Subject: Computer Security · 81 slides · code lesson
Open the interactive version of this deck · Homework for this lesson
Title
CS 161 · Lesson 14 of 45
ROP gadgets · chaining rets · add $0x4,%edx from pieces · stack canaries · why a canary detects but never prevents
Objectives
add $0x4, %edx, and account for the clobbered EBX.ret, and how each ret is the glue.Warm-up
Discussion prompt
Before we open L14 · Return-Oriented Programming & Stack Canaries: without looking back, what was the main idea of L13 · Non-Executable Pages & Return-to-libc, 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 13 (54 slides, code mode): W^X / NX pages and the page-table NX bit, why the L8 buffer-injection attack now faults, the code-reuse insight that defeats NX, the 32-bit ret2libc stack layout (fake return address + argument pointer), finding the libc base with ASLR off vs. needing a leak with ASLR on, and chaining libc calls toward ROP.
Concept
Every example here is a toy in a sandbox, for authorized security education — exactly how the textbook teaches it. You learn the attack so you can recognize and defend against it.
We never write real shellcode; payloads stay abstract. The skill being built is reasoning about control flow and frame layout — and understanding why a defense works or doesn't.
Counterexample
Discussion prompt
Every example here is a toy in a sandbox, for authorized security education — exactly how the textbook teaches it. You learn the attack so you can recognize and defend against it.
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:
We never write real shellcode; payloads stay abstract. The skill being built is reasoning about control flow and frame layout — and understanding why a defense works or doesn't.
Section
Part 1 · §4.7
Concept
L13 defeated NX (a non-executable stack) without injecting code: overwrite the rip to return into an existing function like system. You reused code already in memory.
But returning into a whole function is coarse. What if the exact behavior you want isn't a single function lying around? ROP makes the reuse fine-grained.
Analogy
Discussion prompt
Explain Where we left off: ret2libc 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:
L13 defeated NX (a non-executable stack) without injecting code: overwrite the rip to return into an existing function like system. You reused code already in memory.
Concept
Instead of overwriting one return address to jump into one function, overwrite a chain of return addresses, starting at the rip, to execute a series of short snippets called ROP gadgets.
ROP gadget — A short sequence of instructions already in memory that ENDS IN ret. Gadgets are not functions — they need no prologue or epilogue.
You build custom shellcode out of pieces of existing code: each gadget does a tiny bit of work, then its ret hands control to the next gadget.
Explain it
Discussion prompt
Explain Return-oriented programming 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:
You build custom shellcode out of pieces of existing code: each gadget does a tiny bit of work, then its ret hands control to the next gadget.
Intuition
A function is a self-contained unit with a prologue (set up a frame) and an epilogue (tear it down). A gadget is just whatever instructions happen to sit right before a ret — often the tail end of some unrelated function.
You don't call a gadget; you fall into it because the previous ret pointed there. The ret at its end is the only part that matters structurally — it's the hook for the next gadget.
Intuition
A ransom note is built from letters cut out of magazines: you don't write new letters, you assemble a message from fragments that already exist. ROP is the same — you splice tiny code fragments already in the binary into a program you couldn't otherwise write.
The stack you craft is the glue: each return address says 'use this fragment next.' The ret instructions are the scissors-and-tape that string them together.
Concept
NX assumed the attacker had to inject and run new code on the stack. ROP never injects executable code — it only reuses existing, already-executable code — so a non-executable stack doesn't stop it.
Because of ROP, the book notes that NX is no longer a huge issue for a determined attacker. The chain runs entirely from code the loader already marked executable.
Anomaly
Predict first
A student writes this, and it looks reasonable:
What does each address in the ROP chain point to?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: That's ret2libc, not ROP. If gadgets had to be whole functions you could only do what existing functions already do — losing all the fine-grained power.
What does each address in the ROP chain point to?
Why: That's ret2libc, not ROP. If gadgets had to be whole functions you could only do what existing functions already do — losing all the fine-grained power.
Trap
What does each address in the ROP chain point to?
Assume each return address targets a complete function with its own prologue/epilogue
Why: That's ret2libc, not ROP. If gadgets had to be whole functions you could only do what existing functions already do — losing all the fine-grained power.
What does each address in the ROP chain point to?
Each points to a short instruction sequence ENDING IN ret
Why: §4.7: a gadget is a few instructions already in memory that end in ret. No prologue, no epilogue — just useful work plus the ret that advances the chain.
Section
Part 2 · §4.7
Concept
Say there is no single gadget that does add $0x4, %edx directly. The book's example builds that effect out of two gadgets found at fixed addresses in the loaded program.
The two gadgets live in functions foo and bar — but we don't call foo or bar. We jump only to the tails that interest us.
Intuition
An attacker rarely finds a gadget that does exactly the operation wanted. Instead you take the gadgets that exist and compose them — even if the result lands in the wrong register or has a leftover side effect.
So the plan isn't 'find add $0x4,%edx'; it's 'find a path through available gadgets whose net effect is +4 on the value, then clean up the bookkeeping afterward.'
Concept
; gadget in foo:
0x4005a1: mov %edx, %eax
0x4005a3: leave
0x4005a4: ret
; gadget in bar:
0x400604: add $0x4, %eax
0x400608: pop %ebx
0x40060a: leave
0x40060b: retGadget 1 (in foo) copies EDX → EAX. Gadget 2 (in bar) does EAX += 4. Run them in order and EAX ends up holding edx + 4.
| Gadget | Entry address | Useful effect |
|---|---|---|
| foo's tail | 0x4005a1 | mov %edx,%eax (EDX → EAX) |
| bar's tail | 0x400604 | add $0x4,%eax (EAX += 4) |
| both end in | ret | pops next address, advances chain |
Comparison
Comparison matrix
From The two gadgets in loaded memory: refill the Entry address column from what you know. The rest of the table is as it appeared.
| Gadget | Entry address | Useful effect |
|---|---|---|
| foo's tail | 0x4005a1 | mov %edx,%eax (EDX → EAX) |
| bar's tail | 0x400604 | add $0x4,%eax (EAX += 4) |
| both end in | ret | pops next address, advances chain |
Ranking
Put in order
Put the moves of Building the chain on the stack into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. The overwritten rip sends control into foo's tail.
Worked example
Overflow the buffer so the rip = 0x4005a1 (gadget 1). Above it, place 0x400604 (gadget 2), and above that a word for pop %ebx to consume.
; stack the attacker writes, low -> high (rip first):
rip = 0x004005a1 ; -> gadget 1 (foo's tail)
next ret = 0x00400604 ; -> gadget 2 (bar's tail)
ebx word = 0x........ ; consumed by 'pop %ebx' in barOn return, ret pops 0x4005a1 into eip
Why: The overwritten rip sends control into foo's tail. mov %edx,%eax runs: EAX now holds the old EDX.
foo's gadget ends in leave; ret pops 0x400604
Why: The next stack word is gadget 2's address. Control jumps into bar's tail. add $0x4,%eax runs: EAX is now EDX + 4.
bar's gadget runs pop %ebx, consuming the next stack word
Why: pop %ebx reads one word off the stack into EBX — so we had to place a filler word there. EBX is now clobbered; the chain must account for it.
| Stack word (low→high) | Value | Effect when reached |
|---|---|---|
| rip | 0x004005a1 | jump to foo gadget: EAX = EDX |
| next ret | 0x00400604 | jump to bar gadget: EAX += 4 |
| popped into ebx | filler word | consumed by pop %ebx; EBX clobbered |
Trade off
Comparison matrix
From Building the chain on the stack: every row here is a choice with a cost. Fill the Value column, then say which row you would actually pick and what you give up for it.
| Stack word (low→high) | Value | Effect when reached |
|---|---|---|
| rip | 0x004005a1 | jump to foo gadget: EAX = EDX |
| next ret | 0x00400604 | jump to bar gadget: EAX += 4 |
| popped into ebx | filler word | consumed by pop %ebx; EBX clobbered |
Concept
The result ended up in EAX, not EDX — so to truly mimic add $0x4, %edx you'd need another gadget to move EAX back into EDX. The pieces rarely fit perfectly.
And pop %ebx consumed a stack word and clobbered EBX. Both side effects must be planned around: the popped word has to be placed on the stack, and EBX's old value is gone.
Tracking all of this by hand is tedious — which is exactly why ROP compilers exist.
Concept
ROP compiler — A tool that searches a binary for gadgets and automatically chains them — handling register bookkeeping and consumed stack words — to produce a desired computation.
You describe the effect you want (e.g. 'set these registers, call this'); the compiler finds gadgets and emits the stack layout. The tedious EAX-vs-EDX and clobbered-EBX accounting is automated.
Definition probe
Sort into buckets
Every line below is part of the definition of ROP gadget or of ROP compiler — one or the other, never both. Put each where it belongs.
ret.; they need no prologue or epilogue.ret. Gadgets are not functions — they need no prologue or epilogue.Intuition
Two gadgets already forced us to track a wrong-register result and a consumed stack word. A real payload chains dozens of gadgets — the bookkeeping explodes far past what a human wants to do by hand.
So tooling automates it, the same way a compiler turns high-level code into machine instructions. That automation is precisely why ROP went from a research curiosity to a practical, routine technique — and why NX alone stopped being enough.
Anomaly
Predict first
A student writes this, and it looks reasonable:
Laying out the chain for the two gadgets above.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Forgets that bar's gadget runs pop %ebx, which reads a stack word.
Laying out the chain for the two gadgets above.
Why: Forgets that bar's gadget runs pop %ebx, which reads a stack word. With nothing placed there, it pops the wrong bytes and the chain derails after add.
Trap
Laying out the chain for the two gadgets above.
Place 0x4005a1 then 0x400604 and stop
Why: Forgets that bar's gadget runs pop %ebx, which reads a stack word. With nothing placed there, it pops the wrong bytes and the chain derails after add.
Laying out the chain for the two gadgets above.
Place 0x4005a1, then 0x400604, then a filler word for the pop
Why: §4.7: every pop in a gadget consumes a stack word you must supply. Account for the popped word (and the clobbered EBX) or the chain misaligns.
Section
Part 3 · §4.7
Intuition
A program linked against many libraries has an enormous amount of machine code — and therefore an enormous number of byte sequences that happen to end in ret.
With enough gadgets you can compute anything — the gadget set is effectively Turing-complete. The more code is loaded, the richer the attacker's instruction set becomes.
Concept
gadget finder — A tool that scans a binary for byte sequences that end in a ret, cataloging each as a usable gadget along with the instructions it performs.
It looks for useful tails: on 64-bit, a classic is pop rdi; ret (load an argument register, then return); on 32-bit, pop %ebx; ret. Anything ending in ret that does something useful is a candidate.
| Architecture | Example gadget | Use |
|---|---|---|
| 64-bit | pop rdi; ret | load first arg register, then advance |
| 32-bit | pop %ebx; ret | load EBX from stack, then advance |
| either | ...; ret | any useful tail ending in ret |
Concept
Recall ret pops the next stack word into eip and jumps there. In a normal program that returns to the caller; in a ROP chain it advances to the next gadget.
So the chain runs itself: each gadget does its work, hits ret, pops the next address the attacker planted, and continues. The stack is the program; the rets are the instruction pointer stepping through it.
Sorting
Sort into buckets
These are the pieces of L14 · Return-Oriented Programming & Stack Canaries, out of order. Put each one back under the part of the lesson it belongs to.
Intuition
Normally the code is the program and the stack just holds data. In ROP that flips: the attacker's stack of addresses is the program, and the existing code is a fixed instruction set you index into.
Each ret is like a tiny fetch-decode-execute step: fetch the next address from the stack, jump there, run that gadget, repeat. The 'program counter' is really esp marching up the planted chain.
Step zero
Discussion prompt
Tracing eip and esp across two rets — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: Function returns: ret pops 0x1000, esp += 4, eip = 0x1000
Answer:
Worked example
Two trivial gadgets: gadget A at 0x1000 ends in ret; gadget B at 0x2000 ends in ret. The attacker's stack holds A's address at the rip, then B's address just above it.
; attacker stack (low -> high):
esp -> 0x00001000 ; A's address (the overwritten rip)
0x00002000 ; B's address
........ ; whatever B's ret pops nextFunction returns: ret pops 0x1000, esp += 4, eip = 0x1000
Why: The overwritten rip is popped into eip; esp now points at B's address. Control runs gadget A.
Gadget A finishes with ret: pops 0x2000, esp += 4, eip = 0x2000
Why: A's ret reads the word esp points at (B's address), advances esp, and jumps. Control runs gadget B.
Gadget B's ret pops the next word and continues
Why: The same mechanism repeats: each ret consumes one address and steps to the next gadget. esp marches up the chain.
| Step | esp points at | eip after ret |
|---|---|---|
| start (return) | A's address (0x1000) | 0x1000 — gadget A |
| after A's ret | B's address (0x2000) | 0x2000 — gadget B |
| after B's ret | next chain word | next gadget |
Comparison
Comparison matrix
From Tracing eip and esp across two rets: refill the eip after ret column from what you know. The rest of the table is as it appeared.
| Step | esp points at | eip after ret |
|---|---|---|
| start (return) | A's address (0x1000) | 0x1000 — gadget A |
| after A's ret | B's address (0x2000) | 0x2000 — gadget B |
| after B's ret | next chain word | next gadget |
Anomaly
Predict first
A student writes this, and it looks reasonable:
How does the CPU end up running the ROP gadgets?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Then a non-executable stack (NX) would block it — and ROP's whole point is to defeat NX.
How does the CPU end up running the ROP gadgets?
Why: Then a non-executable stack (NX) would block it — and ROP's whole point is to defeat NX. Injected stack code is the old shellcode model, not ROP.
Trap
How does the CPU end up running the ROP gadgets?
Assume the gadgets' machine code is injected onto the stack
Why: Then a non-executable stack (NX) would block it — and ROP's whole point is to defeat NX. Injected stack code is the old shellcode model, not ROP.
How does the CPU end up running the ROP gadgets?
Only ADDRESSES are written to the stack; the code already lives in executable memory
Why: §4.7: the attacker writes a chain of return addresses. The gadget instructions are existing, already-executable code — so NX never applies to them.
Section
Part 4 · §4.8
Intuition
Miners once carried a caged canary underground. The bird is more sensitive to toxic gas than people are: if the canary dies, you evacuate — it is a sacrificial early warning.
A stack canary plays the same role: a sacrificial value placed where an overflow would pass through. If it's been disturbed on the way to the rip, the program knows it's been attacked and bails out.
Concept
stack canary — A known random value the compiler places between a function's local variables and its saved registers (sfp and rip), checked before the function returns.
It sits directly above the locals and directly below the sfp and rip. The function never uses it for computation — it exists only to be checked on the way out.
Modern compilers add it automatically, and the runtime overhead is negligible.
Matching
Match the pairs
Match each term to the definition this lesson gave it — not the one you would guess from the word.
ret. Gadgets are not functions — they need no prologue or epilogue.ret, cataloging each as a usable gadget along with the instructions it performs.Why: These are the working definitions of ROP gadget, ROP compiler, gadget finder, stack canary as L14 · Return-Oriented Programming & Stack Canaries uses them. Pairing them correctly is the test of whether you could state each one with the slide switched off.
Picture it
Figure (svg): Stack frame high to low: rip at ebp+4, sfp at ebp+0, then the canary directly above the locals, then buf. A contiguous overflow from buf upward must cross the canary before reaching the sfp and rip.
Discussion prompt
Read the picture before the words. What is this showing, and what is the one thing it is built to make obvious? Commit to an answer, then read on.
Hint: Name the parts, then say what changes between them — and if nothing changes, say what is being held still.
Answer:
From high to low addresses: the rip, the sfp, then the canary, then the locals (buf). A contiguous overflow climbing from buf toward the rip must pass through the canary.
Concept
From high to low addresses: the rip, the sfp, then the canary, then the locals (buf). A contiguous overflow climbing from buf toward the rip must pass through the canary.
Figure (svg): Stack frame high to low: rip at ebp+4, sfp at ebp+0, then the canary directly above the locals, then buf. A contiguous overflow from buf upward must cross the canary before reaching the sfp and rip.
| Slot (high→low) | Size | Role |
|---|---|---|
| rip (saved return addr) | 4 B | the target an attacker wants |
| sfp (saved ebp) | 4 B | saved frame pointer |
| canary | 4 B | sacrificial guard, checked on return |
| buf[0..7] | 8 B | locals — attacker-fillable |
Discrimination
Sort into buckets
Sort these by Size, from memory, without looking back at Where the canary lives in the frame. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.
Concept
Intuition
If the canary were a fixed constant, the attacker would just include that constant in the overflow and overwrite the canary with its correct value — the check would pass and the defense would be useless.
Generating it randomly at runtime, fresh each run, means the attacker can't bake it into the payload. To get past it they must learn this run's value — which is exactly what the L15 leak/guess attacks try to do.
Intuition
Many overflows come from string copies like strcpy, which stop at the first NULL byte. If the canary's first byte is 0x00, an attacker trying to overwrite the canary with a copied string can't get past that null — the copy halts.
The cost is a little entropy: one byte is fixed at zero, so a 32-bit canary really has only about 24 bits an attacker must guess. The trade is worth it — it blocks the most common overflow primitive outright.
Ranking
Put in order
Put the moves of An overflow trips the canary into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. buf[0..7] = the eight 'A's — still in bounds, canary untouched so far.
Worked example
The attacker tries the L8 trick: 8 bytes to fill buf, then more to reach the rip. But now the canary sits in between.
input = "AAAAAAAA" "BBBB" "CCCC" "DDDD"
; buf[0..7] canary sfp rip
; the copy must overwrite the canary (BBBB) to reach sfp/ripFirst 8 bytes fill buf
Why: buf[0..7] = the eight 'A's — still in bounds, canary untouched so far.
Next 4 bytes overwrite the canary
Why: To reach the sfp and rip the contiguous write MUST cross the canary slot — replacing the random value with 'BBBB'.
On return, the function compares the canary; it no longer matches
Why: The stored 'BBBB' differs from the random value the function saved. The mismatch is detected.
The program crashes BEFORE control reaches the rip
Why: §4.8: the check runs before ret. Because the canary changed, the program aborts — the overwritten rip is never used, so the hijack fails.
| Input bytes | Lands on | Result |
|---|---|---|
| AAAAAAAA (0–7) | buf[0..7] | in bounds |
| BBBB (8–11) | canary | corrupted — mismatch on return |
| CCCC / DDDD (12–19) | sfp / rip | overwritten, but never reached: crash first |
Trade off
Comparison matrix
From An overflow trips the canary: every row here is a choice with a cost. Fill the Lands on column, then say which row you would actually pick and what you give up for it.
| Input bytes | Lands on | Result |
|---|---|---|
| AAAAAAAA (0–7) | buf[0..7] | in bounds |
| BBBB (8–11) | canary | corrupted — mismatch on return |
| CCCC / DDDD (12–19) | sfp / rip | overwritten, but never reached: crash first |
Anomaly
Predict first
A student writes this, and it looks reasonable:
Believe the canary blocks the out-of-bounds write itself
It is wrong. Say what breaks — and say it before you turn the page.
Correct: It doesn't. The overflow already happened — buf, the canary, the sfp, and the rip are all overwritten in memory.
When does the canary act?
Why: It doesn't. The overflow already happened — buf, the canary, the sfp, and the rip are all overwritten in memory. Nothing stopped the write.
Trap
When does the canary act?
Believe the canary blocks the out-of-bounds write itself
Why: It doesn't. The overflow already happened — buf, the canary, the sfp, and the rip are all overwritten in memory. Nothing stopped the write.
When does the canary act?
The canary is DETECTED on return, after the write
Why: §4.8: the corruption occurs, then on the way out the function checks the canary and crashes. It prevents the hijack (the rip is never used), not the overflow.
Section
Part 5 · §4.8 → L15
Concept
A canary protects exactly the contiguous-overflow-to-rip path: a write that climbs from the locals, through the canary, into the saved registers. That is real protection — but it is the only path it covers.
Anything that reaches sensitive memory without crossing the canary sails right past it.
Intuition
Picture the canary as a tripwire stretched across one corridor — the contiguous path from the locals up to the saved rip. Anyone walking that corridor trips it.
But the building has other ways in: a window onto the rip (a %n write), a side room of locals below the wire (an authenticated flag), and an entirely separate building (the heap). The tripwire guards none of those.
Explain it
Discussion prompt
Explain A tripwire across one hallway 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:
Picture the canary as a tripwire stretched across one corridor — the contiguous path from the locals up to the saved rip. Anyone walking that corridor trips it.
Ranking
Put in order
Put the moves of Overwriting an adjacent local, not the rip into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. The write stops below the canary — it never reaches or disturbs the canary slot.
Worked example
Suppose a frame has char buf[8] and, just above it but below the canary, an int authenticated flag. The attacker doesn't need the rip at all.
; frame (high -> low): rip | sfp | canary | authenticated | buf
input = "AAAAAAAA" "\x01\x00\x00\x00"
; buf[0..7] authenticated = 1 (canary never touched)Overflow buf just enough to overwrite authenticated
Why: The write stops below the canary — it never reaches or disturbs the canary slot.
Set authenticated to a nonzero value
Why: The logic now treats the attacker as logged in, even though they never crossed the canary.
On return, the canary still matches — no crash
Why: The canary was untouched, so the check passes. The attack succeeded without ever going near the rip.
| Target overwritten | Crosses canary? | Canary catches it? |
|---|---|---|
| the rip (contiguous) | yes | yes — crash |
| adjacent local below canary | no | no — undetected |
| heap data | n/a (not on stack) | no — undetected |
Comparison
Comparison matrix
From Overwriting an adjacent local, not the rip: refill the Crosses canary? column from what you know. The rest of the table is as it appeared.
| Target overwritten | Crosses canary? | Canary catches it? |
|---|---|---|
| the rip (contiguous) | yes | yes — crash |
| adjacent local below canary | no | no — undetected |
| heap data | n/a (not on stack) | no — undetected |
Concept
authenticated flag below the canary, reached without crossing it.%n writes straight to the rip, jumping AROUND the canary entirely.Intuition
A buffer overflow is contiguous: it writes byte after byte, so it can't skip the canary on its way up. A format-string %n is arbitrary: it writes a chosen value to a chosen address.
So %n can drop a value directly onto the rip's 4 bytes without touching the canary slot at all. The canary is a tripwire across one hallway; %n comes through the window.
Analogy
Discussion prompt
Explain Why %n walks around the canary 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:
A buffer overflow is contiguous: it writes byte after byte, so it can't skip the canary on its way up. A format-string %n is arbitrary: it writes a chosen value to a chosen address.
Anomaly
Predict first
A student writes this, and it looks reasonable:
Does a stack canary defend against a %n write to the rip?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: It only guards the contiguous path.
Does a stack canary defend against a %n write to the rip?
Why: It only guards the contiguous path. %n writes to an arbitrary address directly, never crossing the canary, so the canary check still passes.
Trap
Does a stack canary defend against a %n write to the rip?
Assume the canary protects the rip from all writes
Why: It only guards the contiguous path. %n writes to an arbitrary address directly, never crossing the canary, so the canary check still passes.
Does a stack canary defend against a %n write to the rip?
No — %n writes around the canary to the rip directly
Why: §4.8: canaries only catch contiguous overflows. A non-contiguous write (format string) hits the rip without disturbing the canary — undetected.
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.
ret hands control to the next gadget.; The stack you craft is the glue: each return address says 'use this fragment next.' The ret instructions are the scissors-and-tape that string them together.; Because of ROP, the book notes that NX is no longer a huge issue for a determined attacker. The chain runs entirely from code the loader already marked executable.Concept
Even on the contiguous path, a canary can be defeated. The book previews two routes that L15 develops:
%x) to read the canary's value, then write it back in place so the check still passes.Both turn the canary from a wall into a speed bump — raising the bar, not closing the door.
Counterexample
Discussion prompt
Even on the contiguous path, a canary can be defeated. The book previews two routes that L15 develops:
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:
Both turn the canary from a wall into a speed bump — raising the bar, not closing the door.
Section
Part 6
Constraint
Discussion prompt
Run Pattern: reason about ROP and canaries with this step confiscated:
ROP defeats NX because it reuses existing executable code; nothing is injected onto the stack.
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:
ret), not whole functions.ret is the glue: it pops the next address the attacker planted and jumps there, stepping through the chain.pop consumes a stack word and clobbers a register — or use a ROP compiler.%n writes; they can be guessed (~2^24) or leaked.Pattern
ret), not whole functions.ret is the glue: it pops the next address the attacker planted and jumps there, stepping through the chain.pop consumes a stack word and clobbers a register — or use a ROP compiler.%n writes; they can be guessed (~2^24) or leaked.Edge cases
Discussion prompt
Pattern: reason about ROP and canaries 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:
ret), not whole functions.ret is the glue: it pops the next address the attacker planted and jumps there, stepping through the chain.pop consumes a stack word and clobbers a register — or use a ROP compiler.%n writes; they can be guessed (~2^24) or leaked.Elimination
Eliminate the wrong options
In a ROP chain, what advances execution from one gadget to the next?
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.
call instruction at the start of each gadget that invokes the next one.ret at the end of each gadget, which pops the next address off the stack into eip and jumps there.jmp hard-coded into every gadget that points directly at the following gadget's address.Survives elimination: B
Why: Each gadget ends in ret. A ret pops the next stack word into eip and jumps there — so the attacker-planted addresses on the stack drive the chain, one gadget per ret. The ret is the glue that strings gadgets together.
Check
A ROP chain has run gadget A and now needs to reach gadget B, whose address the attacker placed on the stack just above A's. Think about the mechanism before clicking.
Check your understanding
In a ROP chain, what advances execution from one gadget to the next?
call instruction at the start of each gadget that invokes the next one.ret at the end of each gadget, which pops the next address off the stack into eip and jumps there. (correct)jmp hard-coded into every gadget that points directly at the following gadget's address.Answer: B
Why: Each gadget ends in ret. A ret pops the next stack word into eip and jumps there — so the attacker-planted addresses on the stack drive the chain, one gadget per ret. The ret is the glue that strings gadgets together.
call. ROP relies on ret, which is exactly why it's called return-oriented.ret.ret, control goes to whatever address the attacker placed on the stack — which can be anywhere in loaded code.Concept
ret, with no prologue or epilogue.%n writes around the canary to the rip directly.Concept
Concept
gcc -m32 -O0 (canary ON by default) and again with -fno-stack-protector, then in gdb watch the canary slot above the locals get clobbered and the __stack_chk_fail abort fire.Connect it up
Draw it
One page, no notation unless you need it: draw how these connect — From ret2libc to ROP · The Two-Gadget Example · Finding & Chaining Gadgets · Stack Canaries · What Canaries Miss · Consolidate. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.
Recap
You can explain how ROP chains gadgets to generalize ret2libc and defeat NX, trace the two-gadget add-4 example (and its EAX/EBX bookkeeping), describe the gadget finder and the ret-as-glue mechanism, state the exact stack-canary design, and list precisely what a canary detects and what it misses.
| Idea | The one fact |
|---|---|
| ROP gadget | instruction tail ending in ret; not a function |
| What advances the chain | each gadget's ret pops the next address |
| Why ROP beats NX | reuses existing code; injects no stack code |
| Stack canary | random NULL-containing word between locals and sfp/rip |
| Canary timing | DETECTS a contiguous overflow on return; never prevents the write |
| Canary gaps | heap, adjacent locals, %n writes; guess (~2^24) or leak |
Want this taught 1-on-1? Alexander tutors Computer Security — $55/session, free consultation.