TECHNICAL WRITEUP

Building SEH Overflow Exploits

My journey learning how to develop exploits for vulnerable programs has been eye-opening. Forcing yourself to understand the smaller moving parts - binary, CPU registers, Windows structures, and APIs - is a lot to take in. The difference between a standard buffer overflow and an SEH overflow can feel especially intimidating.

If you do not already know C, as was the case for me, you end up learning new concepts, taking detours, and working through one rabbit hole after another. The goal of this post is to make SEH overflows easier to understand for newer readers, including those who may not come from a deeply technical background.

I will start by explaining what an SEH overflow is, how to exploit it, then I will briefly cover why exploit portability matters across different target systems.

What is an SEH overflow?

Windows uses SEH to manage exceptions that occur while a program is running, and stands for “Structured Exception Handling”.

At a high level, an SEH overflow happens when an attacker can overwrite both a buffer and the SEH handler information stored alongside it.

If you have written code before, you can think of it like try/except logic: something goes wrong, the program attempts to handle it, and execution may continue instead of stopping immediately.

Examples of exceptions include dividing by zero or supplying unexpected input that the program does not know how to process. It’s better to print an error than to crash the whole program.

The image below shows a simplified version of the concept. An attacker first sends input that fills the Normal Buffer Space and continues into the Extra Bytes region. They then use 8 carefully chosen bytes to overwrite the SEH record. Finally, although it is not shown here, they place shellcode at a location they can reach after redirecting execution through that overwritten SEH record, allowing them to gain unauthorized access.

seh_concept_diagram.png

This is the core of the exploit, so it is worth understanding before moving on.

For readers who are newer to this topic, shellcode is machine-level data, often written as hexadecimal bytes, that a processor can execute as instructions. In practice, an attacker can write assembly code and then convert it into hex bytes using tools such as keystone-engine.

If you want a quick reference for common ASCII and hexadecimal values, this chart is useful: https://ss64.com/ascii.html

How Windows structures and handles SEH

This part is easier to follow once you see how Windows organizes the relevant structures in memory. The Vergilius Project is especially helpful because it documents Windows kernel structures and their offsets clearly.

Starting with the TEB (Thread Environment Block), the _NT_TIB structure contains a pointer to an _EXCEPTION_REGISTRATION_RECORD. That structure contains two important fields: the Next pointer and the Handler pointer.

teb_struct

nt_tib_struct.png

exception_registration_record_struct.png

It’s a mouthful and can be difficult to read, but it makes much more sense if you understand how C structures work, and if you can click around on the Vergilius project yourself to see where each pointer leads you to.

_EXCEPTION_REGISTRATION_RECORD

_EXCEPTION_REGISTRATION_RECORD entries form a linked list. In practice, Windows calls the handler to process the exception. If that handler cannot resolve the error, Windows follows the Next pointer and tries the next handler in the chain. This continues until there are no more handlers left to try, at which point the program crashes.

From an exploitation standpoint, the goal is to overwrite the Handler and Next so it points to instructions that eventually transfer execution to our shellcode. To do that reliably, we need to find a static location in memory inside the target application or one of its modules that contains instructions we can reuse.

For simplicity, I am not covering how to locate these instructions in memory here. If you want to explore that process, I have included references below for further learning.

P/P/R Instruction

So what instructions do we need, and why?

When the handler is called on x86 systems, Windows provides four exception handler arguments that are pushed onto the stack:

typedef EXCEPTION_DISPOSITION _except_handler (*PEXCEPTION_ROUTINE) (
    IN PEXCEPTION_RECORD ExceptionRecord,
    IN VOID EstablisherFrame,
    IN OUT PCONTEXT ContextRecord,
    IN OUT PDISPATCHER_CONTEXT DispatcherContext
);

The most important one for this discussion is EstablisherFrame, because it points back to the current exception registration record’s Next.

In a classic SEH exploit, we often look for a pop pop ret sequence. The two pop instructions discard stack values placed there for the exception handler call, and the ret then transfers execution using the EstablisherFrame pointer. Because EstablisherFrame points to the current exception registration record, execution is redirected back into the overwritten SEH record, starting at the Next field.

ESP -> [ Return address to the OS exception dispatcher ] <- first pop  
       [ ExceptionRecord ]                               <- second pop  
       [ EstablisherFrame ]                              <- ret uses this  
       [ ContextRecord ]  
       [ DispatcherContext ]

So, what do we overwrite Next with?

A common approach is to overwrite the Next pointer with 06eb9090, which is jmp +6 followed by two NOPs. This causes execution to jump over the remaining bytes of the SEH record and land in shellcode placed immediately after it.

So, the overwritten SEH record should look like:

Next       ; jmp +6 
Handler    ; Memory address to ppr instruction, we jump over this
<SHELLCODE STARTS HERE>

Finishing Touches

The final step is getting execution cleanly into the payload.

In one of my public exploits, disk_pulse_seh_v10.0.12.py, I use an add sp, 0xf5c instruction followed by jmp esp so that EIP reaches the shellcode. It works, but it is not a strong long-term approach because it depends on a specific offset. That hurts exploit portability. We will not always know exactly where the shellcode will land across systems, versions, or environments.

That is why the next step is learning about egghunters. They provide a more flexible way to locate shellcode in memory without depending on one exact stack offset. I will cover that in the next post.

I have published two SEH overflow exploits here for anyone to look at:


I have intentionally left out some of the lower-level workflow details here - such as finding the exact SEH offset, building custom opcodes, and locating useful instructions in memory - so this post can stay focused on the big picture.

If you want to go deeper and start building these yourself, these are useful places to start:

  • msf-pattern_create and msf-pattern_offset for finding offsets in both SEH and standard overflows
  • msf-nasm_shell for generating opcodes
  • The narly WinDbg extension for checking which modules were compiled with SafeSEH, ASLR, and DEP
  • WinDbg scripting and command usage for locating instruction sequences in memory, including pop/pop/ret

How to safeguard against SEH overflows

Understanding the offensive side is only useful if we also understand how to defend against it.

At the compiler and platform level, protections such as SafeSEH, ASLR, SEHOP, and DEP make SEH-based exploitation much harder. These features reduce an attacker’s ability to reuse predictable in-memory addresses and help Windows detect corrupted exception chains at runtime.

Even if an overflow still exists, these mitigations can significantly reduce the chance that it becomes a working exploit.

The next layer is secure coding. Buffer overflows happen when user-controlled input is allowed to write past the intended boundary of a buffer. Avoid unsafe copy operations and unchecked string handling. Validate lengths before writing, use bounded APIs, and track buffer sizes explicitly.

Finally, make post-exploitation harder as well. Use modern antivirus and EDR solutions to improve your chances of detecting or blocking payload execution. Not every attacker relies on generic, easy-to-detect, heavily signatured Metasploit payloads, so layered defenses still matter.

Type to search articles and pages.

Search titles, tags, and article content Esc to close