|
Structured Exception Handling
Structured Exception Handling (SEH) is a uninform system for
dispatching and handling exceptions that occur during the normal
course of a program's execution. This system is similar in spirit
to the way that UNIX derivatives use signals to dispatch and handle
exceptions, such as through SIGPIPE and SIGSEGV.
SEH, however, is a more generalized and powerful system for
accomplishing this task, in the author's opinion. Microsoft's
integration of SEH spans both user-mode and kernel-mode and is a
licensed implementation of what is described in a patent owned by
Borland[1]. In fact, this patent is one of the reasons
why open source operating systems have not chosen to integrate this
style of exception dispatching[11].
In terms of implementation, structured exception handling works by
defining a uniform way of handling all exceptions that occur during
the normal course of process execution. In this context, an
exception is defined as an event that occurs during execution that
necessitates some form of extended handling. There are two primary
types of exceptions. The first type, known as a hardware
exception, is used to categorize exceptions that originate from
hardware. For example, when a program makes reference to an invalid
memory address, the processor will raise an exception through an
interrupt that gives the operating system an opportunity to handle
the error. Other examples of hardware exceptions include illegal
instructions, alignment faults, and other architecture-specific
issues. The second type of exception is known as a
software exception. A software exception, as one might
expect, originates from software rather than from the hardware. For
example, in the event that a process attempts to close an invalid
handle, the operating system may generate an exception.
One of the reasons that the word structured is included in
structured exception handling is because of the fact that it is used
to dispatch both hardware and software exceptions. This
generalization makes it possible for applications to handle all
types of exceptions using a common system, thus allowing for greater
application flexibility when it comes to error handling.
The most important detail of SEH, insofar as it pertains to this
document, is the mechanism through which applications can
dynamically register handlers to be called when various types of
exceptions occur. The act of registering an exception handler is
most easily described as inserting a function pointer into a chain
of function pointers that are called whenever an exception occurs.
Each exception handler in the chain is given the opportunity to
either handle the exception or pass it on to the next exception
handler.
At a higher level, the majority of compiler-generated C/C++
functions will register exception handlers in their prologue and
remove them in their epilogue. In this way, the exception handler
chain mirrors the structure of a thread's stack in that they are
both LIFOs (last-in-first-out). The exception handler that
was registered last will be the first to be removed from the chain,
much the same as last function to be called will be the first to be
returned from.
To understand how the process of registering an exception handler
actually works in practice, it makes sense to analyze code that
makes use of exception handling. For instance, the code below
illustrates what would be required to catch all exceptions and then
display the type of exception that occurred:
__try
{
...
} __except(EXCEPTION_EXECUTE_HANDLER)
{
printf("Exception code: %.8x\n", GetExceptionCode());
}
In the event that an exception occurs from code inside of the
__try / __except block, the printf
call will be issued and GetExceptionCode will return the
actual exception that occurred. For instance, if code made
reference to an invalid memory address, the exception code would be
0xc0000005, or EXCEPTION_ACCESS_VIOLATION. To
completely understand how this works, it is necessary to dive deeper
and take a look at the assembly that is generated from the C code
described above. When disassembled, the code looks something like
what is shown below:
00401000 55 push ebp
00401001 8bec mov ebp,esp
00401003 6aff push 0xff
00401005 6818714000 push 0x407118
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
00401020 53 push ebx
00401021 56 push esi
00401022 57 push edi
00401023 8965e8 mov [ebp-0x18],esp
00401026 c745fc00000000 mov dword ptr [ebp-0x4],0x0
0040102d c6050000000001 mov byte ptr [00000000],0x1
00401034 c745fcffffffff mov dword ptr [ebp-0x4],0xffffffff
0040103b eb2b jmp ex!main+0x68 (00401068)
0040103d 8b45ec mov eax,[ebp-0x14]
00401040 8b08 mov ecx,[eax]
00401042 8b11 mov edx,[ecx]
00401044 8955e4 mov [ebp-0x1c],edx
00401047 b801000000 mov eax,0x1
0040104c c3 ret
0040104d 8b65e8 mov esp,[ebp-0x18]
00401050 8b45e4 mov eax,[ebp-0x1c]
00401053 50 push eax
00401054 6830804000 push 0x408030
00401059 e81b000000 call ex!printf (00401079)
0040105e 83c408 add esp,0x8
00401061 c745fcffffffff mov dword ptr [ebp-0x4],0xffffffff
00401068 8b4df0 mov ecx,[ebp-0x10]
0040106b 64890d00000000 mov fs:[00000000],ecx
00401072 5f pop edi
00401073 5e pop esi
00401074 5b pop ebx
00401075 8be5 mov esp,ebp
00401077 5d pop ebp
00401078 c3 ret
The actual registration of the exception handler all occurs behind
the scenes in the C code. However, in the assembly code, the
registration of the exception handler starts at 0x0040100a
and spans four instructions. It is these four instructions that are
responsible for registering the exception handler for the calling
thread. The way that this actually works is by chaining an
EXCEPTION_REGISTRATION_RECORD to the front of the list of
exception handlers. The head of the list of already registered
exception handlers is found in the ExceptionList attribute
of the NT_TIB structure. If no exception handlers are
registered, this value will be set to 0xffffffff. The
NT_TIB structure makes up the first part of the
TEB, or Thread Environment Block, which is an
undocumented structure used internally by Windows to keep track of
per-thread state in user-mode. A thread's TEB can be
accessed in a position-independent fashion by referencing addresses
relative to the fs segment register. For example, the head
of the exception list chain be be obtained through fs:[0].
To make sense of the four assembly instructions that register the
custom exception handler, each of the four instructions will be
described individually. For reference purposes, the layout of the
EXCEPTION_REGISTRATION_RECORD is described below:
+0x000 Next : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : Ptr32
- push 0x4011a4
The first instruction pushes the address of the CRT generated
_except_handler3 symbol. This routine is responsible for
dispatching general exceptions that are registered through the
__except compiler intrinsic. The key thing to note here
is that the virtual address of a function is pushed onto the stack
that is excepted to be referenced in the event that an exception is
thrown. This push operation is the first step in dynamically
constructing an EXCEPTION_REGISTRATION_RECORD on the
stack by first setting the Handler attribute.
- mov eax,fs:[00000000]
The second instruction takes the current pointer to the first
EXCEPTION_REGISTRATION_RECORD and stores it in
eax.
- push eax
The third instruction takes the pointer to the first exception
registration record in the exception list and pushes it onto the
stack. This, in turn, sets the Next attribute of the
record that is being dynamically generated on the stack. Once this
instruction completes, a populated
EXCEPTION_REGISTRATION_RECORD will exist on the stack
that takes the following form:
+0x000 Next : 0x0012ffb0
+0x004 Handler : 0x004011a4 ex!_except_handler3+0
- mov fs:[00000000],esp
Finally, the dynamically generated exception registration record is
stored as the first exception registration record in the list for
the current thread. This completes the process of inserting a new
registration record into the chain of exception handlers.
The important things to take away from this description of exception
handler registration are as follows. First, the registration of
exception handlers is a runtime operation. This means that whenever
a function is entered that makes use of an exception handler, it
must dynamically register the exception handler. This has
implications as it relates to performance overhead. Second, the
list of registered exception handlers is stored on a per-thread
basis. This makes sense because threads are considered isolated
units of execution and therefore exception handlers are only
relative to a particular thread. The final, and perhaps most
important, thing to take away from this is that the assembly
generated by the compiler to register an exception handler at
runtime makes use of the current thread's stack. This fact will be
revisited later in this section.
In the event that an exception occurs during the course of normal
execution, the operating system will step in and take the necessary
steps to dispatch the exception. In the event that the exception
occurred in the context of a thread that is running in user-mode,
the kernel will take the exception information and generate an
EXCEPTION_RECORD that is used to encapsulate all of the
exception information. Furthermore, a snapshot of the executing
state of the thread is created in the form of a populated
CONTEXT structure. The kernel then passes this information
off to the user-mode thread by transferring execution from the
location that the fault occurred at to the address of
ntdll!KiUserExceptionDispatcher. The important thing to
understand about this is that execution of the exception dispatcher
occurs in the context of the thread that generated the exception.
The job of ntdll!KiUserExceptionDispatcher is, as the name
implies, to dispatch user-mode exceptions. As one might guess, the
way that it goes about doing this is by walking the chain of
registered exception handlers stored relative to the current thread.
The diagram in figure provides a basic example
of how it walks the chain. As the exception dispatcher walks the
chain, it calls the handler associated with each registration
record, giving that handler the opportunity to handle, fail, or pass
on the exception.
Figure:
Walking the chain of exception registration records
|
While there are other things involved in the exception dispatching
process, this description will suffice to set the stage for how it
might be abused to gain code execution.
|