 |
Design
The one basic requirement of any solution attempting to prevent the
leveraging of SEH overwrites is that it must not be possible for an
attacker to be able to supply a value for the Handler
attribute of an exception registration record that is subsequently
used in an unchecked fashion by the exception dispatcher when an
exception occurs. If a solution can claim to have satisfied this
requirement, then it should be true that the solution is secure.
To that point, Microsoft's solution is secure, but only if all of
the images loaded in the address space have been compiled with
/SAFESEH. Even then, it's possible that it may not be
completely secure3.1. If there are any
images that have not been compiled with /SAFESEH, it may be
possible for an attacker to overwrite the Handler with an
address of an instruction that resides within an unprotected image.
The reason Microsoft's implementation cannot protect against this is
because SafeSEH works by having the exception dispatcher validate
handlers against a table of image-specific safe exception handlers
prior to calling an exception handler. Safe exception handlers are
stored in a table that is contained in any executable compiled with
/SAFESEH. Given this limitation, it can also be said that
Microsoft's implementation is not secure given the appropriate
conditions. In fact, for third-party applications, and even some
Microsoft-provided applications, these conditions are considered by
the author to be the norm rather than the exception. In the end, it
all boils down to the fact that Microsoft's solution is a
compile-time solution rather than a runtime solution. With these
limitations in mind, it makes sense to attempt to approach the
problem from the angle of a runtime solution rather than a
compile-time solution.
When it comes to designing a runtime solution, the important
consideration that has to be made is that it will be necessary to
intercept exceptions before they are passed off to the registered
exception handlers by the exception dispatcher. The particulars of
how this can be accomplished will be discussed in chapter
. Assuming a solution is found to the layering
problem, the next step is to come up with a solution for determining
whether or not an exception handler is valid and has not been
tampered with. While there are many inefficient solutions to this
problem, such as coming up with a solution to keep a ``secure'' list
of registered exception handlers, there is one solution in
particular that the author feels is bested suited for the problem.
One of the side effects of an SEH overwrite is that the attacker
will typically clobber the value of the Next attribute
associated with the exception registration record that is
overwritten. This occurs because the Next attribute
precedes the Handler attribute in memory, and therefore
must be overwritten before the Handler in the case of a
typical buffer overflow. This has a very important side effect that
is the key to facilitating the implementation of a runtime solution.
In particular, the clobbering of the Next attribute means
that all subsequent exception registration records would not be
reachable by the exception dispatcher when walking the chain.
Consider for the moment a solution that, during thread startup,
places a custom exception registration record as the very last
exception registration record in the chain. This exception
registration record will be symbolically referred to as the
validation frame henceforth. From that point forward,
whenever an exception is about to be dispatched, the solution could
walk the chain prior to allowing the exception dispatcher to handle
the exception. The purpose of walking the chain before hand is to
ensure that the validation frame can be reached. As such, the
validation frame's purpose is similar to that of stack
canaries[5]. If the validation frame can be reached, then
that is evidence of the fact that the chain of exception handlers
has not been corrupted. As described above, the act of overwriting
the Handler attribute also requires that the Next
pointer be overwritten. If the Next pointer is not
overwritten with an address that ensures the integrity of the
exception handler chain, then this solution can immediately detect
that the integrity of the chain is in question and prevent the
exception dispatcher from calling the overwritten Handler.
Figure illustrates how this might look at
execution time.
Figure:
Detecting corruption of the exception handler chain
|
Using this technique, the act of ensuring that the integrity of the
exception handler chain is kept intact results in the ability to
prevent SEH overwrites. The important questions to ask at this
point center around what limitations this solution might have. The
most obvious question to ask is what's to stop an attacker from
simply overwriting the Next pointer with the value that was
already there. There are a few things that stop this. First of
all, it will be common that the attacker does not know the value of
the Next pointer. Second, and perhaps most important, is
that one of the benefits of using an SEH overwrite is that an
attacker can make use of a pop/pop/ret sequence. By forcing
an attacker to retain the value of the Next pointer, the
major benefit of using an SEH overwrite in the first place is gone.
Even conceding this point, an attacker who is able to retain the
value of the Next pointer would find themselves limited to
overwriting the Handler with the address of instructions
that indirectly transfer control back to their code. However, the
attacker won't simply be able to use an instruction like jmp
esp because the Handler will be called in the context of
the exception dispatcher. It's at this point that diminishing
returns are reached and an attacker is better off simply overwriting
the return address, if possible.
Another important question to ask is what's to stop the attacker
from overwriting the Next pointer with the address of the
validation frame itself or, more easily, with 0xffffffff.
The answer to this is much the same as described in the above
paragraph. Specifically, by forcing an attacker away from the
pop/pop/ret sequence, the usefulness of the SEH overwrite
vector quickly degrades to the point of it being better to simply
overwrite the return address, if possible. However, in order to be
sure, the author feels that implementations of this solution would
be wise to randomize the location of the validation frame.
It is the author's opinion that the solution described above
satisfies the requirement outlined in the beginning of this chapter
and therefore qualifies as a secure solution. However, there's
always a chance that something has been missed. For that reason,
the author is more than happy to be proven wrong on this point.
|