 |
Interception of _C_specific_handler
The simplest course of action for disabling PatchGuard version 2 is, in the
author's opinion, to intercept execution at _C_specific_handler. The
_C_specific_handler routine is responsible for dispatching exceptions for
routines compiled with the Microsoft C/C++ compiler (and using try/except,
try/finally, or try/catch clauses). This set of functions includes all ten of
the PatchGuard DPC routines and most other C/C++ functions in the kernel. It
also includes many third party driver routines as well; _C_specific_handler is
exported, and the compiler references this function for all C/C++ images that
utilize SEH in some form (imported from ntoskrnl). Due to this, Microsoft is
forced to export _C_specific_handler from the kernel perpetually, making it
difficult for Microsoft to deny access to the routine's address from the
perspective of third party drivers. Furthermore, because _C_specific_handler
is exported from the kernel, it is trivial to retrieve its address across all
kernel versions from the context of a third party driver. This approach
capitalizes on the fact that PatchGuard utilizes SEH in order to obfuscate the
call to the system integrity checking routine, in effect turning this
obfuscation mechanism into a convenient way to hijack execution control before
the system integrity check is actually performed.
This approach can be implemented in several different ways, but the basic idea
is to intercept execution somewhere between the faulting instruction in the
PatchGuard DPC (whichever is selected at boot time), and the exception
handlers associated with the DPC routine which invoke the PatchGuard system
integrity check routine. With this in mind, _C_specific_handler is exactly
what one could hope for; _C_specific_handler is invoked when the benign
access violation triggered by the bogus DeferredContext value to the
PatchGuard DPC routine is called. Furthermore, being exported, there are no
concerns with compatibility with future kernel versions, or different flavors
of the kernel (PAE vs non-PAE, MP vs UP, and soforth).
Although hooking _C_specific_handler provides a convenient way to gain
control of execution in the execution path for the PatchGuard check routine,
there remains the problem of how to safely defuse the check routine and resume
execution at a safe point such that DPCs continue to be processed by the
system in a timely fashion. On x86, this would pose a serious problem, as in
this context, we (as an attacker attempting to bypass PatchGuard) would gain
control at an exception handler with a context record describing the context
at middle of the PatchGuard DPC routine, with no good way to unwind the
context back up to the DPC routine's caller (the kernel timer DPC dispatcher).
Ironically, by virtue of being only on x64 and not x86, this problem is made
trivial where it might have been difficult to solve in a generalized fashion
on x86. Specifically, there is extensive unwind support baked into the core
of the x64 calling convention on Windows, such that there exists metadata
describing how to unwind any function that manipulates the stack at any point
in its execution lifetime. This metadata is used to implement unwind
semantics that allow functions to be cleanly unwound without having to call
exception/unwind handlers implemented in code that depend on the execution
context of the routine they are associated with. This extensive unwind
metadata can be used to our advantage here, as it provides a clean mechanism
to unwind past the DPC routine (to the DPC dispatcher) in a completely
compatible and kernel-version-independent manner. Furthermore, there is no
good way for Microsoft to disable this unwind metadata, given how deeply
involved it is with the x64 calling convention.
The process of using the unwind metadata of a function to unwind an execution
context is known as a virtual unwind, and there is a documented, exported
routine [5] to implement this mechanism: RtlVirtualUnwind. Using
RtlVirtualUnwind, it is possible to alter the execution context that is
provided as an argument to _C_specific_handler (and thus the hook on
_C_specific_handler). This execution context describes the machine state
at the time of the access violation in the PatchGuard DPC routine. After
performing a virtual unwind on this execution context, all that remains is to
return the manifest ExceptionContinueExecution constant to the kernel
mode exception dispatcher in order to realize the altered context. This
completely bypasses the PatchGuard system integrity check. As an added
bonus, the hook on _C_specific_handler is only needed until the
first time PatchGuard is called. This is due to the fact that the PatchGuard
timer is a one-shot timer, and as the code to re-queue the timer is skipped by
the virtual unwind, PatchGuard is effectively permanently disabled for the
remainder of the Windows boot session.
The last remaining obstacle with this bypass technique is filtering
out the specific PatchGuard access violation exceptions from
legitimate access violations that kernel mode code may produce. This
is important, as access violations in kernel mode are a normal part
of parameter validation (the probe and lock model used to validate
user mode pointers) for drivers and system services. Fortunately, it
is easy to make this determination, as it is generally only legal to
use a try/except to catch an access violation relating to a user
mode address from kernel mode (as previously described). PatchGuard
is a rare exception to this rule, in that it has a well-defined
no-mans-land region where accesses can be attempted without fear of
a bugcheck occurring. As a result, it is a safe assumption that any
access violation relating to a kernel mode address is either
PatchGuard trigger its own execution, or a very badly behaved third
party driver that is grossly breaking the rules relating to Windows
kernel mode drivers. It is the author's opinion that the latter
case is not worth considering as a blocker, especially since if such
a completely broken driver were to exist, it would already be
randomly bringing the system down with bugchecks. It is worth
noting, as an addendum, that the referenced address in the exception
information block passed to the exception handler will always be
0xFFFFFFFF`FFFFFFFF due to how violations on non-canonical
addresses are reported by the processor. This does not impact the
viability of this technique as a valid way to bypass PatchGuard in a
version-independant manner, however.
It is worth noting that the fact that this technique involves modifying the
kernel is not a problem (aside from the inherent race conditions involved in
safely patching a running binary). The hook will disable PatchGuard before
PatchGuard has a chance to notice the hook from the context of the system
integrity check routine.
This proposed approach has several advantages over the previously
suggested approach by Uninformed's original paper on PatchGuard
[2]. Specifically, it does not involve locating each
individual DPC routine (and does not even rely on any sort of code
fingerprinting; only exported symbols are used). This improves both
the reliability of the proposed approach (as code fingerprinting
always introduces an additional margin of error as far as false
positives go) and its resiliency to attack by Microsoft. Because
this technique relies solely on exported functions, and does not
carry any sort of dependency on how many possible DPCs are available
to PatchGuard for use (or any sort of dependency on locating them at
runtime), blocking this approach would be significantly more
involved than simply adding another possible DPC routine or changing
the attributes of an existing DPC routine in an effort to
third-party drivers that were taking a signature-based approach to
locating DPC routines for patching.
Although this technique is quite resilient to kernel changes that do
not directly involve the underlying mechanisms by which PatchGuard
itself functions (the fact that it can operate unmodified on both
Windows Server 2003 x64 and Windows Vista x64 is testament to this
fact), there are a number of different ways by which Microsoft could
block this attack in a future update to PatchGuard. The most
obvious solution is to entirely abandon SEH as a core mechanism
involved in arranging for the PatchGuard system integrity check.
Abandoning SEH removes the convenient mechanism (hooking
_C_specific_handler) that is presented here as a
version-independent way to hook in to the execution path involved in
PatchGuard's system integrity check. If Microsoft were to go this
route, a would-be attacker would need to devise another mechanism to
achieve control of execution before the system integrity check runs.
Assuming that Microsoft played their hand correctly, a future
PatchGuard revision would not have such an easily-accessible
mechanism to hook into the execution process in a generic manner,
largely counteracting this proposed approach. Microsoft could also
employ some sort of pre-validation of the exception handler path
before the DPC triggers an exception, although given that this is
not the easiest and most elegant way to counter such a technique,
the author feels that it is an unlikely solution.
|