CALL leaves a bookmark. RET reads it.
A normal near CALL on our x86 model saves the address of the instruction after CALL and transfers control to the callee. It saves the next instruction, not the address of CALL itself. RET later consumes that saved address and resumes there.
The return address is not the function’s return value. A function can return an integer in EAX in this model while RET independently chooses where execution resumes. Confusing those two meanings of “return” makes debugging unnecessarily mysterious.
In historical 32-bit x86 cdecl, arguments are commonly pushed right-to-left and the caller reclaims their stack space after the call. Our stepper uses a no-argument function so you can focus on the frame. This convention is not universal: other ABIs use registers and different cleanup rules.
/* A tiny function to reason about, not an exploit. */
int greet(void) {
char label[16];
label[0] = 'A';
return 7;
}The simulator chooses an unoptimized-looking frame so you can see its pieces. A real compiler may simplify this function dramatically.
You are the CPU. Take the next step.
This optional frame model becomes interactive with JavaScript. The lesson, explanations and checkpoint answer remain readable without it.
Translate the prologue instead of memorizing it.
- push ebp
- Save the caller’s frame pointer on the stack.
- mov ebp, esp
- Give this frame a stable reference point.
- sub esp, 16
- Reserve sixteen bytes of local stack storage in this chosen layout.
- leave
- In this simple frame, restore ESP from EBP, then pop the saved EBP.
- ret
- Pop the saved instruction address into the instruction pointer.
With this EBP-based arrangement, saved EBP is at [EBP], the return address at [EBP+4] and a first stack-passed argument commonly at [EBP+8]. Locals are below EBP. These are consequences of this specific calling sequence, not offsets to paste into an unrelated binary.
Offsets come from evidence, not folklore.
The paper alters a saved return location so execution skips an assignment. That demonstrates control-flow manipulation; it does not prove that adding the same number works for another build. Instruction lengths, padding, frame layout and compiler decisions all matter.
In a permitted CTF or your own program, use a debugger to relate source, disassembly and memory. GDB’s disassemble shows instructions; info registers shows register state; x/16bx ADDRESS examines sixteen bytes at a known address. A debugger command observes the target’s state; it does not validate an assumed layout.
Record the architecture, compiler options, input length and observed frame. When the evidence changes, update the diagram. Do not force the binary to fit the drawing.
Your turn. Call the outcome.
What does the saved return address identify?
Read the explanation
It is the execution bookmark after CALL. Return values and local buffer addresses are different pieces of state.
Sources & what we changed
Based on the supplied text of Aleph One’s Smashing the Stack for Fun and Profit, Phrack 49, file 14, 8 November 1996. Original author credit remains with Aleph One. This independent OverWatch Labs explanation uses new prose, synthetic models and modern defensive notes; it is not endorsed by Phrack or the original author.
This lesson maps to: The Stack Region; example1.c; example3.c and its disassembly. Historical examples are described, not represented as current product vulnerabilities. We correct byte-addressability, array padding, terminator counting and the supplied return-address arithmetic rather than reproducing those errors.
Original shell-spawning byte arrays and platform-specific exploit drivers are replaced with symbolic models. The causal concepts are preserved; browser outcomes are not native exploitation evidence. Modern controls are editorial additions, not claims made by the 1996 paper.