PatchGuard Analysis - Part 2
Arguments of KiInitPatchGuardContext
Now, we can see another Arguments of the function.
- Argument 1: DPC Routine Pointer
As we have seen before, the several method used a DPC Structure to hide PatchGuard and queue it. This DPC actually contains a pointer to function that is known to unqueue DPC, and will perform specific operation when the DPC is actually a PatchGuard one.
The first argument is an index to choose a routine randomly and this routine will be set as one of these routines:
Index | Routine |
---|---|
0 | CmpEnableLazyFlushDpcRoutine |
1 | ExpCenturyDpcRoutine |
2 | ExpTimeZoneDpcRoutine |
3 | ExpTimeRefreshDpcRoutine |
4 | CmpLazyFlushDpcRoutine |
5 | ExpTimerDpcRoutine |
6 | IopTimerDispatch |
7 | IopIrpStackProfilerDpcRoutine |
8 | KiBalanceSetManagerDeferredRoutine |
9 | PopThermalZoneDpc |
10 | KiTimerDispatch OR KiDpcDispatch |
11 | KiTimerDispatch OR KiDpcDispatch |
12 | KiTimerDispatch OR KiDpcDispatch |
This operation is performed at 0x140BF6426:

To clear understand, i created a Pseudocode:
DWORD Argument0 = *(DWORD*)((rsp + 0x2568) + Arg0);
if (Argument0 <= 6) {
switch(Argument0) {
case 0:
RoutinePointer = CmpEnableLazyFlushDpcRoutine;
break;
case 1:
RoutinePointer = ExpCenturyDpcRoutine;
break;
case 2:
RoutinePointer = ExpTimeZoneDpcRoutine;
break;
case 3:
RoutinePointer = ExpTimeRefreshDpcRoutine;
break;
case 4:
RoutinePointer = CmpLazyFlushDpcRoutine;
break;
case 5:
RoutinePointer = ExpTimerDpcRoutine;
break;
case 6:
RoutinePointer = IopTimerDispatch;
break;
}
}
And finally, let’s check 0x140BF5AEE:

Pseudocode of the function:
switch(eax) {
case 7:
RoutinePointer = IopIrpStackProfilerDpcRoutine;
break;
case 8:
RoutinePointer = KiBalanceSetManagerDeferredRoutine;
break;
case 9:
RoutinePointer = PopThermalZoneDpc;
break;
}
If the second argument is less than 3, these routines KiTimerDispatch and KiDpcDispatch is used.
As we have seen before in KiFilterFiberContext, the first parameter is generally chosen randomy except for the last call of KiInitPatchGuardContext where it is 0 (CmpEnableLazyFlushDpcRoutine), but it isn’t used by the initialization function, as we will see later.
- Argument 3: Random Value To Determine The Total Size of Data To Check
As have seen before, the argument 3 can be one or two value (Check the header of Decompiling KilFilterFiberContext). This value is actually used to divide the hardcoded value 0x140000 and the result is passed to the structure. We can examine it at 0x140BD6F20:

After the divide operation, it passes the result the offset 0x84C of the structure and this value is used to determine the maximum size of data to checksum at each PatchGuard check. The main idea of PatchGuard is that it use a list of structures to check the integrity and after each checksum a counter is incremented by the size of the data.
- Argument 5: Boolean for ntoskrnl functions integrity check
The Argument 5 is a boolean value and with this parameter, it decides whether or not ntoskrnl functions checksum should be performed:

