Stale Stack Data Leak (Frame Reuse)
- •
What it is
- •
Read leftover bytes from an earlier function call whose stack space is reused. Returning moves the stack pointer but does not erase the old data.
- •
- •
How it works
- •
Some earlier step in the program (commonly, in these harnesses, a "read the flag into memory" setup routine) runs inside its own stack frame, writes sensitive data into a local buffer there, and returns.
- •
The stack pointer retreats, but the bytes it wrote are untouched — they remain exactly where they were.
- •
A later function reuses some of that stack range. Bytes it does not initialize can still contain earlier data.
- •
An unterminated string print may expose the leftovers. A precision such as
%.Nslimits the output to N bytes, so the target must be within that bound. - •
Earlier call (returns, frame conceptually freed but not cleared): [ ... "flag reading" function's locals, including the flag bytes ... ] Later call (same stack region, new function's frame): [ current input buffer ][ untouched leftover from earlier call: flag bytes ] ^-- never written by the current function at all
- •
- •
When to use it
- •
Compare the earlier secret location with the current input buffer to determine the gap.
- •
Confirm the print reaches data from the returned call rather than a current-frame variable.
- •
For a string leak, null bytes along the path stop output. Bridge the gap with non-null input only if doing so leaves the target intact.
- •
A static offset may be derived if both calls use the same verified stack baseline. Otherwise, measure the runtime relationship.
- •
- •
- •
pwntools
- •
offset = secret_addr - buffer_addr p.send(b'A' * offset) # Must leave the secret untouched.
- •
- •
Debugging
- •
disas challenge x/40gx $rsp # Inspect stale bytes before input overwrites them.
- •
- •
- •
Cards
- •
How does this technique differ from over-reading into a neighboring variable in the same stack frame?
- •
The leaked data here was written by an entirely different, already-returned function call — it's leftover from stack space reuse across separate invocations, not a variable belonging to the current function's own frame.
- •
- •
Why must the entire span up to the target be overwritten, not just stop right before it?
- •
Any null byte anywhere in that span — including uninitialized garbage unrelated to the actual target — would stop a string-print early; the whole path has to stay non-null for the print to reach the far end.
- •
- •