PatchGuard Analysis - Part 2
Arguments of the KiInitPatchGuardContext
Şimdi ise diğer kalan argümanların ne işe yaradığına göz atalım:
- Argument 1: DPC Routine Pointer
Daha önce de gördüğümüz gibi, çeşitli methodlar PatchGuard’ı gizlemek ve sıraya almak için bir DPC yapısı kullanmıştı. Bu DPC aslında, DPC’yi sıradan çıkaran bir işlevi gösteren bir pointer içerir ve bu DPC gerçekten bir PatchGuard DPC’si olduğunda özel bir işlem gerçekleştirir.
İlk argüman esasen rastgele bir rutin seçmek için bir index’tir ve bu rutinlerden birini seçecektir:
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 |
Bu işlem 0x140BF6426 adresinde gerçekleştirilir:

Daha basit anlaşılması için bir Pseudocode oluşturdum:
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;
}
}
Ve son olarak 0x140BF5AEE adresini kontrol edelim:

Fonksiyonun Pseudocode’su:
...
switch(Argument0) {
case 7:
RoutinePointer = IopIrpStackProfilerDpcRoutine;
break;
case 8:
RoutinePointer = KiBalanceSetManagerDeferredRoutine;
break;
case 9:
RoutinePointer = PopThermalZoneDpc;
break;
}
KiFilterFiberContext’de KiInitPatchGuardContext’in üçüncü çağrısını yaparken sabit olarak 0 (CmpEnableLazyFlushDpcRoutine) verilmesi dışında ilk parametre genellikle rastgele seçilir, ancak daha sonra göreceğimiz gibi initilization fonksiyonu tarafından kullanılmayacaktır.
- Argument 3: Random Value To Determine The Total Size of Data To Check
Argüman 3 esasen bir veya iki değer olabilir (Decompiling KilFilterFiberContext başlığını kontrol edin). Bu değer aslında sabit kodlanmış 0x140000 değerini bölmek için kullanılır ve sonuç direkt olarak yapıya aktarılır. 0x140BD6F20 adresinden bunu inceleyebiliriz:

Bölme işleminden sonra, sonuç hızlı bir şekilde yapının 0x84C ofsetine geçirilir ve bu değer her PatchGuard kontrolünde checksum yapılacak maksimum veri boyutunu belirlemek için kullanılır. PatchGuard’ın ana fikri, bütünlüğü kontrol etmek için bir yapı listesi kullanması ve her checksum sonra bir sayacın verinin boyutu kadar artırılmasıdır.
- Argument 5: Boolean for ntoskrnl functions integrity check
Argüman 5 bir boolean değeridir ve bu parametre ile ntoskrnl fonksiyonları için checksum gerçekleştirilip gerçekleştirilmeyeceğine karar verir:

sil register argüman 5’i tutar ve 1 değeri ile karşılaştırılır. Cheksum sonucu ise daha sonra PatchGuard tarafından kontrol edilecek diğer Windows Kernel yapıları gibi PatchGuard context’inde saklanır.
Other Initilization Methods
Artık şimdi PatchGuard’ı direkt olarak initialize eden methodları inceleyebiliriz.
TV Callback from KiFilterFiberContext
KiFilterFiberContext’te gördüğümüz callback functionu tekrar hatırlayalım. KiFilterFiberContext+0x104‘dan:
mov rcx, [rbp+57h+CallbackObject] ; CallbackObject
lea r8, $$24 ; Argument2
lea rdx, sub_140510650 ; Argument1
call ExNotifyCallback
Bu method’da esasen Method 7’de gördüğümüz Global PatchGuard Context yapısını kullanılmakta:
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 kontrolleri gerçekleştirmek için gizli yollara başvurmakta. CcInitializeBcbProfiler fonksiyon içinde PatchGuard tarafından bazı gizli kontroller gerçekleştiriliyor. Fonksiyonda random ntoskrnl rutinlerin checksum’larını kontrol etmekte. Fonksiyon CcBcbProfiler aracılığıyla bir DPC ve önemli yapı oluşturmakta. Bu yapının izini 0x140BD1606 adresinde sürebiliriz:
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;
};
Bu yapının random rutinin checksum kontrolleri için gerekli tüm alanları kapsadığına dikkat edin. İşte önemli üyeler:
- Fonksiyonun Başlangıç Adresinin Pointer’i
- Image’in Base Adresi
- Fonksiyonun boyutu
- Checksum
İlgili DPC KeSetCoalescableTime aracılığıyla sıraya alınır DueTime parametresi 2 - 2'10 arasında ayarlanır. Bu kısmı 0x140BD18D6 adresinde görebiliriz:

