Uninformed: Informative Information for the Uninformed

Vol 8» 2007.Sep

Canceling the PatchGuard Timer(s)

As PatchGuard continues to rely upon timer DPCs for the execution of its check routines, the kernel timer DPC list itself continues to remain a relatively attractive target for attack. The timer DPC list is common to all control paths leading to PatchGuard, as timers are always used for the delayed execution component that periodically calls the check routine.

There are presently two obstacles in the way of the timer DPC list. The first of which is that altering it relies upon locating non-exported kernel variables. Although it may be possible to do so via fingerprinting, this does make the approach slightly less desirable than it might initially appear. However, fingerprinting can work if done carefully, and there are many short functions that reference the timer list in a fairly predictable fashion (e.g. KeCancelTimer). One other possible way to find the DPC list would be to create and set a timer (thus inserting it into the timer list), and then scan every 8-byte-aligned value in a non-paged uninitialized data section in ntoskrnl, treating each valid address as a linked list and searching the first several entries for the timer that was just linked into the list. While a rather ugly and bruteforce-based approach (and not entirely safe either as one would need to be relying heavily on MmIsAddressValid), scanning the ntoskrnl data sections is one alternative to fingerprinting in terms of finding the timer list.

The secondary problem with this approach is that starting with PatchGuard 2, the timer list itself is obfuscated such that the link between a KTIMER object and its corresponding KDPC is obfuscated. This obfuscation mechanism, as previously describedbackref to 1, hinges upon two additional non-exported kernel variables (KiWaitAlways, KiWaitNever) that act as obfuscation keys. Locating these variables would be likely entail code analysis or fingerprinting of (possibly exported) routines that need to insert a timer into the timer list, such as KeSetTimerEx.

Another alternative approach that dispenses with fingerprinting and/or bruteforce-based approaches altogether, at the expense of requriring added complexity (a user mode component), would be to postpone the activation of any driver code that would run afoul of PatchGuard until after Win32 in user mode has been started. A user mode service could then be created that would download the symbols for the kernel binary in use, retrieve the addresses of KiTimerTableListHead (the timer list), KiWaitNever and KiWaitAlways, and pass these addresses on to the driver via any standard user mode to kernel mode communication mechanism (such as DeviceIoControl). Because the kernel debugger relies on the ability to retrieve these variables by name via the PDB symbols for the !kdexts.timer extension, Microsoft would not be able to block this approach by removing or renaming the obfuscation key variables without imparing the functionality of existing debugger binaries.

Once one has located the KiTimerTableListHead, KiWaitAlways, and KiWaitNever, it is a fairly simple (if perhaps unsafe without synchronization, though one could always take the "sledgehammer" approach and stop all but one CPU and raise IRQL to HIGH_LEVEL) to traverse the timer list, deobfuscate the DPC link on each corresponding timer object, and from there check each timer to see whether it bears the characteristics of being a PatchGuard timer (which may include attributes like a timer interval several minutes into the future, a non-canonical DeferredContext value, and possibly a DPC routine pointer into non-paged pool). After one has located the timer in question, it can be easily disabled (either removing it from the list entirely, such as via KeCancelTimer, or by rewriting the DPC routine to point to an empty function that simply returns without performing any operation.

Because Microsoft has functionality in the debugger that depends on the ability to use these variables to access the timer list, they have unfortunately backed themselves into something of a corner with respect to current operating system versions, as it is generally Microsoft's policy that existing debugger binaries continue to function properly after hotfixes or service pack to a particular already-released operating system version. The best ways to counteract this approach would be to make it more difficult to pick out the PatchGuard DPC in-memory with respect to all of the other timer DPC objects that are in the list at any given time for a typical system, and to create additional launch vectors for PatchGuard that do not depend so heavily on the timer list. There exist a number of other ways to execute code without drawing the attention of someone that does not know what they are looking, many of which are less obvious than a timer.