If the stack-based buffer overflow is where exploit development starts, Structured Exception Handler overflows are where it gets more interesting. The primitive is similar — overflow a buffer, control execution — but the path from crash to shell is a few steps longer, and the constraints are tighter.

This was the first public exploit released under the nullsecurity banner: a SEH buffer overflow in the Eudora Qualcomm IMAP server, commonly known as Worldmail. Unauthenticated, remotely triggerable, and a genuinely satisfying problem to work through.

Here’s the full technical breakdown.

What is an SEH overflow?

A Structured Exception Handler overflow abuses Windows’ own error handling mechanism. When an application encounters an error it can’t handle, Windows looks for an exception handler — a piece of code registered to deal with that specific problem. The SEH chain is where those handlers are stored.

The vulnerability pattern looks like this:

try
accept a socket connection
accept input
do something with the input  <-- too much input - goto except
except
return an error message to the socket <-- crash happens here

When you can overflow the buffer far enough to overwrite the SEH chain with controlled data, you have the same opportunity as any other overflow — with a few extra steps to get there.

“A crash is useless — I want a shell!”

Fair. But a crash caused by your crafted input is only useless if you can’t control it. Control the crash, and instead of the application dying, you can redirect execution to code of your choosing. That’s the goal.

In Worldmail, the crash triggers when the application processes data between braces {. After approximately 769 characters, the SEH chain starts getting overwritten:

{
1
2
...
768
769
NSEH <-- 4 bytes
SEH  <-- 4 bytes
}

NSEH and SEH — which one matters?

NSEH (Next Structured Exception Handler) is meant to point to the next handler in the chain. Overwriting it alone doesn’t get you far.

SEH is what you actually want to control. Overwrite SEH with the address of a useful instruction sequence, and you control where execution goes when the exception fires.

Back in the day, you could point SEH directly at a JMP instruction and land in your shellcode. Windows XP SP2 closed that door — when an exception fires, all registers are zeroed out (00000000). You can’t JMP to a register that contains null.

POP POP RETN — the way through

The solution: POP; POP; RETN — commonly abbreviated PPR.

When the exception fires and registers are zeroed, the stack still has useful structure. Two POP instructions remove the zeroed values from the top of the stack, and RETN transfers execution to whatever’s now at the top — which, with the right setup, is NSEH.

Crucially, a PPR sequence was found inside one of Worldmail’s own included DLLs. That’s important — the DLL address is predictable, which means the SEH overwrite is reliable.

With PPR in place, SEH is overwritten with the PPR address, and execution lands back at NSEH after the exception fires.

The SHORT JMP problem

NSEH is only 4 bytes. That’s not enough room for meaningful shellcode. But it is enough for a SHORT JMP — a two-byte instruction that jumps forward a small number of bytes, over the SEH value, into whatever comes next.

So the structure becomes:


{
1
2
...
768
769
SHORT JMP  <-- NSEH: jumps over SEH
PPR        <-- SEH: triggers PPR, lands at NSEH
SHELLCODE  <-- execution continues here after SHORT JMP
}

The shellcode space problem — enter the egghunter

There’s a catch. Bind shell shellcode is typically over 300 bytes. The space after PPR isn’t nearly enough.

But there is space — 769 bytes of it, sitting in the initial buffer before NSEH. If there’s a way to jump back into that buffer, the shellcode can live there.

Rather than hardcode a backwards jump calculation, an egghunter was used instead — specifically Matt Miller’s egghunt-shellcode implementation. An egghunter is a small piece of code (~30 bytes) that searches process memory for a specific tag (the “egg”), then jumps to whatever follows it. The bind shell shellcode gets placed in the initial buffer, prefixed with the egg marker. The egghunter finds it.

The final structure:

{
1
2
...
EGG        <-- marker the egghunter is looking for
SHELLCODE  <-- bind shell lives here
...
768
769
SHORT JMP  <-- NSEH
PPR        <-- SEH
EGGHUNTER  <-- small, fits in post-SEH space
}

The exploit

The full original exploit code is on Exploit-DB:

https://www.exploit-db.com/exploits/18354

A note on modern Windows compatibility

The exploit has proven reliable — more so than most public code. However, the INT 2E Windows syscall used in the egghunter no longer behaves the same way on current Windows versions. On the latest releases, reliability may be reduced.

An updated version with this addressed is included in the nullsploit exploitation engine — worth using if you’re testing against anything modern.

What makes SEH overflows worth understanding

The techniques here — PPR, NSEH as a trampoline, egghunters for space constraints — appear across a wide range of real-world vulnerabilities. The egghunter pattern in particular is useful any time shellcode space is the limiting factor, regardless of the overflow type.

If you’ve worked through the stack overflow basics and want to understand the next layer of complexity, this is a good place to sit with the concepts. Set up Worldmail in a lab, replicate the crash, and work through each stage yourself. The egghunter paper linked above is worth reading in full — Matt Miller’s explanation of the technique is one of the clearest pieces of exploit development writing out there.