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
|
|