SEH overwrite
TL;DR: Stack-based overflows on 32-bit Windows can clobber the on-stack
EXCEPTION_REGISTRATION_RECORDchain; when the overflow triggers an exception, the dispatcher follows the attacker-controlled handler pointer.
What it is
Structured Exception Handling on 32-bit x86 Windows stores a singly linked list of EXCEPTION_REGISTRATION_RECORD { Next, Handler } on the stack, with fs:[0] (the TIB) pointing at the most recent entry. When an exception fires, ntdll!RtlDispatchException walks the chain calling each Handler. A linear stack overflow that reaches past local variables overwrites first the Next pointer and then the Handler pointer of one of these records — and any exception thrown thereafter lands at the attacker’s address.
Preconditions / where it applies
- 32-bit Windows process (x86_64 uses table-based PDATA SEH and is not vulnerable to this technique)
- Stack-overflow primitive that extends past locals into a saved exception record
- Module providing the pivot must not be SafeSEH-protected (see safeseh-bypass) and not in the SEHOP chain validation
- An exception is reachable after the overflow — many overflows naturally trigger one when corrupted frame pointers cause an access violation
Technique
Classic three-step layout:
1
2
3
4
5
[ junk filling locals ... ]
[ ... ]
[ nseh = short jmp 0x06 ] ; next-SEH overwritten with a relative jump
[ seh = &(pop pop ret) gadget ] ; handler overwritten with a ppr in non-SafeSEH module
[ shellcode ... ]
How the chain executes:
- Overflow corrupts
nSEHandSEHpointers on stack - The corrupted frame triggers an access violation
- Dispatcher calls
Handler(record, ...)— attacker’s address: apop pop ret - The two pops discard handler args;
retpops the next stack qword which isrecord— i.e.nSEH - CPU executes
nSEHas code: ajmp $+0x06skipping overSEHitself - Execution lands in the attacker’s shellcode placed after
SEH
mona.py automates pointer hunting:
1
!mona seh -m non_safeseh_dll.dll -cp nonull
For larger payloads, combine with an egghunter when the post-SEH buffer is too small.
Detection and defence
- SafeSEH (safeseh-bypass) — linker-emitted table of valid handlers; dispatcher rejects unknown ones. All modern DLLs ship with SafeSEH; a single non-compliant DLL in-proc breaks this
- SEHOP — runtime validation of the SEH chain integrity (default Server, off-by-default Client). Confirms the chain still terminates at
ntdll!FinalExceptionHandler - Stack cookies (/GS) — placed before saved EBP/return address; trips before the overflow reaches SEH on properly compiled code
- 64-bit ports kill the technique entirely (table-based SEH lives in PE
.pdata/.xdata, not on the stack)
References
- Corelan: SEH exploitation — canonical walkthrough
- Microsoft: SafeSEH / SEHOP — defender reference
- mona.py SEH cookbook — pointer hunting
Related: safeseh-bypass, stack-buffer-overflow, egghunters, mona-py