In this article we are going to discuss what is SEHOP, how it is related to SafeSEH and what is its role in preventing exploitation.

What is SEH

Structured Exception Handling aka SEH is a Windows proprietary exception handling mechanism. It’s a versatile tool that provides a unified way for handling both software and hardware exceptions. Hardware exceptions are the ones like reading from an invalid memory address or executing an invalid instruction. Software exceptions are, well, issues in the software, like trying to divide by 0.

How does SEH work?

The implementation of SEH is different between 32 and 64 bit Windows.

On 32bit, SEH is stack based, that is each function records an exception handlers in its frame. The registration happens on demand during the run-time.

For the 64bit applications, the exception handlers are moved away from the stack into the PE header. The table is called “exception directory” and it holds the relationship between routines and their exception handlers.

Since the common SEH exploitation technique “SEH Overwrites” only applicable to 32bit software, I will cover Win32 SEH dynamics in this article. Keep in mind, implementation details of the structures we are going to discuss may be different among compilers.

If a function needs to utilize an exception handler, it must register it during the prologue. To do that, it fills out and submits exception registration record:

typedef struct _EXCEPTION_REGISTRATION_RECORD 
{ 
   struct _EXCEPTION_REGISTRATION_RECORD *Next; 
   PEXCEPTION_ROUTINE                 	Handler; 
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;

This record has two fields, a pointer to the previous _EXCEPTION_REGISTRATION_RECORD and a PEXCEPTION_ROUTINE function that will be called to handle the exception.

Let's take a look at the snippet of dissasembly of a function prologue:

0040100a 68a4114000       push    0x4011a4
0040100f 64a100000000     mov     eax,fs:[00000000]
00401015 50               push    eax
00401016 64892500000000   mov     fs:[00000000],esp
0040101d 83c4f4           add     esp,0xfffffff4 ; same as sub esp, 0x4h

The stack grows from high addresses to low addresses. At line 0040100a we push a pointer to the handle function. Then we copy fs:[00000000] to eax register and add eax to the registration. Then we submit our registration by moving the current stack pointer back to the fs:[00000000]. After that, the function continues with its usual business, like allocating space for the variables, etc. It’s important to note that the record pointer stays on the stack, so if later any buffer overflows occur, the record will get written over.

image25

Just to clarify the fs:[0] reference. This is a pointer to NT_TIB structure aka Thread Information Block. It looks like this:

/* This structure is 56 bytes on x64, and 28 bytes on x86 */
struct _NT_TIB {              //  x86  /  x64
  void *ExceptionList;        // 0x000 / 0x000
  void *StackBase;            // 0x004 / 0x008
  void *StackLimit;           // 0x008 / 0x010
  void *SubSystemTib;         // 0x00c / 0x018
  union {
    void *FiberData;          // 0x010 / 0x020
    uint32_t Version;         // 0x010 / 0x020
  };
  void *ArbitraryUserPointer; // 0x014 / 0x028
  struct _NT_TIB *Self;       // 0x018 / 0x030
};

After the record is submitted, fs:[0] is extended with a new handler. it looks like this:

Untitled-drawing--2-

Later, if exception occurs, ntdll!KiUserExceptionDispatcher will go through the chain of exception handlers until it finds the suitable one or fails the application with the default.

The attack

The idea behind the attack is very straightforward. We already have a machinery to execute any function of our choosing, now we only need a way to overwrite application exception handler. So we need a buffer overflow vulnerability. Generally speaking, the SEH overwrite is used to exploit buffer overflow due to its portability across Windows versions and its ability to defend some of the stack protection.

To exploit this vulnerability, we need to overwrite the _EXCEPTION_REGISTRATION_RECORD structure in a certain way. Specifically, we write a pop pop ret gadget's address into PEXCEPTION_ROUTINE, a jmp 6 bytecode into the _EXCEPTION_REGISTRATION_RECORD *Next field and the shellcode right before the SEH record on the stack. We also need to cause an exception by corrupting some data. When the exception handler is executed, a EXCEPTION_DISPOSITION and a return address will be pushed onto the stack.

the-need-for-a-pop-pop-ret-instruction-sequence-figure-3

EXCEPTION_DISPOSITION __cdecl _except_handler(
    struct _EXCEPTION_RECORD * ExceptionRecord,
    void * EstablisherFrame,
    struct _CONTEXT * ContextRecord,
    void * DispatcherContext
)

Where void * EstablisherFrame is the pointer to _EXCEPTION_REGISTRATION_RECORD *Next.

When the handler is executed, the pop pop ret instruction will remove 2 entries from the stack and jump onto the third, which will lead it to our _EXCEPTION_REGISTRATION_RECORD *Next instruction, and after that, to our shellcode. It was that easy under Windows XP SP 1.

Mitigations

Here we come to our first exploit mitigation techniques. Since Windows XP SP2, Microsoft introduced two very interesting exploit mitigation techniques: Registry XOR & SafeSEH.

Registry XOR is said to be a response to the Code Red worm, which propagated through a remote buffer overflow. All this mitigation does, is resets the registers to 0 before exception handler takes control. The reason for that the worm would store its shellcode address in other registers, like ebx and instead of doing pop pop ret would just call ebx.

The second mitigation is SafeSEH, which is a compiler-time mitigation. If application is compiled with /SAFESEH option, the linker will produce a table of safe exception handlers. In this table it will record the address for each of the handlers. When exception occurs, the operating system checks for two conditions.

First, it checks if the exception handler points to the address on the stack by referencing Thread Environment Block (TEB)'s entry for high FS:[4] and low FS:[8] addresses. If the handler is on the stack, it won't be called. Then the operating system verifies that the address of the handler belongs to any of the loaded modules, including both executable image and DLLs. If the module is that address space, the OS will check in its safe table if the handler is authorized to run. If, however, the handler is not in the address space of the loaded modules, it just runs. Go figure.

SafeSEH could be bypassed in a number of ways:

  • If any of the OS/application module are not compiled with SafeSEH, we can simply find “pop pop ret” there and it will work
  • If our pop pop ret gadget is not within the loaded application’s modules, it will be executed
  • If the gadget is within the address range of loaded module, but the module doesn’t have Load Configuration Directory PE header, the gadget will be executed
  • If the address of the handler points to the heap instead of the stack, it will be executed
  • Rarely, but if another registered handler can be used to gain control of the CPU, we can redirect execution to it

Finally, SEHOP

As you may have noticed, all SafeSEH bypasses still rely on our ability to return to the address of the “Next SEH record” _EXCEPTION_REGISTRATION_RECORD *Next. SEHOP aka Structured Exception Handling Overwrite Protection takes an extra step in providing defenses against SEH overwrites by checking if the exception handling chain was corrupted. It’s done on runtime by traversing the exception handler’s list to verify that the last entry is ntdll!FinalExceptionHandler, which is a default Windows exception handler located in ntdll.dll.

The validation algorithm used in SEHOP has been exposed by A. Sotirov during Black Hat 2008 [^1].

BOOL RtlIsValidHandler(handler) {
    if (handler is in an image) {
      if (image has the IMAGE_DLLCHARACTERISTICS_NO_SEH flag set)
        return FALSE;
      if (image has a SafeSEH table)
        if (handler found in the table)
          return TRUE;
        else
          return FALSE;
      if (image is a.NET assembly with the ILonly flag set)
        return FALSE;
      // fall through
    }
    if (handler is on a non - executable page) {
      if (ExecuteDispatchEnable bit set in the process flags)
        return TRUE;
      else
      // enforce DEP even if we have no hardware NX
        raise ACCESS_VIOLATION;
    }
    if (handler is not in an image) {
      if (ImageDispatchEnable bit set in the process flags)
        return TRUE;
      else
        return FALSE; // don't allow handlers outside of images
    }
    // everything else is allowed
    return TRUE;
  }
  [...]
  // Skip the chain validation if the
DisableExceptionChainValidation bit is set
if(process_flags & 0x40 == 0) {
    // Skip the validation if there are no SEH records on the
    // linked list
    if (record != 0xFFFFFFFF) {
      // Walk the SEH linked list
      do {
        // The record must be on the stack
        if (record < stack_bottom || record > stack_top)
          goto corruption;
        // The end of the record must be on the stack
        if ((char * ) record + sizeof(EXCEPTION_REGISTRATION) >
          stack_top)
          goto corruption;
        // The record must be 4 byte aligned
        if ((record & 3) != 0)

It follows that to successfully bypass SafeSEH and SEHOP, all of the following conditions should be true:

  • SEH handler should point onto a non-SafeSEH module
  • The page should be executable
  • The SEH chain should not be altered and must end with a SEH structure containing a special
  • value (0xFFFFFFFF as next SEH structure pointer and a specific value as SEH handler)
  • All SEH structures should be 4-byte aligned
  • Last SEH structure's handler should point right into ntdll to ntdll!FinalExceptionHandler routine
  • We know or can guess the address of ntdll!FinalExceptionHandler
  • All SEH pointers should point to stack locations

One theoretical way to bypass this is by creating a fake SEH chain. To do that, we first need to craft such “next handler’s address” that it works both as an address and instruction, but in practice, it will be extremely hard, especially when SEHOP is combined with ASLR/DEP which I will cover later.


This is what makes SEHOP is a very solid defense, and why its enabled system wide in Windows Defender Exploit Guard by default.

Part 1 - Windows Defender Exploit Guard for Pentesters - Validate Exception Chains (SEHOP)
Part 2 - Windows Defender Exploit Guard for Pentesters - ASLR
Part 3 - Windows Defender Exploit Guard for Pentesters - DEP
Part 4 - Windows Defender Exploit Guard for Pentesters - CFG
Part 5 - Windows Defender Exploit Guard for Pentesters - CIG & ACG