sil register contains boolean value and it is compared with 1 value. The checksum result is then stored in the PatchGuard context as every other Windows Kernel structures that are to be checked by PatchGuard.
Other Initilization Methods
Now we can examine other method that directly initialize PatchGuard.
TV Callback from KiFilterFiberContext
As we saw before in KilFiterFiberContext, there’s a callback function (KiFilterFiberContext+0x104):
mov rcx, [rbp+57h+CallbackObject] ; CallbackObject
lea r8, $$24 ; Argument2
lea rdx, sub_140510650 ; Argument1
call ExNotifyCallback
In this method, it is used global PatchGuard context structure:
sub_140510650:
...
mov rsi, cs:MaxDataSize
test rsi, rsi
jnz short loc_1405106A5
mov eax, 0C00000A3h ; 0xC00000A3 == STATUS_DEVICE_NOT_READY
jmp loc_14051ED9F
CcInitializeBcbProfiler
PatchGuard uses an hidden way to perform checks with CcInitializeBcbProfiler, and this function starts by computing the checksum of a random ntoskrnl routine.
This function sets up a DPC with the routine CcBcbProfiler and an important structure. We can trace this structure from 0x140BD1606:
struct pg_CcInitializeBcbProfiler
{
KDPC_ kdpc;
KTIMER timer;
ULONG64 res_RtlpLookupPrimaryFunctionEntry
ULONG64 hardcoded_140000000h
ULONG32 func_size_0x90;
ULONG32 padding_0x94;
ULONG64 checksum_function_0x98;
ULONG64 random_1_0xa0;
ULONG32 random_2_0xa8;
bool_CcBcbProfiler_or_sub_140499010_0xac;
ULONG64 KiAreCodePatchesAllowed;
struct _LIST_ENTRY_ workitem_List;
void* workitem_WorkerRoutine_sub_1406F86E0
void* workitem_Parameter_CurrentStruct;
};
Notice that this structure contains everything to compute the checksum of the random routine:
- Pointer of the Function Entry
- Base address of the image
- Size of the function
- Checksum
- Random values used at seed for the checksum
The DPC is queued by KeSetCoalescableTime and DueTime parameter is set between 2 - 2'10 at 0x140BD18D6:

Or i created a Pseudocode:
/* Get initial timestamp and generate obfuscated value */
RandomValue = __rdtsc();
RotatedValue = __ROR8__(RandomValue, 3);
XoredValue = RotatedValue ^ RandomValue;
/* Calculate TimeParameter using a specific multiplier and modulo */
TimeParameter.QuadPart = -1200000000
- ((0x7010008004002001 * XoredValue)
^ ((XoredValue * 0x7010008004002001) >> 64))
% 0x5F5E100;
/* Get another timestamp */
SecondTimestamp = __rdtsc();
SecondXored = __ROR8__(SecondTimestamp, 3) ^ SecondTimestamp;
PeriodValue = ((0x7010008004002001 * SecondXored)
^ ((SecondXored * 0x7010008004002001) >> 64))
% 0x2710;
/* Set the coalescable timer with calculated parameters */
LOBYTE(Pool2) = KeSetCoalescableTimer(
Dpc + 1, // Timer object
TimeParameter, // Due time
0, // Tolerance
PeriodValue, // Period
Dpc // DPC routine
);
PspProcessDelete
The another integrity verification can also performed by another ntoskrnl.exe functions, such as PspProcessDelete. Don’t be fooled by the name ‘PspProcessDelete’, believe me that this function does more than that.
Althought it is just a small piece of verification, here an integrity check will be performed independently on KeServiceDescriptorTable or KeServiceDescriptorTableShadow without any PatchGuard context structure or any system thread. There are the original checksum of the both table and shift value necessary to compute in the global variable, so if an attacker wants to patch an entry of the Descriptor Table (Shadow or not), then computing again the checksum and replacing the original one is completely feasible.
First of all, a random value is generated with KiQueryUnbiaisedInterruptTime at 0x1407D430C:

And this timer is stored at 0x140E620C0. The checksum results are stored in 0x140E620C8, 0x140E620D0 and 0x140E620D8 and these checksums are initialized in CmpInitializeDelayedCloseTable function (also the timer is initialized in this function). For instance, we can check one of them at 0x1407D4245:

One important detail is that if one of these checksum fails, then a KeBugCheck is triggered through a Dpc inserted with KiSchedulerDpc at 0x140897E11:

