在之前的一篇文章中介紹了替換IDT向量表中的地址來達到Hook的目的 IDT hook KiTrap03 但是這樣很容易就可以被檢測了。接下來要學習就是通過patch GDT來達到Hook IDT的目的。
首先,我們要了解一下,當觸發INT 3號中斷之后,CPU是如何找到接下來要執行的指令的地址。
CPU 在執行中斷的時候,先會得到中斷描述符表中該中斷的中斷例程(InterruptFunc ),然后得到該中斷描述符中的段選擇符,解析出段選擇符對應的GDT(因為中斷例程全都是在內核層中)中的地址(Base),然后執行:Base + InterruptFunc = 目標真正要執行的地址。
可能到這里還不太理解,我們以KiTrap03 3號中斷為例來學習。
kd> !pcr KPCR for Processor 0 at 83f6bc00: Major 1 Minor 1 NtTib.ExceptionList: 83f680ac NtTib.StackBase: 00000000 NtTib.StackLimit: 00000000 NtTib.SubSystemTib: 801e4000 NtTib.Version: 000bd40c NtTib.UserPointer: 00000001 NtTib.SelfTib: 00000000 SelfPcr: 83f6bc00 Prcb: 83f6bd20 Irql: 0000001f IRR: 00000000 IDR: ffffffff InterruptMode: 00000000 IDT: 80b95400 GDT: 80b95000 TSS: 801e4000 CurrentThread: 83f75380 NextThread: 00000000 IdleThread: 83f75380 DpcQueue:
我們可以看到當前處理器的控制塊地址和IDT,GDT地址。
80b95400 83e78e000008efc0 83e78e000008f150 0000850000580000 80b95418 83e7ee000008f5c0 83e7ee000008f748 83e78e000008f8a8
我們可以看到3號中斷的描述項的地址是0x80b95418
typedef struct _IDTENTRY { unsigned short LowOffset; unsigned short selector; unsigned char retention:5; unsigned char zero1:3; unsigned char gate_type:1; unsigned char zero2:1; unsigned char interrupt_gate_size:1; unsigned char zero3:1; unsigned char zero4:1; unsigned char DPL:2; unsigned char P:1; unsigned short HiOffset; } IDTENTRY,*PIDTENTRY;
kd> db 80b95418 80b95418 c0 f5 08 00 00 ee e7 83
我們可以得到LowOffset = 0xf5c0,HiOffset = 83e7 所以3號中斷的InterruptFunc = 0x83e7f5c0。
段選擇符selector = 0x08 二進制表示即1000。這里就又牽扯到另一個概念,段選擇符。
比如我們通知都知道 FS:[0] 指向的地址在內核層是處理器控制塊,在應用層是當前線程的TEB的首地址。
之前分析KiTrap03的匯編的時候有幾條指令:
.text:00436C5F mov ebx, 30h .text:00436C64 mov fs, bx .text:00436C67 mov ebx, large fs:0 ; fs對應處理器相關的_KPCR結構,kpcr,那么得到的是 .text:00436C67 ; kpcr.NtTib.ExceptionList
這里,為什么fs=0x30了以后,fs:0指向的就是kpcr?
FS 寄存器在Windows下表示的不是我們通常意義上的段基址,而是段選擇符。就是說CPU對於FS寄存器的尋址不是通常意義上的段基址+段偏移的方式,而是采用了另外的一種解析方式。我們這里還是以fs=0x30為例來講解。
FS寄存器是16位寄存器,先大致了解一下每一位的意義:
0和1位:代表當前特權級,用戶層:11 內核層:00::
2位:表指示位,0 表示在GDT(全局)中 ,1表示在LDT(局部)中:
3--15位:段索引。
先了解這么多就夠了,然后回到我們的問題 FS=0x30 二進制表示就是110 0 00,特權級是0 內核態, GDT表,段索引是0x6。
我們之前有得到過GDT的地址,然后索引值為6的地址為0x80b95030
80b95000 0000000000000000 00cf9b000000ffff 00cf93000000ffff 80b95018 00cffb000000ffff 00cff3000000ffff 80008b1e400020ab 80b95030 834093f6bc003748
kd> db 80b95030 80b95030 48 37 00 bc f6 93 40 83
typedef struct _KGDTENTRY // 3 elements, 0x8 bytes (sizeof) { /*0x000*/ UINT16 LimitLow; /*0x002*/ UINT16 BaseLow; union // 2 elements, 0x4 bytes (sizeof) { struct // 4 elements, 0x4 bytes (sizeof) { /*0x004*/ UINT8 BaseMid; /*0x005*/ UINT8 Flags1; /*0x006*/ UINT8 Flags2; /*0x007*/ UINT8 BaseHi; }Bytes; struct // 10 elements, 0x4 bytes (sizeof) { /*0x004*/ ULONG32 BaseMid : 8; // 0 BitPosition /*0x004*/ ULONG32 Type : 5; // 8 BitPosition /*0x004*/ ULONG32 Dpl : 2; // 13 BitPosition /*0x004*/ ULONG32 Pres : 1; // 15 BitPosition /*0x004*/ ULONG32 LimitHi : 4; // 16 BitPosition /*0x004*/ ULONG32 Sys : 1; // 20 BitPosition /*0x004*/ ULONG32 Reserved_0 : 1; // 21 BitPosition /*0x004*/ ULONG32 Default_Big : 1; // 22 BitPosition /*0x004*/ ULONG32 Granularity : 1; // 23 BitPosition /*0x004*/ ULONG32 BaseHi : 8; // 24 BitPosition }Bits; }HighWord; }KGDTENTRY, *PKGDTENTRY;
我們對照着GDTENTRY的結構體,得出BaseLow = 0xbc00 , BaseMid = 0xf6 , BaseHi = 0x83,於是就得到了一個地址 0x83f6bc00 。
是不是很熟悉,沒錯,這就是我們KPCR的地址,也就是說CPU是按照段選擇符的方式來解析FS寄存器的。
回到我們的問題,IDTENTRY中的selector域也是一個段選擇符,它所解析出來的地址,就是我們要加上的Base,得到真正的中斷例程的地址。
還是以 3號中斷為例,selector = 0x08,二進制表示為1 0 00,也就是說是GDT表中的索引為1的項,
kd> db 80b95000+0x8 80b95008 ff ff 00 00 00 9b cf 00
可以得出 BaseLow = 0 , BaseMid = 0 , BaseHi = 0 ,得出的Base = 0;
所以真的執行的例程地址就是我們的lpInterruptFunc 。
既然中斷向量的真正執行地址要經過GDT的查詢,那么如果我們替換GDT中的內容,使最后CPU得到的Base = NewKiTrap03 - KiTrap03,就可以達到對IDT隱蔽性Hook的目的。但是這樣又會出現一個問題,很多IDT處理例程的selector都是0x08,就是說和KiTrap03的段索引是一樣的,也就是說我們不能直接替換KiTrap03對應的GDT中的內容,而是應該找一個沒有用過的GDT表項,然后將KiTrap03的selector的段索引指向我們選定的GDT表項。
typedef struct _KGDTENTRY // 3 elements, 0x8 bytes (sizeof) { /*0x000*/ UINT16 LimitLow; /*0x002*/ UINT16 BaseLow; union // 2 elements, 0x4 bytes (sizeof) { struct // 4 elements, 0x4 bytes (sizeof) { /*0x004*/ UINT8 BaseMid; /*0x005*/ UINT8 Flags1; /*0x006*/ UINT8 Flags2; /*0x007*/ UINT8 BaseHi; }Bytes; struct // 10 elements, 0x4 bytes (sizeof) { /*0x004*/ ULONG32 BaseMid : 8; // 0 BitPosition /*0x004*/ ULONG32 Type : 5; // 8 BitPosition /*0x004*/ ULONG32 Dpl : 2; // 13 BitPosition /*0x004*/ ULONG32 Pres : 1; // 15 BitPosition /*0x004*/ ULONG32 LimitHi : 4; // 16 BitPosition /*0x004*/ ULONG32 Sys : 1; // 20 BitPosition /*0x004*/ ULONG32 Reserved_0 : 1; // 21 BitPosition /*0x004*/ ULONG32 Default_Big : 1; // 22 BitPosition /*0x004*/ ULONG32 Granularity : 1; // 23 BitPosition /*0x004*/ ULONG32 BaseHi : 8; // 24 BitPosition }Bits; }HighWord; }KGDTENTRY, *PKGDTENTRY; typedef struct _IDTR{ USHORT IDT_limit; USHORT IDT_LOWbase; USHORT IDT_HIGbase; }IDTR,*PIDTR; typedef struct _IDTENTRY { unsigned short LowOffset; unsigned short selector; unsigned char retention:5; unsigned char zero1:3; unsigned char gate_type:1; unsigned char zero2:1; unsigned char interrupt_gate_size:1; unsigned char zero3:1; unsigned char zero4:1; unsigned char DPL:2; unsigned char P:1; unsigned short HiOffset; } IDTENTRY,*PIDTENTRY; typedef struct _X86_KTRAP_FRAME { ULONG DbgEbp; ULONG DbgEip; ULONG DbgArgMark; ULONG DbgArgPointer; ULONG TempSegCs; ULONG TempEsp; ULONG Dr0; ULONG Dr1; ULONG Dr2; ULONG Dr3; ULONG Dr6; ULONG Dr7; ULONG SegGs; ULONG SegEs; ULONG SegDs; ULONG Edx; ULONG Ecx; ULONG Eax; ULONG PreviousPreviousMode; ULONG ExceptionList; ULONG SegFs; ULONG Edi; ULONG Esi; ULONG Ebx; ULONG Ebp; ULONG ErrCode; ULONG Eip; ULONG SegCs; ULONG EFlags; ULONG HardwareEsp; // WARNING - segSS:esp are only here for stacks ULONG HardwareSegSs; // that involve a ring transition. ULONG V86Es; // these will be present for all transitions from ULONG V86Ds; // V86 mode ULONG V86Fs; ULONG V86Gs; } X86_KTRAP_FRAME, *PX86_KTRAP_FRAME;
KIRQL Irql; ULONG_PTR g_jmp_offset = 0; ULONG_PTR OldBase; PKGDTENTRY NewGDTAddr; ULONG_PTR g_OrigKiTrap03; unsigned short OldSelector; IDTENTRY* idt_entries; __declspec(naked) void NewKiTrap03() { __asm { push 0 ;ErrorCode push ebp push ebx push esi push edi push fs mov ebx,30h mov fs,bx mov ebx,dword ptr fs:[0] push ebx sub esp,4 push eax push ecx push edx push ds push es push gs sub esp,30h //esp此時就指向陷阱幀 push esp //FilterExceptionInfo自己清理了 call FilterExceptionInfo //過濾函數 add esp , 0x30 pop gs pop es pop ds pop edx pop ecx pop eax add esp , 4 pop ebx pop fs pop edi pop esi pop ebx pop ebp add esp , 0x4 jmp g_OrigKiTrap03 } } VOID __stdcall FilterExceptionInfo(PX86_KTRAP_FRAME pTrapFrame) { //eip的值減一過int3,匯編代碼分析中dec, DbgPrint("Eip:%x\r\n",(pTrapFrame->Eip)-1); } NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObj, IN PUNICODE_STRING pRegistryString) { ULONG oriaddr=0; ULONG newaddr=0; PKGDTENTRY GDT_Addr; KGDTENTRY GDTInfo; PKGDTENTRY Gdt_Addr3e0; PKGDTENTRY Gdt_Addr8; ULONG jmpoffset=0; IDTR idt_info; unsigned short selector; #ifdef _DBG __asm int 3 #endif pDriverObj->DriverUnload = DriverUnLoad; __asm { sidt idt_info push edx sgdt [esp-2] pop edx mov GDT_Addr,edx } idt_entries = (IDTENTRY*) MAKELONG(idt_info.IDT_LOWbase,idt_info.IDT_HIGbase); g_OrigKiTrap03 = MAKELONG(idt_entries[3].LowOffset,idt_entries[3].HiOffset); jmpoffset = (ULONG)NewKiTrap03 - g_OrigKiTrap03; selector = idt_entries[1].selector; //我選擇的是索引為0x10的,空白的GDT表項 NewGDTAddr = GDT_Addr + 0x10; //保存原來的 memcpy((UCHAR*)&OldBase,(char*)(&(NewGDTAddr->BaseLow)),2); memcpy((UCHAR*)&OldBase+2,(char*)(&(NewGDTAddr->HighWord.Bytes.BaseMid)),1); memcpy((UCHAR*)&OldBase+3,(char*)(&(NewGDTAddr->HighWord.Bytes.BaseHi)),1); //修改 WPOFF(); memcpy((char*)(&(NewGDTAddr->BaseLow)),(UCHAR*)&jmpoffset,2); memcpy((char*)(&(NewGDTAddr->HighWord.Bytes.BaseMid)),(UCHAR*)(&jmpoffset)+2,1); memcpy((char*)(&(NewGDTAddr->HighWord.Bytes.BaseHi)),(UCHAR*)(&jmpoffset)+3,1); OldSelector = idt_entries[3].selector; idt_entries[3].selector = 0x80; WPON(); return STATUS_SUCCESS; } void DriverUnLoad(PDRIVER_OBJECT pDriverObject) { WPOFF(); memcpy((char*)(&(NewGDTAddr->BaseLow)),(UCHAR*)(&OldBase),2); memcpy((char*)(&(NewGDTAddr->HighWord.Bytes.BaseMid)),(UCHAR*)(&OldBase)+2,1); memcpy((char*)(&(NewGDTAddr->HighWord.Bytes.BaseHi)),(UCHAR*)(&OldBase)+3,1); idt_entries[3].selector = OldSelector; WPON(); } VOID WPOFF() { ULONG_PTR cr0 = 0; Irql = KeRaiseIrqlToDpcLevel(); cr0 =__readcr0(); cr0 &= 0xfffffffffffeffff; __writecr0(cr0); } VOID WPON() { ULONG_PTR cr0=__readcr0(); cr0 |= 0x10000; __writecr0(cr0); KeLowerIrql(Irql); }