|
Gaining Control of the Unhandled Exception Filter
At this point, the only feasible vector for gaining control of the
top-level UEF is to cause calls to be made to
kernel32!SetUnhandledExceptionFilter. This is primarily
due to the fact that the global variable has the current function
pointer encoded. One could consider attempting to cause code to be
redirected directly to
kernel32!SetUnhandledExceptionFilter, but doing so would
require some kind of otherwise-exploitable vulnerability in an
application, thus making it not useful in the context of this
document.
Given these restrictions, it makes sense to think a little bit more
about the process involved in registering and deregistering UEFs.
Since the chain of registered UEFs is implicit, it may be possible
to cause that chain to become corrupt or invalid in some way that
might be useful. One of the requirements that is known about the
registration process for top-level UEFs is that the register and
deregister operations must be symmetric. What happens if they
aren't, though? Consider the diagram in figure , where
Fx and Gx are registered and deregistered, but in
asymmetric order.
Figure:
Asymmetric register and deregister of UEFs
|
As shown in the diagram in figure , Fx and
Gx are registered first. Following that, Fx is
deregistered prior to deregistering Gx, thus making the
operation asymmetrical. As a result of Fx deregistering
first, the top-level UEF is set to Nx, even though
Gx should technically still be a part of the chain.
Finally, Gx deregisters, setting the top-level UEF to
Fx even though Fx had been previously
deregistered. This is obviously incorrect behavior, but the code
associated with Gx has no idea that Fx has been
deregistered due to the implicit chain that is created.
If asymmetric registration of UEFs can be made to occur, it might be
possible for an attacker to gain control of the top-level UEF.
Consider for a moment that the register and deregister operations in
the diagram in figure occur during DLL load and
unload, respectively. If that is the case, then after
deregistration occurs, the DLLs associated with the UEFs will be
unloaded. This will leave the top-level UEF set to Fx
which now points to an invalid region of memory. If an exception
occurs after this point and is not handled by a registered exception
handler, the unhandled exception filter will be called. If a
debugger is not attached, the top-level UEF Fx will be
called. Since Fx points to memory that is no longer
associated with the DLL that contained Fx, the process will
terminate -- or worse.
From a security prospective, the act of leaving a dangling function
pointer that now points to unallocated memory can be a dream come
true. If a scenario such as this occurs, an attacker can attempt to
consume enough memory that will allow them to store arbitrary code
at the location that the function originally resided. In the event
that the function is called, the attacker's arbitrary code will be
executed rather than the code that was was originally at that
location. In the case of the top-level UEF, the only thing that an
attacker would need to do in order to cause the function pointer to
be called is to generate an unhandled exception, such as a
NULL pointer dereference.
All of these details combine to provide a feasible vector for
executing arbitrary code. First, it's necessary to be able to cause
at least two DLLs that set UEFs to be deregistered asymmetrically,
thus leaving the top-level UEF pointing to invalid memory. Second,
it's necessary to consume enough memory that attacker controlled
code can reside at the location that one of the UEF functions
originally resided. Finally, an exception must be generated that
causes the top-level UEF to be called, thus executing the attacker's
arbitrary code.
The big question, though, is how feasible is it to really be able to
control the registering and deregistering of UEFs? To answer that,
chapter provides a case study on one such application
where it's all too possible: Internet Explorer.
|