PatchGuard Analysis - Part 2

PatchGuard Analysis - Part 2

Ağustos 6, 2025·0xbekoo
0xbekoo
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:

Lidt komutundan sonra VMware'den bu mesajı aldım. Bu teknik **0x140BD36A2** adresinde KiInitPatchGuardContext tarafından da kullanılıyor.

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
Last updated on