Veya daha anlaşılır olması için bir Pseudocode oluşturdum:
/* 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
Bir başka gizli integrity kontrolü PspProcessDelete fonksiyonunda gerçekleştirilir. Bu fonksiyonun ismi sizi yanıltmasın, güvenin bana bu fonksiyon isminden çok büyük işler yapıyor…
Küçük bir kontrol kodları içermesine karşın burada önemli bir tablo olan KeServiceDescriptorTable veya KeServiceDescriptorTableShadow için kontroller gerçekleştirilmekte. Buradaki önemli bir detay ise bu kontroller hiçbir şekilde bir thread veya PatchGuard context yapısı aracılığıyla gerçekleştirilmiyor; bağımsız ve direkt olarak kontroller yapılıyor. Bu gerçekten önemli çünkü bu kontrollerin gerçekleştirilmesi için iki tablonun orijinal checksum değerleri ve kaydırma değerleri global değişkenlerde tutulamakta ve işlemler sırasında bu global değişkenler kullanılıyor. Bir saldırgan gözüyle bakarsak, bu SSDT tablolarında oynama yapmak istersek çok da zorlanmayacağımız anlamına gelir. Herhangi bir tablonun orijinal checksum değerini tekrar hesaplayıp ardından orijinal adresini değiştirmemiz yeterli olacaktır. Bu saldırganın amacına göre değişiklik gösterebilir.
İlk olarak KiQueryUnbiaisedInterruptTime fonksiyon aracılığıyla bir random değer oluşturuluyor. Bu işlemi 0x1407D430C adresinden görebiliriz:

Ve bu timer 0x140E620C0 adresinde depolanıyor. Bahsettiğim önemli bilgiler şu adreste tutulmakta: Checksum sonuçları 0x140E620C8, 0x140E620D0 ve 0x140E620D8 adreslerinde tutulmakta ve bu checksum’lar CmpInitializeDelayedCloseTable fonksiyonunda initialize edilmekte (ayrıca timer’da burada ayarlanıyor). Örnek olarak herhangi birine göz atabiliriz (0x1407D4245):

Bir önemli detay da eğer herhangi bir checksum kontrolü başarısız olursa KeBugCheck tetiklenecektir ancak direkt olarak değil KiSchedulerDpc ile yerleştirilmiş bir DPC aracılığıyla fonksiyon tetiklenecektir. Bunu 0x140897E11 adresinden görebiliriz:

Kaydırma değeri 0x140E620B8 adresinde tutulmakta ve bu değer CmpInitializeDelayedCloseTable fonksiyonun 0x1407D41DA adresinde hazırlanmaktadır:

Şimdi ise bu fonksiyondan herhangi bir kontorlü IDA ve WinDBG ile göz atabiliriz. İşte hedefimiz şu kısım:

İlk olarak r9 KeServiceDescriptorTable adresini almakta:
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 register’ı kaydırma değerini tutmakta, esasen bunu IDA’dan 0x140897D0D adresinde görebiliriz:
loc_140897CC7:
mov rdi, cs:qword_140E620B8
Kaydırma değeri ise 0x70f59b2a olduğunu görebiliriz:
kd> t
nt!PspProcessDelete+0x40c:
fffff802`a777244c 8bcf mov ecx,edi
kd> r edi
edi=70f59b2a
Ancak kontrol esnasında 0xc2 kullanılmakta:
kd> t
nt!PspProcessDelete+0x414:
fffff802`a7772454 48d3ca ror rdx,cl
kd> r cl
cl=2a
r10 register kontrol esnasında döngü sayacını tutmakta ve 0x38 değerinden başlıyor. Bu sayaç 0x140897D09 adresinden alınıyor:
lea r10d, [r15+40h] ; pass 0x38 value to r10d
Veya:
kd> t
nt!PspProcessDelete+0x417:
fffff802`a7772457 4183c2ff add r10d,0FFFFFFFFh
kd> r r10d
r10d=38
Her döngü sonunda r10d değeri bir azaltılacaktır.
Son olarak ise CmpInitializeDelayedCloseTable tarafından hazırlanan orijinal checksum değerlerini görebiliriz:
kd> dps fffff802a7be2108 L1
fffff802`a7be2108 72e10000`09fcd74a
kd> dps fffff802a7be2110 L1
fffff802`a7be2110 620ad43a`aeef4fce
kd> dps fffff802a7be2118 L1
fffff802`a7be2118 fcd74a72`e1000009
Bu yöntemi devre dışı bırakmak için, timer’ı sonsuza kadar patch edebilir veya değiştirilen tablonun checksum değerini yeniden hesaplayabilirsiniz.
KiInitializeUserApc
PspProcessDelete fonksiyonunda olduğu gibi KiInitializeUserApc içerisinde de IDT için küçük bir integrity kontrolü için kod parçası bulunmakta. The Checksum değeri 0x140E62170 adresinde, kaydırma değeri 0x140E62178 ve timer ise 0x140E62180 adresinde tutulmakta.
Başlangıç için 0x1403DC674 adresini inceleyerek başlayabiliriz:

İlk olarak, sidt instruction aracılığıyla IDT tablosundan 10 byte’lık bir veri alınmakta. Bu verilerden 8 byte’ı IDT’nin base adresini içermekte. Yani bu instruction aracılığıyla base adres alınmış oluyor. Ayrıca kaydırma ve checksum değerlerine göz atabiliriz:
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
Döngüden sonra 0x1403DC88E adresinde checksum sonucunu kontrol edildiğini görebiliriz:

Veya:
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’ı sonucu tutmakta.
Hadi bir çılgınlık yapalım ve rdx’in değerini değiştirelim:
kd> r rdx
rdx=2884d7392257ef7f
kd> r rdx = 2884d7392257ef7a
kd> g
VEEEEEE BOOOOOM:

Tamam, sadece sıkıldım. Kaldığımız yerden devam edelim….
Daha önce PspProcessDelete’den gördüğümüz gibi, kontrol esnasında ilgili sorun varsa, DPC aracılığıyla KeBugCheck’i çağırır (0x1403DC8DF):

Exception Dünyası: KiVerifyXcpt15
KiInitPatchGuardContext ayrıca KiVerifyXcpt15 aracılığıyla da çağırılabilir. Ancak burada birçok garip yöntemler kullanılmakta.
KiVerifyXcpt15’in tüm kodlarını kontrol ederseniz, bu basit kodları görürsünüz:

Çok basit gözüküyor demi? 5 Dakikada analiz edebiliriz… Ancak ne yazık kı bu dağın görünen önyüzü. Şimdi de arkasına bakalım:

Fonksiyonda neredeyse birçok exception kullanılmakta. Ancak sakin olalım. Makalede sadece fonksiyonun bir kısmını analiz edeceğiz.
- Calling KiVerifyXcpt15
KiVerifyXcpt15 fonksiyonu KeInitAmd64SpecificState ve ExpLicenseWatchInitWorker’den önce çağırılır ve esasen bu fonksiyonun kendisi KiVerifyScopesExecute aracılığıyla çağırılmaktadır.
KiVerifyScopesExecute sadece KiVerifyXcpt15’i değil aynı zamanda KiVerifyXcptRoutines adlı bir yapıdaki diğer fonksiyonları da beraberinde çağırıyor. Listedeki fonksiyonlar (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 |
Fonksiyonun başlangıcında, 0x140C1C2FD adresinde 0xA değerini tutan KiVerifyPass değişkeninden veri alarak işlemlere başlanıyor:
KiVerifyScopesExecute proc near:
...
mov ebx, cs:KiVerifyPass ; Get data of KiVerifyPass
Bu değişken aslında önemli çünkü daha sonra göreceğimiz gibi bu değişken döngü için kullanılacak.
Daha sonra değişkenden alınan değeri 0 ile karşılaştırır. Değişken sıfır değilse, 0x140C1C314 adresindeki yapıdaki tüm rutinleri sırasıyla çağırmaya başlıyor:

Yapının adresi hazırlandıktan sonra, rutinleri sırasıyla çağırmaya başlar:

Listedeki tüm rutinlerin çağırılması sonrasında KiVerifyPass değişkeninden 1 değeri çıkarılır (0x140C1C371):
loc_140C1C371:
dec ebx
jmp short loc_140C1C314
Bu kısımdan sonra tekrardan döngünün başına atlanıp (0x140C1C314) diğer rutinler sırasıyla çağırılacaktır. Farklı bir deyişle ifade edilmesi gerekirse, değişkenin değeri 0 olana kadar bu adımlar tekrarlanacaktır. Değer 1 azaltılmadan önce tüm rutinler çağırılıyor ve ardından 1 değer azaltılıyor.
Anlaşılır olması için bir diyagram hazırladım:

- Disassembling KiVerifyXcpt15
Fonksiyonun başlangıcında KiVerifyXcpt2 çağırılarak başlanıyor (Muhtemelen bu gerçekten doğrulama yapan fonksiyonun kendisi). KiVerifyXcpt2 fonksiyonu KiVerifyXcptRoutines listesinde bulunmaz ve sadece KiVerifyXcpt15 çağırılan bir fonksiyondur.
Ardından bir exception aracılığıyla işlemlere başlanıyor, 0x140C6023D adresi çağırılarak başlanır. Bu kısım da önceden gördüğümüz KiVerifyPass‘dan işlemler yaparak başlıyor:
mov eax, cs:KiVerifyPass
dec eax
mov cs:KiVerifyPass, eax
mov eax, cs:KiVerifyPass
test eax, eax
jnz loc_140C60EA0 ; Exit the function
KiVerifyPass sıfıra ulaşana kadar denk fonksiyondan çıkılıyor. Eğer değişken sıfır olursa bir MSR okumaya başlıyor. IA32_APIC_BASE‘a karşılık gelen 0x1B değerini alıyor ve rdmsr instruction’u çalıştırıyor:

0x1B adresinde bulunan IA32_APIC_BASE, Intel işlemcilerde Local Advanced Programmable Interrupt Controller (APIC) yapılandırmasını ve çalışmasını yöneten kritik bir kontrol kaydıdır.[3][4].
Daha sonra sonucun 10. bitini kontrol ettiğini görebiliriz. IA32_APIC_BASE’in 10. baytı Intel İşlemcilerdeki x2APIC Modu durumuna karşılık gelir. Intel 64 Architecture x2APIC Specification belgesinden:[3]
Note
“Sistem yazılımı, MSR adresi 0x1B’de bulunan IA32_APIC_BASE MSR’de x2APIC modu etkinleştirme bitini (bit 10) ayarlayarak yerel APIC’yi x2APIC moduna yerleştirebilir.”
Ayrıca Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 4: Model-Specific Registers[4]‘dan da faydalanabiliriz:

Eğer 10. bit set edilmemiş ise - yani 0 ise - MmMapIoSpaceEx aracılığıyla sanal adres olarak eşliyor (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
Daha sonra bu sanal adres - veya 0 değeri - global bir değişkene (qword_141006090) aktarılıyor:
loc_140C602DA:
mov cs:qword_141006090, rax
...
Ayrıca daha anlaşılır olması için bir pseudocode oluşturdum:
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;
}
}
Bu sanal adres daha sonradan KiInitPatchGuardContext tarafından alınıyor ve PatchGuard Context Structure’a hızlı bir şekilde aktarıyor (0x140BEA132):
loc_140BEA10F:
...
mov rax, cs:qword_141006090
mov [rdi+0A58h], rax
Daha sonra sabit adres 0x0FFFFF78000000289‘in byte’ını kontrol ederek işlemlere devam ediyor:
loc_140C602DA:
mov cs:MappedVirtualAddress, rax
mov rax, 0FFFFF78000000289h
cmp byte ptr [rax], 0
jz short loc_140C6034D
Bu adresin bitini görmek için WinDBG kullanabiliriz:
kd> dps 0FFFFF78000000289 l1
fffff780`00000289 01000000`00010100
Eğer byte 1 değil ise KiSwInterruptPresent çalıştırılıyor ve random değer oluşturuluyor. Bu oluşturulan random değer, bir değişkenin (dword_141006088) 1 ile ayarlanıp ayarlanamayacağına karar vermesi için kullanılıyor:

Bu değişken daha sonra KiInitPatchGuardContext çağırılması için hazırlık yapan kısma yönlendirip yönlendirilmeyeceğini belirlemek üzere kullanılıyor:
loc_140C6036D:
cmp cs:dword_141006088, 0
jz loc_140C60D7B
Eğer sabit adres 0FFFFF78000000289’in ilk byte’ı 0 ise bu değişkende 0 olacaktır ve KiInitPatchGuardContext için ilk adımları atmaya başlayacaktır, ancak eğer 1 değerini alırsa farklı işlemler yapan başka kısımdan devam edecektir.
0x140C60E1D adresine odaklanabiliriz. Burada rastgele değer oluşturuluyor ve daha önce KeInitAmd64SpecificState’de gördüğümüz gibi Debugger bayraklarından değer alıyor:

Bu kısım KiInitPatchGuardContext fonksiyonun çağırılıp çağırılmayacağını karar veren son kısım artık. Alınan bayrak sonuçları ile rastgele değer bir takım işlemlerden sonra karşılaştırma yapılıyor. Eğer bayrak sonucunu taşıyan r9 register’ı r10 yani random sonuçtan büyük ise Debugger’ı kapatıyor ve exception aracılığıyla KiInıtPatchGuardContext’i çağırıyor. Aksi takdirde çıkışa yönlendiriyor:
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
Dinamik analiz ile bu kısma bir göz atalım:
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
Görünen o ki r10 her zaman r9’dan daha yüksek bir değer alıyor. Yani r10’un düşük değer alma olasılığı gerçekten düşük. Bu nedenle koşul karşılanmıyor. Doğrulamak adına r9’un değerini değiştirdim ve:
kd> r r9 = 45
kd> t
nt!KeInitAmd64SpecificState$filt$0+0xc8b:
fffff800`7d174e87 e8c45493ff call nt!KdDisableDebugger (fffff800`7caaa350)
Dediğim gibi, koşulun karşılanması için r9’un değerinin r10’dan büyük olması gerekiyor.
İlginç olan kısım da KiVerifyXcpt15’de KiInitPatchGuardContext’in argümanlarının rastgele belirlenmemiş olmasıdır (0x140C60EFE):

ecx register hariç. Bu register DPC Routine Pointer için rastgele değer alır. Dinamik analizden sonuçlar:
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
Veya:
- RCX: 3 (Argüman 1 olarak)
- RDX: 0 (Argüman 2 olarak)
- R8: 1 (Argüman 3 olarak)
- R9: 0 (Argüman 4 olarak)
- RSP+0x20: 2 (Argüman 5 olarak)
Bu bilgilerle artık bu karmaşık fonksiyondan kurtulabiliriz. Fonksiyon içeriği gerçekten çok uzun… Hemen bir sonraki başlığımıza kayalım.
KeCheckedKernelInitialize
Son olarak ise KiInitPatchGuardContext fonksiyonu KeCheckedKernelInitialize aracılığıyla da çağırılabilir. Fonksiyonun içeriğine göz atarsak kendisi sadece KiInitPatchGuardContext için hazırlanmış gibi görünüyor. Burada KiInitPatchGuardContext method 7 aracılığıyla çağırıyor.
Öncelikle bu fonksiyon Method 7’de gördüğümüz Global PatchGuard Context’i kontrol ederek başlar. Eğer Global Structure 0 ise KiInitPatchGuardContext’i çağırmadan doğrudan çıkışa atlar:

Bu fonksiyon KiInitPatchGuardContext tarafından kullanılan bir hata ayıklama önleme tekniği kullanır. Global Structure kontrolünden sonra, hata ayıklama önleme ile alakalı bir fonksiyon çağırıyor (‘InterestingAntiDebuggingStuff’ olarak yeniden adlandırdım). Bu fonksiyon esasen basit:

Bu kod, LIDT (Load Interrupt Descriptor Table) komutunu bir __try/__except bloğu içinde çalıştırarak bir hata ayıklama tekniği uygular.
Bir istisnanın tetiklenip tetiklenmediğini kontrol ederek, program bir hata ayıklayıcının veya sanallaştırılmış ortamın varlığını tespit edebilir. İstisna tetiklenirse, eax 1 olarak ayarlanır (kısıtlı bir ortamı gösterir); aksi takdirde, 0 olarak kalır ve müdahale olmadan normal yürütmeyi gösterir.
Ve sonra VM’den bu mesajı görebilirsiniz:

Daha önce KiVerifyXcpt15’te gördüğümüz gibi, burada da aynı şekilde KiInitPatchGuardContext’in parametreleri rastgele olarak ayarlanmıyor:

Bu çağrı metodu, daha sonra göreceğimiz gibi KiFilterFiberContext’teki KiInitPatchGuardContext’in üçüncü çağrısı ile tamamen aynıdır. 0x140BD1DD8‘den:

KeCheckedKernelInitialize fonksiyonu, KeInitAmd64SpecificState fonksiyonundan sonra çağrılır. Eğer KeHotpatchTestMode bayrağı 0 değilse (Esasen bu değişken ntoskrnl’de 0 olarak ayarlanmıştır), KeCheckedKernelInitialize’ı çağırır. Bu bölümü PipInitializeCoreDriversAndElam fonksiyonunun 0x140C55A71 adresinden kontrol edebiliriz:
loc_140C55A3A
...
call KeInitAmd64SpecificState
cmp cs:KeHotpatchTestMode, 0
jz short loc_140C55A84
call KeCheckedKernelInitialize