Back
April 26, 2023 ~1 hour read zer0condition

Demystifying PatchGuard: An In-Depth Analysis Through Practical Engineering

A comprehensive analysis of Microsoft's sophisticated security feature called PatchGuard, designed to prevent unauthorized modifications to the Windows kernel. Explores inner workings and possible bypass methods.

PatchGuard Windows Kernel Security Research Reverse Engineering

What is PatchGuard?

The presence of PatchGuard in the 64-bit Windows operating system is a remarkable security measure that thwarts the efforts of kernel-level rootkits and other malware to manipulate critical system code and structures. Its method of operation is through regular monitoring of the kernel to identify any illicit modifications and counteracting them without delay. The purpose of PatchGuard is to preserve the integrity of the operating system and guarantee that it operates at an optimal level, consequently heightening the general security and steadiness of the system.

Kernel code and data structures are verified and digital signatures enforced by PatchGuard, a measure designed to avert instability or security threats brought about by inconsistent modifications or code injections into the kernel by developers. Working within the scope of PatchGuard's restrictions, software developers must ensure compliance and a safe design to function seamlessly with the kernel.

Initialization

KiFilterFiberContext

PatchGuard's initialization process is mainly carried out by the function KiFilterFiberContext, which initializes PatchGuard's contexts and verification mechanisms. KiFilterFiberContext is called twice during the boot process, with one of the methods involving an exception handler known as KiAmd64SpecificState.

To trigger the exception handler and execute KiFilterFiberContext, a division error is intentionally triggered at the beginning of the boot process. The two values used to compute the division are known symbols - KdDebuggerNotPresent and KdPitchDebugger - which are used to determine whether a debugger is attached or not. If a debugger is present, PatchGuard is not initialized.

In a normal scenario, the values of the two symbols are set to 1, which results in the idiv instruction computing the values rax=0x80000000, rdx=0x80000000 and r8d=0xffffffff. The computation of this division results in a divide error, which triggers the KiDivideErrorFault function to execute the KiFilterFiberContext function.

KiFilterFiberContext is responsible for calling the initialization procedure that creates PatchGuard contexts, with specific arguments. It is worth noting that one of the arguments is hardcoded to 0, which suggests that it may be called from elsewhere. In fact, another initialization process has already been documented, which points to the function ExpLicenseWatchInitWorker.

ExpLicenseWatchInitWorker

The function in question is called ExpLicenseWatchInitWorker and is invoked before KeInitAmd64SpecificState in the boot process. It is part of Microsoft's PatchGuard, which verifies the authenticity of Microsoft licenses. The call stack shows that ExInitSystemPhase2 calls ExpGetNtProductTypeFromLicenseValue, which is related to license verification. ExpLicenseWatchInitWorker then calls KiFilterFiberContext, but only with a low probability of 4%, using random values generated by the rdtsc instruction.

ExpLicenseWatchInitWorker includes some checks for the presence of a debugger and the safe boot mode, which are common security measures. The return value of the function is the random value generated by the rdtsc instruction, multiplied by a constant value of 0x51eb851f. If InitIsWinPEMode is true, the random returned value is later used as an index.

PatchGuard Context

The structure of the PatchGuard context is divided into three sections. The first section holds the core content of the PatchGuard mechanisms. The second section is a data recipient that keeps original data for later use, while the third section holds information about data to check.

The First Part

The first part of the structure includes the code for the function CmpAppendDllSection, which is copied directly into the structure and is used later when the integrity check is triggered by PatchGuard. This code's primary function is to decrypt the rest of the PatchGuard context structure with a randomly generated key. The section also holds many function pointers from ntoskrnl API, which are kept this way so that PatchGuard routines can use them independently of a relocation.

The second part of the structure includes many references to global variables, such as KiWaitAlways and KiWaitNever. These values are initialized randomly at boot time and are used to encode and decode PatchGuard DPC pointers. Additionally, it holds a pointer to another PatchGuard context structure, which is used multiple times as a clean backup of a structure.

The Second Part

The second part of the structure contains data that will be used later on. In Windows, entries are saved in this part of the structure to prevent bypass. These entries include PTEs, which are restored just before triggering KeBugCheck, and critical kernel routines, such as Hal, Ntoskrnl, and RtlpBreakWithStatusInstruction/DbgBreakPointWithStatus, among others.

The Third Part

The third part of the PatchGuard context structure contains an array of structures that hold information for each check that needs to be performed. Each structure has a KeBugCheckType field that distinguishes the type of structure being checked, such as IDT or GDT. The structure also contains a pointer to the data to be checked, its size, and a checksum that was computed during PatchGuard initialization as a reference for integrity checks.

Initialization Stage

The initialization of the context is primarily carried out through a function called KiInitPatchGuardContext, which although unnamed, is documented in existing literature. However, there are alternative methods of initializing PatchGuard context, and in certain scenarios, separate mechanisms are established for system checking purposes.

Different Initialization Methods

As previously mentioned, the KiInitPatchGuardContext function is responsible for initializing most of the PatchGuard contexts, with the choice of method determined by the function arguments. The function arguments are as follows:

