SEH Overwrites, Explained: Your First Structured-Exception Exploit

06/01/2026
design-img

A one-paragraph primer on SEH

Windows lets a program register handlers for things that go wrong, like access violations or a divide by zero. Each handler is a tiny record on the stack made of two four-byte fields, a pointer to the next record and a pointer to the handler function. Chained together those records form the SEH chain, and when an exception fires the operating system walks that chain looking for someone willing to deal with it. That is the whole mechanism, and it is exactly the mechanism we are going to abuse.

Why the plain overflow stops working

In a classic stack overflow you smash the saved return address and take control of EIP when the function returns. But if your oversized input trips an access violation first, which is very common when you spray a big buffer, the function never returns cleanly. Windows raises an exception instead, and at that moment the SEH chain is what is in charge, not the return address you were counting on. So instead of fighting that, you aim at the thing that is actually driving.

The overwrite: nSEH and SEH

Overflow far enough and you write straight over the exception registration record itself. First comes the next-SEH pointer, four bytes, which everyone calls nSEH. Right after it comes the handler pointer, another four bytes, which everyone calls SEH. When the exception fires, the operating system calls whatever address you left in that SEH field. That is your control point. The catch is that you cannot use it the obvious way.

POP POP RET, the move that makes it work

You cannot just point SEH straight at your shellcode, because at exception time the registers are not in your favour and ASLR may hide your buffer anyway. So you point SEH at a POP POP RET gadget sitting inside a loaded module. Here is why that works: when the OS invokes your handler, a pointer to the exception registration record is sitting at a known offset on the stack. Two POPs clear the values above it, and the RET then lands execution right on your nSEH field.

That is why nSEH is not just filler. You make nSEH a short jump, usually a six-byte hop forward over the SEH field, into your shellcode, and you make SEH the address of the POP POP RET. Read the whole thing as a chain of hand-offs: exception fires, handler runs the POP POP RET, execution lands on nSEH, nSEH jumps over SEH into your shellcode. Four steps, and none of them are magic once you have watched it happen in a debugger.

nSEH = b"\xeb\x06\x90\x90"        # jmp +6, then two NOPs
SEH  = pack("<I", 0x10015a2b)     # POP POP RET in a non-SafeSEH module
# ...shellcode follows after the SEH field...

Finding a clean gadget

  • SafeSEH: the linker records a table of legitimate handlers, and an address not in the table is rejected, so you look for a module built without it
  • SEHOP: validates the integrity of the whole chain at runtime by walking it to a known terminator
  • DEP and ASLR: your shellcode location and whether it is executable still matter, so you may chain this with an egghunter or a small ROP stub

Lab, not the wild

This is course and CTF material. The clean path assumes a module built without SafeSEH, which is exactly what OSED-style labs hand you on purpose. On modern, fully mitigated software you will be combining this technique with other primitives rather than firing it on its own, so treat this as the foundation, not the finish line.

SEH exploitation is really one idea. Hijack the exception mechanism, then use the stack layout at the moment the handler runs to bounce back into your own buffer. Once the POP POP RET hand-off clicks, everything else is bad-byte hygiene and finding the right gadget.

Thoughts?

Drop a comment below - it'll show up here once I read it.

loading…

Comments are moderated. Be kind, be specific.

Mushraf Mustafa logo