📜 ⬆️ ⬇️

Windows 8.1 Kernel Patch Protection - PatchGuard

image Periodically, as a rule, on the second Wednesday of the month, you can hear stories that Windows, after the next update, stops loading, showing the blue screen of death. In most cases, the cause of such a situation is either a rootkit or specific system software that treats the internal structures of the operating system frivolously. The blame, of course, is still an update, because “everything worked before it”. With this attitude, it is not surprising that Microsoft does not encourage the use of everything that is not documented. At some point, namely with the release of Windows Server 2003, MS took a more active position in the fight against third-party miracle crafts. Then a kernel integrity protection mechanism appeared - kernel patch protection, better known as PatchGuard.

From the very beginning, it was not positioned as a protection mechanism against rootkits, since rootkits work in the kernel with the same privileges, and therefore, PatchGuard can be disabled. It is rather a filter that cuts off lazy rootkit developers.

What PatchGuard Protects


The most popular place for modifying the kernel was the system call table. By modifying the pointers to the functions of the system calls, they could easily be intercepted, filtered, logged, etc. Moreover, this patch was popular for both rootkits and antivirus software. Other objects of interest for the patch are descriptor tables (GDT, IDT). Through modifying the global table of descriptors, it was possible to change the attributes of the segments, creating backdoors for the code, and through the table of interrupt descriptors it was possible to intercept ... interrupts! The advanced guys splice the kernel functions themselves.
')
Accordingly, the first version of PatchGuard protected:

With the development of NT, many components of the kernel were reworked, including PatchGuard. At the moment it is already difficult to list everything that is protected with its help:

How PatchGuard Guards


It should be noted that PatchGuard is actively using the new implementation of exception handling, introduced in x64-versions of Windows. It is used both for obfuscating PatchGuard itself and for checking the integrity of protected images.

In previous versions of Windows, the exception handler used data structures directly on the stack, which even made it possible to bypass stack cookies when exploiting vulnerabilities. The main change is the storage of a special table within the executable image with entries for each of its individual functions.

typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY { uint32_t BeginAddress; //   uint32_t EndAddress; //   union { uint32_t UnwindInfoAddress; //   ,      uint32_t UnwindData; //   }; } _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY; 

Due to the fact that the address of the beginning and end of any function can be obtained directly in runtime, the task of counting the checksum of a single function becomes trivial. For comparison, in the x86 versions, the integrity control of images is impossible because it is not clear how to define the boundaries of a particular function, and the entire image (or even its individual sections) cannot be covered with a checksum because the same core contains functions that are patched by the core itself on the fly.

When loading the OS, PatchGuard creates from 1 to 4 contexts - data structures in which copies of the functions used by it are stored, checksums of the structures being protected and the encryption keys of the context itself. These contexts are stored in an unloaded pool in an encrypted form. Let's talk about context checking a bit later.

PatchGuard contexts are initialized in phase 1 of the OS boot. The function directly involved in creating the context does not have a public symbol (we will call it KiInitializePatchGuardContext), but you can find it inside the KiFilterFiberContext function. We found two places in which to create a PatchGuard context:

 ... -(call)-> Phase1InitializationDiscard -(call)-> KeInitAmd64SpecificState -(exception)-> KiFilterFiberContext 

 ... -(call)-> Phase1InitializationDiscard -(call)-> sub_14071815C -(call)-> ExpLicenseWatchInitWorker -(call)-> KiFilterFiberContext 

The first option always creates at least one context, while the second only in 4% of cases. Also, the first option is notable for the fact that it invokes the KiFilterFiberContext function implicitly, namely, by throwing an exception.
Pseudocode KeInitAmd64SpecificState
 __int64 KeInitAmd64SpecificState() { signed int v0; // edx@2 __int64 result; // rax@2 //    PatchGuard   if ( !InitSafeBootMode ) { v0 = __ROR4__(KdPitchDebugger | KdDebuggerNotPresent, 1); //       (    -1), //      KiFilterFiberContext result = (v0 / ((KdPitchDebugger | KdDebuggerNotPresent) != 0 ? -1 : 17)); } return result; } 


The function sub_14071815C obviously does not have a public symbol, since it is associated with checking the OS license.
ExpLicenseWatchInitWorker pseudocode
 VOID ExpLicenseWatchInitWorker() { PVOID KiFilterParam; NTSTATUS (*KiFilterFiberContext)(PVOID pFilterparam); BOOLEAN ForgetAboutPG; // KiServiceTablesLocked == KiFilterParam KiFilterParam = KiInitialPcr.Prcb.HalReserved[1]; KiInitialPcr.Prcb.HalReserved[1] = NULL; KiFilterFiberContext = KiInitialPcr.Prcb.HalReserved[0]; KiInitialPcr.Prcb.HalReserved[0] = NULL; ForgetAboutPG = (InitSafeBootMode != 0) | (KUSER_SHARED_DATA.KdDebuggerEnabled >> 1); // 96%  if (__rdtsc() % 100 > 3) ForgetAboutPG |= 1; if (!ForgetAboutPG && KiFilterFiberContext(KiFilterParam) != 1) KeBugCheckEx(SYSTEM_LICENSE_VIOLATION, 0x42424242, 0xC000026A, 0, 0); } 


Below is the pseudo-code of the KiFilterFiberContext function, which selects the method for checking a specific context and calls the function for creating the context itself.
KiFilterFiberContext pseudocode
 BOOLEAN KiFilterFiberContext(PVOID pKiFilterParam) { BOOLEAN Result = TRUE; DWORD64 dwDpcIdx1 = __rdtsc() % 13; //  DPC,      DWORD64 dwRand2 = __rdtsc() % 10; // 50  50,     DWORD64 dwMethod1 = __rdtsc() % 6; //     AntiDebug(); Result = KiInitializePatchGuardContext(dwDpcIdx, dwMethod1, (dwRand2 < 6) + 1, pKiFilterParam, TRUE); if (dwRand2 < 6) { DWORD64 dwDpcIdx2 = __rdtsc() % 13; DWORD64 dwMethod2 = __rdtsc() % 6; do { dwMethod2 = __rdtsc() % 6; } while ((dwMethod1 != 0) && (dwMethod1 == dwMethod2)); Result = KiInitializePatchGuardContext(dwDpcIdx2, dwMethod2, 2, pKiFilterParam, FALSE); } AntiDebug(); return Result; } 


The function that creates the PatchGuard context is so obfuscated that automatic tools cannot cope with it, and researchers suddenly find it uninteresting to reverse it. In statics, this is a complete mess, 10K + lines of decompiled head-on code (decompilation itself in IDA Pro takes about 40 minutes).

image

Everything speaks about the extensive use of macros:

Dynamics is also quite difficult. Here are a couple of examples of what is scattered around the code.
 cli xor eax, eax cmp byte ptr cs:KdDebuggerNotPresent, al jnz short loc_140F3CFBD jmp short loc_140F3CFBB sti 

What does the anti-debugging trick 1 do?
When the debugger is connected, it enters an infinite uninterrupted loop.

 cli sidt fword ptr [rbp+320h] lidt fword ptr [rbp+228h] mov dr7, r13 lidt fword ptr [rbp+320h] sti 

What does the anti-debugging trick 2 do?
Loads a temporary invalid interrupt descriptor table. If we watched access to the debug registers, a debug exception will occur that, under these conditions, will cause a tripple fault followed by a reboot.

Consider the parameters of the KiInitializePatchGuardContext function.
  1. The DPC index of the function to be called for context checking and can be one of the following:
    • KiTimerDispatch
    • KiDpcDispatch
    • ExpTimerDpcRoutine
    • Ioptimerdispatch
    • IopIrpStackProfilerTimer
    • PopThermalZoneDpc
    • CmpEnableLazyFlushDpcRoutine
    • CmpLazyFlushDpcRoutine
    • KiBalanceSetManagerDeferredRoutine
    • ExpTimeRefreshDpcRoutine
    • ExpTimeZoneDpcRoutine
    • ExpCenturyDpcRoutine

  2. Scheduling verification method:
    1. KeSetCoalescableTimer
      A timer object is created, which will start the test after 2m: 05s ± 5 s.
    2. Prcb.AcpiReserved
      DPC will operate at a specific ACPI event, for example, when it enters a low power state. It works not earlier than in 2m: 05s ± 5 s.
    3. Prcb.HalReserved
      DPC will work with the HAL timer tick. Not earlier than in 2m: 05s ± 5 s.
    4. PsCreateSystemThread
      A separate system stream is created, sleeping 2 m: 05 s ± 5 s. After this, the context check is invoked.
    5. KeInsertQueueApc
      A regular kernel APC is created that works immediately, but waits for 2m: 05s ± 5 seconds inside the work item.
    6. KiBalanceSetManagerPeriodicDpc
      DPC will work according to the timer of the balancing manager, not earlier than in 2m: 05s ± 5 s.

  3. The purpose of the parameter is not completely clear; it is only known that it affects the number of checks in the context.
  4. Parameter specific to the selected planning method.
  5. Parameter indicating the need for recalculation of checksums for the context.

DPCs that call validation through an exception within themselves, “look” - whether the DeferredContext parameter is a pointer to non-canonical memory. If the pointer is OK, the DPC does its legitimate work. Otherwise, the DPC calls a chain of recursive functions, which ultimately lead to an exception (due to dereference of the non-canonical address) and the execution of its handler.
Call sequences of recursive functions depending on the DPC function
ExpTimerDpcRoutine -> KiCustomAccessRoutine0 -> KiCustomRecurseRoutine0 ... KiCustomRecurseRoutineN
IopTimerDispatch -> KiCustomAccessRoutine1 -> KiCustomRecurseRoutine1 ... KiCustomRecurseRoutineN
IopIrpStackProfilerTimer -> KiCustomAccessRoutine2 -> KiCustomRecurseRoutine2 ... KiCustomRecurseRoutineN
PopThermalZoneDpc -> KiCustomAccessRoutine3 -> KiCustomRecurseRoutine3 ... KiCustomRecurseRoutineN
CmpEnableLazyFlushDpcRoutine -> KiCustomAccessRoutine4 -> KiCustomRecurseRoutine4 ... KiCustomRecurseRoutineN
CmpLazyFlushDpcRoutine -> KiCustomAccessRoutine5 -> KiCustomRecurseRoutine5 ... KiCustomRecurseRoutineN
KiBalanceSetManagerDeferredRoutine -> KiCustomAccessRoutine6 -> KiCustomRecurseRoutine6 ... KiCustomRecurseRoutineN
ExpTimeRefreshDpcRoutine -> KiCustomAccessRoutine7 -> KiCustomRecurseRoutine7 ... KiCustomRecurseRoutineN
ExpTimeZoneDpcRoutine -> KiCustomAccessRoutine8 -> KiCustomRecurseRoutine8 ... KiCustomRecurseRoutineN
ExpCenturyDpcRoutine -> KiCustomAccessRoutine9 -> KiCustomRecurseRoutine9 ... KiCustomRecurseRoutineN

The context check consists of two stages: first, checking the structure of the context itself, which occurs at the DPC level, then a work item is planned, which checks the protected structures in the system flow. If the test was successful, the old context is deleted and a new one is created instead, which will be launched at a random time interval. If the check fails, PatchGuard clears all its tracks, including zeroing the stack, and displays a blue screen with error code 0x109: CRITICAL_STRUCTURE_CORRUPTION.

Gif with self-decrypting context in the first stage of verification:
image

How to win


There are several approaches to neutralizing PatchGuard:

We liked the latter method, since it is the most “pure”: nothing needs to be hooked and dirty, you just need to replace the value of some variables.
  1. KeSetCoalescableTimer
    You need to scan all timers for which the DPC will contain a DeferredContext with a non-canonical address, and increase the timeout for those found to infinity.
  2. Prcb.AcpiReserved
    Just zanulit this field.
  3. Prcb.HalReserved
    Just zanulit this field.
  4. PsCreateSystemThread
    Scan the sleeping threads and unwind their stack. If it rests on a function from the KiServiceTablesLocked structure, this is our client. We set a hibernation time equal to infinity.
  5. KeInsertQueueApc
    Scan all workflows with stack promotion. If there are functions in the stack that are not from the kernel code section, and they are untwisted using the data for the functions FsRtlMdlReadCompleteDevEx and FsRtlUnininitializeSmallMcb, this is exactly the PatchGuard workflow. We neutralize as in the previous version.
  6. KiBalanceSetManagerPeriodicDpc
    Restore the "legitimate" procedure - KiBalanceSetManagerDeferredRoutine.

These actions must be completed in 2 minutes for the reasons described above. The result is that the context check will never be launched, and a new one will not be scheduled. PatchGuard will not work.

Windows 10


When viewing KiFilterFiberContext from Windows 10 Technical Preview, we noticed a slight change. All old planning methods remained the same. However, a new one has appeared, which so far unconditionally returns STATUS_HV_FEATURE_UNAVAILABLE. Having a little rummaged, we found the KiSwInterruptDispatch function, inside which there is a clear decryption and a call to the context check. Obviously, the ability to check contexts on the Hyper-V hypervisor request will be added. Under certain conditions, a synthetic interrupt will come from the hypervisor, the handler of which will check the integrity of the kernel.

The story continues


In the article we tried not to specify the names of specific functions, not because we feel sorry for. It's simple: the names of the functions used to decrypt and verify contexts are intentionally changed by the developers of PatchGuard and are changed in different versions of the OS.

Here is an example of the inconsistency of the function name with what it actually does. This is the same function, a copy of which is used to self-interpret the context.
image

One thing is good - all these functions are nearby, so you can start with the KiFilterFiberContext function. Obviously, they all lie in one source file. However, kernel integrity checking is not limited to PatchGuard alone. Macros are inserted into various parts of the kernel, checking for those or other structures. Each such place has to be searched manually. Example:

 ... --> Phase1InitializationDiscard --> CcInitializeCacheManager --> CcInitializeBcbProfiler 

With a probability of 50%, this function calculates a checksum for an arbitrary kernel function and plans to check it every 2 minutes in DPC with the CcBcbProfiler function.

So good luck with your search! PatchGuard is interesting precisely because it is fun to reverse;)

Links to help:
Kernel patch protection: frequently asked questions
Bypassing PatchGuard on Windows x64
PatchGuard Reloaded
TSS blog: Patch-Guard 1
The Windows 8.1 Kernel Patch Protection
Understanding and Defeating Windows 8.1 Kernel
Patch protection
Microsoft Windows 8.1 Kernel Patch Protection Analysis & Attack Vectors

Source: https://habr.com/ru/post/246841/


All Articles