Read the binary-exploitation lecture as a list of techniques — shellcode, ROP, ret2libc, ret2plt, info leaks — and the mitigations seem to be a separate list of fixes bolted on after. They are not. The techniques and the mitigations are the same list, interleaved: each defence closes exactly one attack primitive corrupting the saved return address, and the very next technique is whatever keeps the same primitive going under the new defence.
The tempting picture: a defence removes the attack
The straightforward reading is that a mitigation removes an attack class outright: stack canaries prevent instruction-pointer hijacking, NX prevents injected shellcode, ASLR prevents hardcoded addresses. On that reading the lecture is a catalogue of wins: each column ticked, each technique retired. The catalogue is real — the mitigations do work — but the relationship is not “closed”. It is “rerouted”.
The mechanism: each defence closes one stage of the saved-return-address overwrite
The technique class that runs through the whole lecture has a single invariant: ret trusts whatever value sits on top of the stack when the function returns. Every attack in the family is instruction pointer hijacking at the core, and every defence closes one step in the path from a buffer overflow to a hijacked rip:
Down the right column: each mitigation (cyan) is met by finding a technique (orange) that does the same saved-return-address overwrite using a different payload channel — canary is met by leaking it back; NX is met by jumping to existing code instead of injected code (ROP); ASLR for libraries is met by leaking one address to compute the base; ASLR for the binary is met by ret2plt; PIE is met by leaking the binary base the same way. The mitigation does not destroy the saved-return-address overwrite; it just changes which channel the overwrite’s payload reaches through.
The consequence: the cascade is machinery, not removal
Read the cascade that way and the lecture’s structure is no longer a list but a zipper. Each technique answers one defence, and the next defence is shaped by the residual channel the previous technique has left:
- stack canary — close: a linear overflow into the saved
ripis detected by the mismatched canary. Residual channel: any primitive that bypasses the canary (a leak of the canary’s value, or an arbitrary-write that overwritesripthrough a pointer and not by walking the stack). - NX — close: injected code on the stack cannot be executed. Residual channel: code the binary already contains — gadgets, libc functions. The technique becomes ROP and its libc subset ret2libc.
- ASLR (libraries only) — close: hardcoded library addresses do not work; the attacker has to find them. Residual channel: the binary itself is not randomised, so its PLT addresses are fixed. The technique becomes ret2plt — call
system@pltinstead ofsystem. - PIE — close: the binary’s segments are randomised too, so
system@pltis no longer a fixed address. Residual channel: leak one address (any of them) to compute the base, since intra-binary offsets stay fixed. The technique becomes info-leak-driven ROP.
At every step the same primitive survives, via a different channel.
The corrected model
Mitigations don’t remove the bug — they rate-limit it. Each accepts a different residual attack class as the cost of closing the easier one, and bypasses systematically undo them by leaking the randomisation or by finding one writable path that skips the canary. The whole cascade shares one invariant: ret will trust whatever sits on the stack when the function returns. The stack frame leaves that value one slot above the local buffer the program forgot to bound, and that — the lesson from the background lecture — is the seam none of these mitigations close. The only defence that removes the primitive at the root is replacing the memory-unsafe language with a memory-safe one. Until then the lecture is a zipper, and the techniques are the next tooth.