Windows 8.1 Kernel Patch Protection - PatchGuard
From time to time, usually on the second Wednesday of the month, you can hear stories that Windows after the next update stops loading, showing a blue screen of death. In most cases, the root cause is either a rootkit or a specific system software that frivolously manipulates the internal structures of the OS. The blame, of course, is the update anyway, because "everything worked before him." With this attitude, it is not surprising that Microsoft does not encourage the use of anything that is not documented. At some point, namely with the release of Windows Server 2003, MS took a more active stance on the issue of combating the wonder crafts of third-party developers. Then the kernel integrity protection mechanism, better known as PatchGuard, appeared.
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.
The most popular place to modify the kernel was the system call table. By modifying pointers to system call functions, it was easy to intercept, filter, log them, etc. Moreover, this patch was popular for both rootkits and anti-virus software. Other objects of interest for the patch are descriptor tables (GDT, IDT). By modifying the global descriptor table, it was possible to change segment attributes, creating backdoors for the code, and through the interrupt descriptor table it was possible to intercept ... interrupts! Advanced guys spliced directly the kernel functions.
Accordingly, the first version of PatchGuard protected:
With the development of NT, many core components have been processed, including PatchGuard. At the moment, it is already difficult to list everything that is protected with its help:
It is worth noting that PatchGuard actively uses the new exception handling implementation introduced in x64 versions of Windows. It is used both for obfuscation of 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 to store a special table inside the executable image with entries for each individual function.
Due to the fact that the address of the beginning and end of any function can be obtained directly in runtime, the task of calculating the checksum of an individual function becomes trivial. For comparison, in the x86 versions it is not possible to control the integrity of images due to the fact that it is not clear how to determine the boundaries of an individual function, and the entire image (or even its individual sections) cannot be covered with a checksum, since there are functions in the same kernel that patch 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, checksums of the protected structures and encryption keys of the context itself are stored. These contexts are stored in the non-paged pool in encrypted form. We'll talk about checking contexts 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 where you can create a PatchGuard context:
The first option always creates at least one context, while the second only in 4% of cases. Also, the first option is noteworthy in that it calls the KiFilterFiberContext function implicitly, namely through the “throwing” of an exception.
The sub_14071815C function obviously does not have a public symbol, since it is related to checking the OS license.
Below is the pseudocode of the KiFilterFiberContext function, which selects the method for checking a specific context and calls the function to create the context itself.
The function that creates the PatchGuard context is so obfuscated that automatic tools cannot cope with it, and researchers suddenly become uninterested in reversing it. In statics, this is a complete mess, 10K + lines of decompiled code "on the forehead" (decompilation itself in IDA Pro takes about 40 minutes).
Everything speaks of the extensive use of macros:
The dynamics are also quite complicated. Here are a couple of examples of what is scattered around the code.
Consider the parameters of the KiInitializePatchGuardContext function.
DPCs that call checking through an exception within themselves “look” to see if the DeferredContext parameter is a pointer to noncanonical memory. If the pointer is OK, the DPC does its legitimate job. Otherwise, the DPC calls a chain of recursive functions that ultimately lead to an exception (due to dereferencing of the noncanonical address) and the execution of its handler.
Checking the context consists of two stages: first, checking the structure of the context itself, which occurs at the DPC level, then it is planned to work item, which checks the protected structures in the system stream. If the check was successful, the old context is deleted and a new one is created in its place, which will be launched at a random time interval. If the check fails, PatchGuard clears all its traces, including zeroing the stack, and displays a blue screen with error code 0x109: CRITICAL_STRUCTURE_CORRUPTION.
Gifka with a self-decrypting context at the first stage of verification:
There are several approaches to neutralizing PatchGuard:
We liked the last method, since it is the most “clean” one: you don’t need to hook and post anything, you just need to replace the value of some variables.
These actions must be completed in 2 minutes for the reasons described above. The result - a context check will never be run, nor will a new one be planned. PatchGuard will not work.
When viewing the KiFilterFiberContext from the Windows 10 Technical Preview, we noticed a slight change. All the old planning methods have remained the same. However, a new one has appeared, which so far certainly returns STATUS_HV_FEATURE_UNAVAILABLE. After a little digging, we found the KiSwInterruptDispatch function, inside which the decryption and the context check call are obviously going on. Obviously, the ability to check contexts at the request of the Hyper-V hypervisor 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.
In the article, we tried not to indicate the names of specific functions, not because we feel sorry. It's simple: the names of the functions used to decrypt and verify contexts are intentionally changed by the PatchGuard developers and change in different versions of the OS.
Here is an example of a function name mismatching with what it really does. This is the same function, a copy of which is used for self-decryption of the context.
One thing is good - all these functions are nearby, so you can start with the KiFilterFiberContext function. Obviously, they all lie in the same source code file. However, kernel integrity checking is not limited to one PatchGuard. Macros are inserted into various parts of the kernel, which verify certain structures. Each such place has to be searched manually. Example:
With a probability of 50%, this function calculates the checksum for an arbitrary kernel function and plans to check it every 2 minutes in the DPC with the CcBcbProfiler function.
So good luck in your search! PatchGuard is interesting because it is fun to reverse;)
Help links:
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
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 guards PatchGuard
The most popular place to modify the kernel was the system call table. By modifying pointers to system call functions, it was easy to intercept, filter, log them, etc. Moreover, this patch was popular for both rootkits and anti-virus software. Other objects of interest for the patch are descriptor tables (GDT, IDT). By modifying the global descriptor table, it was possible to change segment attributes, creating backdoors for the code, and through the interrupt descriptor table it was possible to intercept ... interrupts! Advanced guys spliced directly the kernel functions.
Accordingly, the first version of PatchGuard protected:
- system call tables (SST),
- global descriptor table (GDT),
- interrupt descriptor table (IDT),
- kernel image
- nuclear stacks.
With the development of NT, many core components have been processed, including PatchGuard. At the moment, it is already difficult to list everything that is protected with its help:
- many system images, not just the kernel image (nt, hal, WerLiveKernelApi, tm, clfs, pshed, kdcom, bootvid, ci, msrpc, ndis, ntfs, tcpip, fltmgr),
- critical kernel data structures (e.g. process list),
- MSR set (for example, model specific register IA32_LSTAR),
- KdpStub is a debugger procedure that gets control after exceptions.
How guards PatchGuard
It is worth noting that PatchGuard actively uses the new exception handling implementation introduced in x64 versions of Windows. It is used both for obfuscation of 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 to store a special table inside the executable image with entries for each individual function.
typedefstruct _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 calculating the checksum of an individual function becomes trivial. For comparison, in the x86 versions it is not possible to control the integrity of images due to the fact that it is not clear how to determine the boundaries of an individual function, and the entire image (or even its individual sections) cannot be covered with a checksum, since there are functions in the same kernel that patch 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, checksums of the protected structures and encryption keys of the context itself are stored. These contexts are stored in the non-paged pool in encrypted form. We'll talk about checking contexts 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 where you can 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 noteworthy in that it calls the KiFilterFiberContext function implicitly, namely through the “throwing” of an exception.
KeInitAmd64SpecificState Function Pseudocode
__int64 KeInitAmd64SpecificState(){
signedint 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 sub_14071815C function obviously does not have a public symbol, since it is related to checking the OS license.
ExpLicenseWatchInitWorker function pseudo code
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 pseudocode of the KiFilterFiberContext function, which selects the method for checking a specific context and calls the function to create the context itself.
KiFilterFiberContext function 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();
returnResult;
}
The function that creates the PatchGuard context is so obfuscated that automatic tools cannot cope with it, and researchers suddenly become uninterested in reversing it. In statics, this is a complete mess, 10K + lines of decompiled code "on the forehead" (decompilation itself in IDA Pro takes about 40 minutes).
Everything speaks of the extensive use of macros:
- even the simplest operation, such as taking a random number, is spread out over 50+ lines of assembler code;
- all cycles are deployed;
- a lot of dead code has been inserted;
- indirect access to variables and external functions is used.
The dynamics are also quite complicated. 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 debugging trick 1 do?
When a debugger is connected, it enters an endless uninterrupted cycle.
cli
sidt fword ptr [rbp+320h]
lidt fword ptr [rbp+228h]
mov dr7, r13
lidt fword ptr [rbp+320h]
sti
What does anti-debugging trick 2 do?
Загружает временную невалидную таблицу дескрипторов прерываний. Если мы следили за доступом к отладочным регистрам, произойдет отладочное исключение, которое при данных условиях приведет к tripple fault с последующей перезагрузкой.
Consider the parameters of the KiInitializePatchGuardContext function.
- The DPC index of the function that will be called to check the context and may be one of the following:
- KiTimerDispatch
- KiDpcDispatch
- ExpTimerDpcRoutine
- IopTimerDispatch
- IopIrpStackProfilerTimer
- PopThermalZoneDpc
- CmpEnableLazyFlushDpcRoutine
- CmpLazyFlushDpcRoutine
- KiBalanceSetManagerDeferredRoutine
- ExpTimeRefreshDpcRoutine
- ExpTimeZoneDpcRoutine
- ExpCenturyDpcRoutine
- Inspection Planning Method:
- KeSetCoalescableTimer
A timer object is created that starts the scan after 2m: 05s ± 5 s. - Prcb.AcpiReserved
DPC will fire when a certain ACPI event occurs , for example, when it enters a low-power state. Will work no sooner than after 2m: 05s ± 5 s. - Prcb.HalReserved
DPC will fire when the HAL timer ticks. Not earlier than after 2m: 05s ± 5 s. - PsCreateSystemThread
A separate system thread is created, sleeping 2m: 05s ± 5 s. After this, a context check is called. - KeInsertQueueApc
A regular kernel APC is created, which works immediately, but waits 2m: 05s ± 5s inside the work item. - KiBalanceSetManagerPeriodicDpc
DPC will work according to the timer of the balancing manager, not earlier than after 2m: 05s ± 5 s.
- KeSetCoalescableTimer
- The purpose of the parameter is not completely clear, it is only known that it affects the number of checks in the context.
- A parameter specific to the selected planning method.
- The parameter that reports the need to recalculate the checksums for the context.
DPCs that call checking through an exception within themselves “look” to see if the DeferredContext parameter is a pointer to noncanonical memory. If the pointer is OK, the DPC does its legitimate job. Otherwise, the DPC calls a chain of recursive functions that ultimately lead to an exception (due to dereferencing of the noncanonical address) and the execution of its handler.
Sequences of recursive function calls 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
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
Checking the context consists of two stages: first, checking the structure of the context itself, which occurs at the DPC level, then it is planned to work item, which checks the protected structures in the system stream. If the check was successful, the old context is deleted and a new one is created in its place, which will be launched at a random time interval. If the check fails, PatchGuard clears all its traces, including zeroing the stack, and displays a blue screen with error code 0x109: CRITICAL_STRUCTURE_CORRUPTION.
Gifka with a self-decrypting context at the first stage of verification:
How to win
There are several approaches to neutralizing PatchGuard:
- Such a kernel image patch so that PatchGuard does not initialize at all.
- Patch context verification procedures.
- KeBugCheck hook with system state recovery.
- Canceling scheduled inspections is what we implemented.
We liked the last method, since it is the most “clean” one: you don’t need to hook and post anything, you just need to replace the value of some variables.
- KeSetCoalescableTimer
It is necessary to scan all timers for which the DPC will contain a DeferredContext with a non-canonical address, and increase the waiting interval for those found to infinity. - Prcb.AcpiReserved
Just zero this field. - Prcb.HalReserved
Just zero this field. - PsCreateSystemThread
Scan sleeping threads and spin their stack. If it rests on a function from the KiServiceTablesLocked structure, this is our client. Set the hibernation time to infinity. - KeInsertQueueApc
Scan all worker threads with stack promotion. If there are functions on the stack that are not from the kernel code section, and which are unwound using the data for the FsRtlMdlReadCompleteDevEx and FsRtlUninitializeSmallMcb functions, this is exactly the PatchGuard workflow. We neutralize the same as in the previous version. - KiBalanceSetManagerPeriodicDpc
Restore the "legal" procedure - KiBalanceSetManagerDeferredRoutine.
These actions must be completed in 2 minutes for the reasons described above. The result - a context check will never be run, nor will a new one be planned. PatchGuard will not work.
Windows 10
When viewing the KiFilterFiberContext from the Windows 10 Technical Preview, we noticed a slight change. All the old planning methods have remained the same. However, a new one has appeared, which so far certainly returns STATUS_HV_FEATURE_UNAVAILABLE. After a little digging, we found the KiSwInterruptDispatch function, inside which the decryption and the context check call are obviously going on. Obviously, the ability to check contexts at the request of the Hyper-V hypervisor 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 goes on
In the article, we tried not to indicate the names of specific functions, not because we feel sorry. It's simple: the names of the functions used to decrypt and verify contexts are intentionally changed by the PatchGuard developers and change in different versions of the OS.
Here is an example of a function name mismatching with what it really does. This is the same function, a copy of which is used for self-decryption of the context.
One thing is good - all these functions are nearby, so you can start with the KiFilterFiberContext function. Obviously, they all lie in the same source code file. However, kernel integrity checking is not limited to one PatchGuard. Macros are inserted into various parts of the kernel, which verify certain structures. Each such place has to be searched manually. Example:
... --> Phase1InitializationDiscard --> CcInitializeCacheManager --> CcInitializeBcbProfiler
With a probability of 50%, this function calculates the checksum for an arbitrary kernel function and plans to check it every 2 minutes in the DPC with the CcBcbProfiler function.
So good luck in your search! PatchGuard is interesting because it is fun to reverse;)
Help links:
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