Uninformed: Informative Information for the Uninformed

Vol 5» 2006.Sep

Gaining Code Execution

There is one important thing to remember when it comes to trying to gain code execution through an SEH overwrite. Put simply, the fact that each exception registration record is stored on the stack lends itself well to abuse when considered in conjunction with a conventional stack-based buffer overflow. As described in section [*], each exception registration record is composed of a Next pointer and a Handler function pointer. Of most interest in terms of exploitation is the Handler attribute. Since the exception dispatcher makes use of this attribute as a function pointer, it makes sense that should this attribute be overwritten with attacker controlled data, it would be possible to gain code execution. In fact, that's exactly what happens, but with an added catch.

While typical stack-based buffer overflows work by overwriting the return address, an SEH overwrite works by overwriting the Handler attribute of an exception registration record that has been stored on the stack. Unlike overwriting the return address, where control is gained immediately upon return from the function, an SEH overwrite does not actually gain code execution until after an exception has been generated. The exception is necessary in order to cause the exception dispatcher to call the overwritten Handler.

While this may seem like something of a nuisance that would make SEH overwrites harder to exploit, it's not. Generating an exception that leads to the calling of the Handler is as simple as overwriting the return address with an invalid address in most cases. When the function returns, it attempts to execute code from an invalid memory address which generates an access violation exception. This exception is then passed onto the exception dispatcher which calls the overwritten Handler.

The obvious question to ask at this point is what benefit SEH overwrites have over the conventional practice of overwriting the return address. To understand this, it's important to consider one of the common practices employed in Windows-based exploits. On Windows, thread stack addresses tend to change quite frequently between operating system revisions and even across process instances. This differs from most UNIX derivatives where stack addresses are typically predictable across multiple operating system revisions. Due to this fact, most Windows-based exploits will indirectly transfer control into the thread's stack by first bouncing off an instruction that exists somewhere in the address space. This instruction must typically reside at an address that is less prone to change, such as within the code section of a binary. The purpose of this instruction is to transfer control back to the stack in a position-independent fashion. For example, a jmp esp instruction might be used. While this approach works perfectly fine, it's limited by whether or not an instruction can be located that is both portable and reliable in terms of the address that it resides at. This is where the benefits of SEH overwrites begin to become clear.

When simply overwriting the return address, an attacker is often limited to a small set of instructions that are not typically common to find at a reliable and portable location in the address space. On the other hand, SEH overwrites have the advantage of being able to use another set of instructions that are far more prevalent in the address space of most every process. This set of instructions is commonly referred to as pop/pop/ret. The reason this class of instructions can be used with SEH overwrites and not general stack overflows has to do with the method in which exception handlers are called by the exception dispatcher. To understand this, it is first necessary to know what the specific prototype is for the Handler field in the EXCEPTION_REGISTRATION_RECORD structure:

typedef EXCEPTION_DISPOSITION (*ExceptionHandler)(
        IN EXCEPTION_RECORD ExceptionRecord,
        IN PVOID EstablisherFrame,
        IN PCONTEXT ContextRecord,
        IN PVOID DispatcherContext);

The field of most importance is the EstablisherFrame. This field actually points to the address of the exception registration record that was pushed onto the stack. It is also located at [esp+8] when the Handler is called. Therefore, if the Handler is overwritten with the address of a pop/pop/ret sequence, the result will be that the execution path of the current thread will be transferred to the address of the Next attribute for the current exception registration record. While this field would normally hold the address of the next registration record, it instead can hold four bytes of arbitrary code that an attacker can supply when triggering the SEH overwrite. Since there are only four contiguous bytes of memory to work with before hitting the Handler field, most attackers will use a simple short jump sequence to jump past the handler and into the attacker controlled code that comes after it. Figure [*] illustrates what this might look like after an attacker has overwritten an exception registration record in the manner described above.

Figure: Gaining code execution from an SEH overwrite
Image seh_chain_overwrite