Uninformed: Informative Information for the Uninformed

Vol 8» 2007.Sep

Page-Table Swap

Like all memory accesses in the Windows kernel, PatchGuard's system integrity check routine operates in protected mode with paging enabled. It may theoretically be possible to take advantage of this fact to hide kernel patches from PatchGuard.

The proposed bypass technique would involve patching the first instruction in the timer and DPC dispatchers to branch to third party code. When a DPCs and timer DPCs are about to be considered for execution, as signaled by a call to one of the two dispatcher routines, a shadow copy of the page tables is created. This shadow copy is configured to be identical to the normal page table for the current process, except that the page table entries for any kernel code pages that have been patched are altered to refer to physical pages that are representative of the original state. The return address of the DPC or timer DPC dispatcher on the stack is swapped with a pointer into driver-supplied code, and cr3 is reconfigured to point to the shadow page table. Then, execution is transferred back to the timer or DPC dispatcher entrypoint (which no longer shows any signs of patching due to the page table swap), and DPCs are dispatched. When the dispatcher is finished with its work, which would include invoking PatchGuard if PatchGuard is to be executed in any batched timer DPCs, then control is returned to driver-supplied code, which then mirrors any page table modifications since the shadow copy was made back to the actual page table for the process, and cr3 is returned to its original value. Control is then transferred to the normal return point of the dispatcher.

This approach does not involve disabling PatchGuard at all. Instead, it describes a potential way to "peacefully coexist" with it, so long as only kernel code patches are being done. (Data pages, which could be expected to be modified by a DPC, are considered by the author to be much less practical to protect from PatchGuard in this fashion.) Because the DPC and timer DPC dispatcher logic executes at IRQL DISPATCH_LEVEL, thread context switching is disabled for the current thread, making the cr3 swap approach relatively feasible.

Because this approach does not involve attacking PatchGuard directly, it automatically circumvents all of the myriad defensive mechanisms built into PatchGuard in current releases, making it a fairly attractive potential avenue of attack. However, there are some downsides. Among other things, the synchronization required to pull a page tabpe swap off in a multiprocessor environment are likely to be complex and difficult to safely duplicate if one allows DPC routines to perform operations that alter PTEs. Additionally, there would be a performance impact incurred by this approach as it would need to run continuously in a relatively high-impact path (DPC dispatching) throughout system lifetime. The performance implications of invalidating TLBs on every DPC batch may be problematic in some circumstances (swapping cr3 automatically clears out TLBs).

Another disadvantage of this approach is that by virtue of the fact that all DPCs (and potentially all device hardware interrupts) may run with the shadow copy of the page table, most hardware-related events will not be subject to kernel code patches hidden by this mechanism. This may or may not be a problem depending on what the goal of the desired kernel patching is.

Microsoft could counteract this approach by making a copy of all PTEs that describe the kernel at PatchGuard initialization time and then validating all kernel code PTEs from within the PatchGuard check routine. Additionally, if Microsoft could make the assumption that PatchGuard always executes in the system process, another approach could be to require that cr3 take on a known value.