PatchGuard is a component of NT Kernel Microsoft introduced back in 2005 to prevent both legitimate and malicious software from patching the kernel in unsupported ways, mainly for purposes such as preventing malicious processes, files and registries being hidden by the patching of system service tables.
Researchers kept finding ways around it, researching components of PatchGuard to disarm it in whether real-time or boot-time, finding new ways of hooking and Microsoft kept improving it incrementally for 14 years straight, obfuscating their code further, abusing undocumented system, dispatcher, scheduling internals and recently introducing hypervisor-based code integrity AKA HVCI.
But what if you never had to hook anything in the first place to get to the goal? What if we never had to mess with PatchGuard itself in the first place. A common hooking technique, exception-based hooking, has been used in user-mode for years so what makes it so impossible in kernel-mode? This was the main thought behind our project. Although it won’t be as simple as a call to kernel32!AddVectoredExceptionHandler, we can still create a global kernel-mode exception handler.
BugCheck callbacks get called very late into the routine itself, late enough to make the returning from it very hard, and we can’t really hook KeBugCheckEx directly. However what we can do use is abuse HalPrivateDispatchTable which resides in the .data segment, free modify as one wishes. HAL is very frequently used inside the KeBugCheck2 routine, HalTimerWatchdogStop is used to stop the timer watchdog, HalPrepareForBugcheck is used to prepare for it, HalNotifyProcessorFreeze is invoked from slave processors that did not invoke the BugCheck in the first place and HalRestoreHvEnlightenment is used right before the system returns to firmware requesting a hard-reset.
In order to hook into this process before it reaches the final stage, without placing any inline hooks, we abuse this freely modifiable table to hijack the control flow both for slave and master processors, reset the processors frozen state and restore the callers context from the CONTEXT structure saved in KPCRB as the first thing upon the execution of KeBugCheck/KeBugCheckEx. ByePg abuses exactly this to create a system-level exception handler after which we can finally do any kind of exception hooking in the kernel.
Although the things we can do with this is simply endless, we demonstrate the potential of this kind of power on our weaponized example, ExHook hooking all system-calls in less than 200 lines of very simple code; abusing the fact that the KiCopyCounters call at the end of any interrupt/syscall only checks for the DISPATCHER_HEADER.CycleProfiling flag and does not check if the KTHREAD.ThreadCounters is a null pointer or not. We set this flag, leaving the ThreadCounters null, catch the dereferencing of the invalid pointer in KiCopyCountersWorker using our system-wide handler, skip to the routine epilogue and inject a call to a routine of our own.
By doing this, we end up achieving a complete SYSCALL / UM exception hook which works for all versions of Windows after 8.0 without any hard-coded constants, searching for PatchGuard contexts, messing with the obfuscated PatchGuard routines or physical memory scanning in a completely clean manner.
The moral of the story is, regardless of what Microsoft or anyone does, as demonstrated by other similar projects and our work, ByePg/ExHook, security through obscurity is never going to work. As long as your code runs at the same privilege level as the defense mechanism, there is always a way to exploit it, and someone will find it, sooner or later.