The shift value is stored in 0x140E620B8 and it is initialized in 0x1407D41DA address of CmpInitializeDelayedCloseTable:

Well, we can examine one check with IDA and Windbg. Here’s our target:

First of all, r9 contains address of KeServiceDescriptorTable:
kd> t
nt!PspProcessDelete+0x408:
fffff802`a7772448 410fb601 movzx eax,byte ptr [r9]
kd> r r9
r9=fffff802a7f818c8
kd> u fffff802a7f818c8 L1
nt!KeServiceDescriptorTable+0x8:
fffff802`a7f818c8 0000 add byte ptr [rax],al
edi contains the shift value. We can see this in IDA (0x140897D0D):
loc_140897CC7:
mov rdi, cs:qword_140E620B8
Shift value is actually 0x70f59b2a
kd> t
nt!PspProcessDelete+0x40c:
fffff802`a777244c 8bcf mov ecx,edi
kd> r edi
edi=70f59b2a
but in the loop, it uses 0xc2:
kd> t
nt!PspProcessDelete+0x414:
fffff802`a7772454 48d3ca ror rdx,cl
kd> r cl
cl=2a
And r10d register contains 0x38 value as loop counter. This value is taken from 0x140897D09:
lea r10d, [r15+40h] ; pass 0x38 value to r10d
Or:
kd> t
nt!PspProcessDelete+0x417:
fffff802`a7772457 4183c2ff add r10d,0FFFFFFFFh
kd> r r10d
r10d=38
In this section the r10d value will be reduced by one.
Lastly, here are checksums which initialized by CmpInitializeDelayedCloseTable:
kd> dps fffff802a7be2108 L1
fffff802`a7be2108 72e10000`09fcd74a
kd> dps fffff802a7be2110 L1
fffff802`a7be2110 620ad43a`aeef4fce
kd> dps fffff802a7be2118 L1
fffff802`a7be2118 fcd74a72`e1000009
To disable this method, one can just patch the timer to infinity or compute again the checksum of the modified table.
KiInitializeUserApc
Like PspProcessDelete function, this function contains small piece of code to check the integrity for the IDT. The checksum is stored at 0x140E62170, shift value is stored at 0x140E62178 and the timer is stored at 0x140E62180.
We can start by examining 0x1403DC674 address with WinDBG and IDA:

With sidt instruction, it gets 10 bytes from IDT, then it gets base address of IDT:
kd> t
nt!KiInitializeUserApc+0x3c8:
fffff804`e4895430 0f018c24a8000000 sidt tbyte ptr [rsp+0A8h]
kd> r r10
r10=fffff80476b5e000
kd> r idtr
idtr=fffff80476b5e000
8 bytes contains the base address. After that it starts the loop for checksum. We can check the values of the shift and checksum:
kd> t
nt!KiInitializeUserApc+0x415:
fffff804`e489547d 8b0d35cda500 mov ecx,dword ptr [nt!MiSystemPartition+0x299b8 (fffff804`e52f21b8)]
kd> dps fffff804`e52f21b0 L1
fffff804`e52f21b0 00000000`560451ca ; Checksum
kd> dps fffff804`e52f21b8 L1
fffff804`e52f21b8 00000000`5d83c49b ; Shift Value
After the loop, it checks the result of the checksum at 0x1403DC88E:

