Uninformed: Informative Information for the Uninformed

Vol 6» 2007.Jan

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.