Stack buffer overflow
TL;DR: Write past a fixed-size stack array into the saved return address (or saved EBP/RBP, or an SEH record) and on function return the CPU jumps wherever the attacker wrote.
What it is
The stack stores per-call frames: locals, saved frame pointer, saved return address (and on 32-bit Windows, also the SEH chain). A function copying attacker-controlled input into a local buffer without bounds-checking — strcpy, gets, unchecked memcpy(local, src, attacker_len), vulnerable sprintf — extends past the buffer into adjacent saved state. When the function executes ret, it pops the corrupted address into RIP/EIP and the attacker controls execution.
Preconditions / where it applies
- A function with a fixed-size stack buffer and unbounded input copy
- Either no stack cookie, or a leak / bypass for it
- Either DEP off and a known stack address (legacy), or DEP on with dep-bypass via rop-chains
- Either ASLR off, or a leak (see aslr-bypass)
Technique
Classical workflow:
- Trigger and crash. Send a long pattern (
pattern_create.rb 5000orcyclic 5000) — observe RIP/EIP in WinDbg. - Find offset.
pattern_offsetorcyclic_findagainst the crashed RIP value → exact distance from start of input to saved return address. - Enumerate bad characters. Send
\x01\x02..\xffand see which bytes get stripped/transformed by the receive path — see bad-character-handling. - Locate a stable trampoline. Without ASLR/DEP:
jmp esp/call espgadget in a loaded module via!mona jmp -r esp. - Build the payload.
[ pad to offset ] + [ trampoline addr ] + [ NOP sled ] + [ shellcode ]. - With DEP+ASLR. Replace step 5 with a ROP chain to
VirtualProtect(see rop-chains, dep-bypass). Need a non-ASLR module or info-leak.
Minimal POC (32-bit, DEP/ASLR off):
1
2
3
4
5
6
7
8
9
import socket, struct
offset = 1024
ret = struct.pack('<I', 0x10101010) # jmp esp in some module
nops = b'\x90' * 16
shell = b'\xcc' * 4 # int3 placeholder
payload = b'A'*offset + ret + nops + shell
s = socket.create_connection(('target', 9999))
s.sendall(b'OVERFLOW ' + payload + b'\r\n')
When the buffer is small, drop an egghunter and stash the real payload elsewhere. When you can only overwrite SEH, see seh-overwrite.
Detection and defence
/GSstack cookie (Visual Studio) — random canary placed before saved EBP/return;__security_check_cookieaborts on mismatch- DEP / NX makes shellcode-on-stack non-executable
- ASLR randomises module bases — no static
jmp esp - CFG (control-flow-guard) + CET shadow stack (cet-shadow-stack) break ret-based and indirect-call gadget reuse
- Compile with
/GS /guard:cf /CETCOMPAT /DYNAMICBASE /HIGHENTROPYVA /NXCOMPAT; statically replace dangerous APIs with bounded variants (strncpy_s,StringCchCopy)
References
- Aleph One: Smashing the Stack for Fun and Profit (Phrack 49) — historical foundation
- Corelan: Exploit writing tutorial part 1 — Windows workflow
- HackTricks: Stack overflow — modern overview
Related: seh-overwrite, rop-chains, dep-bypass, mona-py