The Known Methods

There are different methods that KiInitPatchGuardContext may use to initialize PatchGuard checks:

Method 1 (Timer/DPC): Involves inserting a timer linked with a DPC. PatchGuard initializes a PatchGuard Context structure and a DPC structure and sets it in a timer structure. The timer is queued with KeSetCoalescableTimer and will fire the DPC between 2 to 2'10" following the call.

Method 2 (AcpiReserved): The pointer to the DPC is hidden in the field AcpiReserved from the PRCB. It is queued in HalpTimerDpcRoutine and checks that at least two minutes have elapsed between each check.

Method 3 (HalReserved): The pointer to the DPC is hidden in the field HalReserved from the PRCB. It is queued by HalpMcaQueueDpc, also with a 2 minutes minimum period.

Method 4 (System Thread): Involves creating a new system thread using a pointer to a KI_FILTER_FIBER_PARAM structure, which only occurs with a probability of 4%.

Method 5 (APC Insertion): The PatchGuard Context structure and an APC structure are initialized, and then inserted into an existing system thread.

DPC Routine Functions

The first argument randomly selects an index to choose a routine from the following functions:

  1. CmpEnableLazyFlushDpcRoutine
  2. ExpCenturyDpcRoutine
  3. ExpTimeZoneDpcRoutine
  4. ExpTimeRefreshDpcRoutine
  5. CmpLazyFlushDpcRoutine
  6. ExpTimerDpcRoutine
  7. IopTimerDispatch
  8. IopIrpStackProfilerDpcRoutine
  9. KiBalanceSetManagerDeferredRoutine
  10. PopThermalZoneDpc
  11. KiTimerDispatch/KiDpcDispatch

Triggering Contexts

This section focuses on how the context structures are activated, which can vary depending on the method used.

Via DPC Execution

One common method used by PatchGuard to initiate a check is through DPC. The first step when one of the PatchGuard DPC functions is called is to determine whether the DPC is a PatchGuard DPC or a usual DPC. This is done by checking if the DeferredContext has a canonical address or not.

If the DeferredContext parameter has a non-canonical address, then the function calls KiCustomAccessRoutineX and executes what is known as "the Russian roulette trick".

KiCustomRecurseRoutines

Triggering the Exception Handler

After the check for the canonical address of the DeferredContext parameter, if it is found to be non-canonical, PatchGuard calls the function KiCustomAccessRoutineX. This function then calls KiCustomRecurseRoutineX with two parameters: a counter and the non-canonical DeferredContext. The mechanism is similar to playing Russian roulette, where one keeps pulling the trigger of a gun with one bullet until it fires.

Decrypting PatchGuard Context

The responsibility of decrypting the first layer of the PatchGuard context structure lies with the exception handler. The decryption process involves two layers and a small trick. The first layer decrypts the entire structure, and then "a half" of it overwrites the header with hardcoded values. The second layer involves self-modifying code that decrypts the rest of the structure.

The encryption and decryption routines used in PatchGuard rely on random values stored in global variables called KiWaitNever and KiWaitAlways. These variables hold randomly generated values during boot time and are used by KiInitPatchGuardContext to encrypt the PatchGuard context structure.

Additional Checks

PspProcessDelete

Additional integrity checks can be found in specific functions like PspProcessDelete, where an integrity check is performed on the KeServiceDescriptorTable and KeServiceDescriptorTableShadow during process deletion.

PspProcessDelete

KiInitializeUserApc

Similar to PspProcessDelete, there is another function that includes an independent integrity verification for the Interrupt Descriptor Table (IDT). If a modification is detected, a DPC is injected with KiSchedulerDpc, which then triggers KeBugCheck.

KiInitializeUserApc

CcInitializeBcbProfiler

PatchGuard uses a hidden method to perform checks using the CcInitializeBcbProfiler function. This function first calculates the checksum of a random routine in the ntoskrnl.

CcInitializeBcbProfiler

The Verification Phase

This section discusses the different verification routines used in PatchGuard, including FsRtlMdlReadCompleteDevEx, which is the main routine.

Prologue

  1. PatchGuard begins by checking the integrity of the whole PatchGuard context structure
  2. PatchGuard proceeds to re-encrypt the first part of the PatchGuard context structure
  3. PatchGuard performs another checksum of part 2 and 3 from the context
  4. The wait (sleep) ensures that at least two minutes have elapsed between two checks
  5. The first part of the context is decrypted back without any additional steps
  6. A checksum is performed on part 2 and 3 of the context to ensure no modifications occurred
  7. The first 0x618 bytes of the context are checksummed
  8. PatchGuard determines the processor on which the check will run

When a Modification is Detected

After the checksum is completed, if a modification is detected, PatchGuard triggers a BSOD after performing some specific actions:

Conclusion

In conclusion, PatchGuard is an important security feature in the Windows operating system that helps protect against malicious attacks by preventing unauthorized modifications to the kernel. While it has been criticized by some for limiting access to certain kernel functions, it remains a key component of Windows security.