Or:
kd> bp fffff804`e48954ba
kd> g
Breakpoint 5 hit
nt!KiInitializeUserApc+0x452:
fffff804`e48954ba 48391507cda500 cmp qword ptr [nt!MiSystemPartition+0x299c8 (fffff804`e52f21c8)],rdx
kd> dps fffff804`e52f21c8 L1
fffff804`e52f21c8 2884d739`2257ef7f
kd> r rdx
rdx=2884d7392257ef7f
rdx register contains the result.
Let’s do something crazy and change the value of rdx:
kd> r rdx
rdx=2884d7392257ef7f
kd> r rdx = 2884d7392257ef7a
kd> g
ANDDDDD BOOOM:

Okaay, I was just bored. Let’s keep going.
As we seen before from PspProcessDelete, if there is a problem with the check, it calls KeBugCheck via DPC (0x1403DC8DF):

Exception Worlds: KiVerifyXcpt15
KiInitPatchGuardContext can also called from KiVerifyXcpt15. But in here, some interesting technique is used.
If you check all codes of the KiVerifyXcpt15, you’ll see this simple codes:

It is simple, right? This is the visible front of the mountain. Now let’s take a look at the back:

- Calling KiVerifyXcpt15
KiVerifyXcpt15 is called before KeInitAmd64SpecificState and ExpLicenseWatchInitWorker, and essentially KiVerifyXcpt15 is called from KiVerifyScopesExecute.
KiVerifyScopesExecute calls not only KiVerifyXcpt15 but also other functions from a structure which named KiVerifyXcptRoutines. Here are the functions in the list (0x1410060D8):
Index | Routine |
---|---|
0 | KiVerifyXcpt0 |
1 | KiVerifyXcpt1 |
2 | KiVerifyXcpt15 |
3 | KiVerifyXcpt3 |
4 | KiVerifyXcpt4 |
5 | KiVerifyXcpt5 |
6 | KiVerifyXcpt6 |
7 | KiVerifyXcpt7 |
8 | KiVerifyXcpt8 |
9 | KiVerifyXcpt9 |
10 | KiVerifyXcpt10 |
11 | KiVerifyXcpt11 |
11 | KiVerifyXcpt12 |
12 | KiVerifyXcpt13 |
13 | KiVerifyXcpt14 |
The beginning of the function it gets data from KiVerifyPass variable which holds 0xA value at 0x140C1C2FD:
KiVerifyScopesExecute proc near:
...
mov ebx, cs:KiVerifyPass ; Get data of KiVerifyPass
This variable is actually important because this variable will used for the loop, as we will see later.
After that it compares the value which taken from the variable with 0. If the variable is not zero, then it calls the routines from the structure at 0x140C1C314:

After gets the address of the structure, it calls the routines from the structure:

If the all routines are called, KiVerifyPass variable subtract 1 at 0x140C1C371:
loc_140C1C371:
dec ebx
jmp short loc_140C1C314
Then it jumps the beginning of the loop (0x140C1C314) and again it calls the routines. In other words, It will call these routines until KiVerifyPass is zero.
I have prepared a diagram for clarity:

- Disassembling KiVerifyXcpt15
The beginning of the function, it calls KiVerifyXcpt2 (Probably this function does original verifies). Then it calls the main function (0x140C6023D) with an exception.
As we’ve already seen, here, KiVerifyPass is processed. Firstly it gets the current data from the KiVerifyPass and decrements it:
loc_140C6023D:
...
mov eax, cs:KiVerifyPass ; Get Data
dec eax ; Decrements eax
mov cs:KiVerifyPass, eax ; Store the result in KiVerifyPass
This variable decreases until it becomes zero. If this variable is not zero, the function exits:
loc_140C6023D:
mov eax, cs:KiVerifyPass ; Get 0xA
dec eax
mov cs:KiVerifyPass, eax ; Pass the result to KiVerifyPass
mov eax, cs:KiVerifyPass
test eax, eax
jnz loc_140C60EA0 ; Exit the function
When KiVerifyPass reach zero, it reads an MSR. Specifically, it adds 0x1B (27 decimal, the MSR address for IA32_APIC_BASE):

The IA32_APIC_BASE located at address 0x1B, is a critical control register in Intel processors that manages the configuration and operation of the local Advanced Programmable Interrupt Controller (APIC)[3][4].
Then it checks the 10th bit of the result. This 10th byte of IA32_APIC_BASE corresponds to x2APIC Mode status in Intel Processors. From Intel® 64 Architecture x2APIC Specification documentation:[3]
Note
“System software can place the local APIC in the x2APIC mode by setting the x2APIC mode enable bit (bit 10) in the IA32_APIC_BASE MSR at MSR address 01BH.”
Also we can benefit from Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 4: Model-Specific Registers[4]:

If the 10th bit is not set, it maps an virtual address with MmMapIoSpaceEx (0x140C6028E):
loc_140C6028E:
mov rax, 0FFFFFF000h
and rbx, rax
mov edx, 1000h ; Protect
mov r8d, 204h ; Size
mov rcx, rbx
call MmMapIoSpaceEx
test rax, rax
jnz short loc_140C602DA ; Pass the address to the variable
and then this virtual address - or zero value - is passed to global variable (qword_141006090):
loc_140C602DA:
mov cs:qword_141006090, rax
...
i also created a pseudocode:
void func_140C6023D() {
int UpdateVerifyPassData = KiVerifyPass - 1;
if (UpdateVerifyPassData == 0x0) {
int number = 0x1B;
ULONG64 MappedVirtualAddress = 0;
ULONG64 GlobalVirtualAddress = 0;
/* Read MSR */
ULONG64 ResultAddress = rdmsr(number);
if ([ResultAddress+0x10] == 0x0) {
MappedVirtualAddress = MmMapIoSpaceEx(ResultAddress, 0x1000, 0x204);
if (MappedVirtualAddress != 0x0) {
goto SetGlobalVariable;
}
MappedVirtualAddress = 0x0;
}
SetGlobalVariable:
GlobalVirtualAddress = MappedVirtualAddress;
}
}
This virtual address is taken by KiInitPatchGuardContext and the passed the address to the context structure at 0x140BEA132:
loc_140BEA10F:
...
mov rax, cs:qword_141006090
mov [rdi+0A58h], rax
Then it checks the first byte of the hardcoded address 0x0FFFFFFF78000000289:
loc_140C602DA:
mov cs:MappedVirtualAddress, rax
mov rax, 0FFFFF78000000289h
cmp byte ptr [rax], 0
jz short loc_140C6034D
We can use WinDBG to see the byte of the address:
kd> dps 0FFFFF78000000289 l1
fffff780`00000289 01000000`00010100
If the byte isn’t 0, then KiSwInterruptPresent is executed and the random value is generated. The random value is used to determine whether a variable (dword_141006088) should be set to 1 or not:

This variable is then used to determine whether the KiInitPatchGuardContext should be redirected to the part that prepares for the call:
cmp cs:dword_141006088, 0
jz loc_140C60D7B
If the first byte of the hardcoded address 0FFFFF78000000289 is 0 then this variable will be 0 and it will start the first steps for KiInitPatchGuardContext, but if it is 1 then it will continue with another part that does different operations.
We can focus on the 0x140C60E1D address. A random value is created and then the values from the debugger flags are taken, as we saw before in KeInitAmd64SpecificState:

After a few operations, the random value is compared with the result of the flags. If the result of the flags (r9) is greater than the random value (r10), it disables Debugger and calls KiInitPatchGuardContext with an exception. Otherwise, it jumps to exit:
cmp r10, r9
jnb short loc_140C60EA0
call KdDisableDebugger
mov [rbp+0A4h], eax
; Call KiInitPatchGuardContext
loc_140C60E8C:
lea rdx, loc_140BD11D4
mov rcx, [rbp+0C8h]
call _local_unwind
nop
; Exit
loc_140C60EA0:
add rsp, 30h
pop r15
pop r14
pop r12
pop rdi
pop rsi
pop rbp
pop rbx
retn
Let’s take a look at this part with dynamic analysis:
kd> g
Breakpoint 1 hit
nt!KeInitAmd64SpecificState$filt$0+0xc86:
fffff800`7d174e82 4d3bd1 cmp r10,r9
kd> r r10
r10=000000000000003b
kd> g
Breakpoint 1 hit
nt!KeInitAmd64SpecificState$filt$0+0xc86:
fffff800`7d174e82 4d3bd1 cmp r10,r9
kd> r r10
r10=0000000000000041
Seems like r10 always gets a higher value than r9. therefore the condition is not met. But i changed the r9’s value:
kd> r r9 = 45
kd> t
nt!KeInitAmd64SpecificState$filt$0+0xc8b:
fffff800`7d174e87 e8c45493ff call nt!KdDisableDebugger (fffff800`7caaa350)
Like i said, for the condition to be met, r9’s value is greater than r10.
The interesting part is that the arguments of the KiInitPatchGuardContext are not randomized (0x140C60EFE):

Except ecx. This register is taken random value for DPC Routine Pointer. Here’s dynamic analysis:
kd> g
Breakpoint 4 hit
nt!KiFilterFiberContext+0x2916d:
fffff802`a666e69d e80e8afdff call nt!KiFilterFiberContext+0x1b80 (fffff802`a66470b0)
kd> r
rax=0000000000000002 rbx=ffffa60199a5fdd8 rcx=0000000000000003
rdx=0000000000000000 rsi=0000000000000000 rdi=ffffa60199a5fdd8
rip=fffff802a666e69d rsp=ffffa60199a5d360 rbp=ffffa60199a5fd90
r8=0000000000000001 r9=0000000000000000 ...
kd> ? rsp+20h
Evaluate expression: -98949173750912 = ffffa601`99a5d380
kd> dps ffffa601`99a5d380 l1
ffffa601`99a5d380 ffffa601`00000002
Or:
- RCX: 3 (As Argument 1)
- RDX: 0 (As Argument 2)
- R8: 1 (As Argument 3)
- R9: 0 (As Argument 4)
- RSP+0x20: 2 (As Argument 5)
With these information, we can now get rid of this complex function. The function content is really long…
KeCheckedKernelInitialize
Finally, KiInitPatchGuardContext can be called from KeCheckedKernelInitialize. Apparently, this function is actually coded for PatchGuard itself. Here, KiInitPatchGuardContext is called with method 7.
First of all, this function uses Global PatchGuard Context (Check the Method 7 Header), and if the global context structure is 0, it jumps directly to exit without calling KiInitPatchGuardContext:

This function uses an anti-debugging technique which used by KiInitPatchGuardContext. After the check of the global context structure, the anti-debugging function is called (I renamed ‘InterestingAntiDebuggingStuff’). The Anti-Debugging function is actually simple:

This code implements an anti-debugging technique by attempting to execute the LIDT (Load Interrupt Descriptor Table) instruction inside a __try/__except block.
By checking whether an exception is raised, the program can detect the presence of a debugger or virtualized environment. If the exception is triggered, eax is set to 1 (indicating a restricted environment); otherwise, it remains 0, suggesting normal execution without interference.
And then you can see this message from VM:

I got this message from VMware after the lidt instruction. This technique is also used by KiInitPatchGuardContext at 0x140BD36A2.
As we saw before from KiVerifyXcpt15, the parameters of the KiInitPatchGuardContext are not randomized:

This calling methos is exactly same with the third call of KiInitPatchGuardContext in KiFilterFiberContext as we will see later. From 0x140BD1DD8:

KeCheckedKernelInitialize is called after the KeInitAmd64SpecificState function. If the KeHotpatchTestMode flag is not 0 (Essentially this variable is set to 0 in ntoskrnl), then it calls KeCheckedKernelInitialize. We can check this section at 0x140C55A71 of PipInitializeCoreDriversAndElam function:
loc_140C55A3A
...
call KeInitAmd64SpecificState
cmp cs:KeHotpatchTestMode, 0
jz short loc_140C55A84
call KeCheckedKernelInitialize
That’s all there is to the initialization methods. Now in the Part 3, we’ll take a look at how the controls are triggered, which is really an important topic.