IDT hook KiTrap03
关于idt的基本知识就不进行赘述了,先看一个例子
0x1000: mov eax,0 0x1006: Int 3 ;------->进入内核,找到中断处理例程KiTrap03 0x1007: Mov eax,1
这段代码执行,触发3号中断,然后开始执行KiTrap03例程,要知道,执行完中断以后还是要回到原来的程序处继续执行的,也就是我们这的Mov eax, 1的指令,显然,发生中断时的寄存器环境就要被保存,便于之后的恢复程序运行,这里,就出现了一个结构_KTRAP_FRAME,陷阱帧。
kd> dt _KTRAP_FRAME nt!_KTRAP_FRAME +0x000 DbgEbp : Uint4B +0x004 DbgEip : Uint4B +0x008 DbgArgMark : Uint4B +0x00c DbgArgPointer : Uint4B +0x010 TempSegCs : Uint2B +0x012 Logging : UChar +0x013 Reserved : UChar +0x014 TempEsp : Uint4B +0x018 Dr0 : Uint4B +0x01c Dr1 : Uint4B +0x020 Dr2 : Uint4B +0x024 Dr3 : Uint4B +0x028 Dr6 : Uint4B +0x02c Dr7 : Uint4B +0x030 SegGs : Uint4B +0x034 SegEs : Uint4B +0x038 SegDs : Uint4B +0x03c Edx : Uint4B +0x040 Ecx : Uint4B +0x044 Eax : Uint4B +0x048 PreviousPreviousMode : Uint4B +0x04c ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD +0x050 SegFs : Uint4B +0x054 Edi : Uint4B +0x058 Esi : Uint4B +0x05c Ebx : Uint4B +0x060 Ebp : Uint4B +0x064 ErrCode : Uint4B
+0x068 Eip : Uint4B //硬件自动填充 +0x06c SegCs : Uint4B //硬件自动填充 +0x070 EFlags : Uint4B //硬件自动填充 //后面都是可选的,只有当我们运行的程序处于虚拟86模式异常才会有 +0x074 HardwareEsp : Uint4B +0x078 HardwareSegSs : Uint4B +0x07c V86Es : Uint4B +0x080 V86Ds : Uint4B +0x084 V86Fs : Uint4B +0x088 V86Gs : Uint4B
整个陷阱帧结构记录中断发生时的寄存器环境,其中,结构体最后面的几个成员只有运行于虚拟86模式下才会存在,而Eip,SegCs和EFlags三个成员由硬件自动填充,然后剩下的成员才是由KiTrap03自己构建的陷阱帧,我们可以分析下KiTrap03的代码,由于在wrk中只有asm文件,没有C源代码,用ida进行分析
.text:00436C50 _KiTrap03 proc near ; DATA XREF: INIT:0077317Co .text:00436C50 .text:00436C50 var_2= word ptr -2 .text:00436C50 arg_4= dword ptr 8 .text:00436C50 .text:00436C50 push 0 ; Trap_frame.Errcode .text:00436C52 mov [esp+4+var_2], 0 ; Trap_frame.Errcode = 0 .text:00436C59 push ebp ; Trap_frame.Ebp .text:00436C5A push ebx ; Trap_frame.Ebx .text:00436C5B push esi ; Trap_frame.Esi .text:00436C5C push edi ; Trap_frame.Edi .text:00436C5D push fs ; Trap_frame.SegFs .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 .text:00436C6E push ebx ; Trap_frame.ExceptionList .text:00436C6F sub esp, 4 ; Trap_frame.PreviousPreviousMode,在后面填充 .text:00436C72 push eax ; Trap_frame.Eax .text:00436C73 push ecx ; Trap_frame.Ecx .text:00436C74 push edx ; Trap_frame.Edx .text:00436C75 push ds ; Trap_frame.SegDs .text:00436C76 push es ; Trap_frame.SegEs .text:00436C77 push gs ; Trap_frame.SegGs .text:00436C79 mov ax, 23h ; ??? .text:00436C7D sub esp, 30h ; 继续增大栈空间,刚好是整个Trap_Frame的大小 .text:00436C80 mov ds, ax ; ??? .text:00436C83 mov es, ax .text:00436C86 mov ebp, esp ; ebp指向Trap_frame .text:00436C88 test [esp+68h+arg_4], 20000h ; Trap_frame.EFlags,0x2000代表EFLAGS_V86_MASK, .text:00436C88 ; 标记虚拟86模式 .text:00436C90 jnz short V86_kit3_a ; 如果是虚拟86模式跳转 .text:00436C92 .text:00436C92 loc_436C92: ; CODE XREF: V86_kit3_a+25j .text:00436C92 mov ecx, large fs:124h ; ecx = CurrentThread .text:00436C92 ; +0x004 CurrentThread : Ptr32 _KTHREAD 当前线程 ecx .text:00436C99 cld .text:00436C9A and dword ptr [ebp+2Ch], 0 ; Trap_frame.Dr7清零 .text:00436C9E test byte ptr [ecx+3], 0DFh .text:00436CA2 jnz Dr_kit3_a .text:00436CA8 .text:00436CA8 loc_436CA8: ; CODE XREF: Dr_kit3_a+Dj .text:00436CA8 ; Dr_kit3_a+79j .text:00436CA8 mov ebx, [ebp+60h] .text:00436CAB mov edi, [ebp+68h] .text:00436CAE mov [ebp+0Ch], edx ; Trap_frame.DbgArgPointer = edx .text:00436CB1 mov dword ptr [ebp+8], 0BADB0D00h ; Trap_frame.DbgArgMark = 0BADB0D00h .text:00436CB8 mov [ebp+0], ebx ; Trap_frame.DbgEbp = kTrap_frame.Ebp .text:00436CBB mov [ebp+4], edi ; Trap_frame.DbgEip = kTrap_frame.Eip .text:00436CBE cmp ds:_PoHiberInProgress, 0 .text:00436CC5 jnz short loc_436CCE .text:00436CC7 lock inc ds:_KiHardwareTrigger .text:00436CCE .text:00436CCE loc_436CCE: ; CODE XREF: _KiTrap03+75j .text:00436CCE mov eax, 0 .text:00436CD3 .text:00436CD3 loc_436CD3: ; CODE XREF: _KiDebugService+7Aj .text:00436CD3 test byte ptr [ebp+72h], 2 .text:00436CD7 jnz short loc_436D08 .text:00436CD9 test byte ptr [ebp+6Ch], 1 ; CS 最后一位,判断是否为UserMode .text:00436CDD jnz short loc_436CE7 ; 如果是UserMode,跳转 .text:00436CDF test byte ptr [ebp+71h], 2 ; 判断Eflags.IF位是否存在 .text:00436CE3 jz short loc_436CEF .text:00436CE5 jmp short loc_436CEE .text:00436CE7 ; --------------------------------------------------------------------------- .text:00436CE7 .text:00436CE7 loc_436CE7: ; CODE XREF: _KiTrap03+8Dj .text:00436CE7 cmp word ptr [ebp+6Ch], 1Bh .text:00436CEC jnz short loc_436D08 .text:00436CEE .text:00436CEE loc_436CEE: ; CODE XREF: _KiTrap03+95j .text:00436CEE ; _KiTrap03+C9j .text:00436CEE sti ; IF标志位存在 .text:00436CEF .text:00436CEF loc_436CEF: ; CODE XREF: _KiTrap03+93j .text:00436CEF ; _KiTrap03+D6j .text:00436CEF mov esi, ecx ; esi=CurrentThread .text:00436CF1 mov edi, edx .text:00436CF3 mov edx, eax .text:00436CF5 mov ebx, [ebp+68h] ; ebx = kTrap_frame.Eip .text:00436CF8 dec ebx ; "eip-1"是因为int 3本身占一个字节 .text:00436CF9 mov ecx, 3 ; ???在下层函数中会用到,到时候就知道了 .text:00436CFE mov eax, 80000003h ; 异常类型(STATUS_BREAKPOINT) .text:00436D03 call CommonDispatchException ; 处理异常 .text:00436D08 .text:00436D08 loc_436D08: ; CODE XREF: _KiTrap03+87j .text:00436D08 ; _KiTrap03+9Cj .text:00436D08 mov ebx, large fs:124h .text:00436D0F mov ebx, [ebx+50h] .text:00436D12 cmp dword ptr [ebx+148h], 0 .text:00436D19 jz short loc_436CEE .text:00436D1B push 3 .text:00436D1D call _Ki386VdmReflectException_A@4 ; Ki386VdmReflectException_A(x) .text:00436D22 test ax, 0FFFFh .text:00436D26 jz short loc_436CEF .text:00436D28 jmp Kei386EoiHelper@0 .text:00436D28 _KiTrap03 endp
很明显,在KiTrap03中没有进行太多的处理,只是判断是否是虚拟86模式来构建了一个不同的陷阱帧结构,之后调用了CommonDispatchException 函数来处理异常。
在虚拟86模式下将会跳转到V86_kit3_a,我们可以看下实现
.text:00436C28 V86_kit3_a proc near ; CODE XREF: _KiTrap03+40j .text:00436C28 mov eax, [ebp+84h] .text:00436C2E mov ebx, [ebp+88h] .text:00436C34 mov ecx, [ebp+7Ch] .text:00436C37 mov edx, [ebp+80h] .text:00436C3D mov [ebp+50h], ax ; Trap_frame.SegFs = Trap_frame.V86Fs .text:00436C41 mov [ebp+30h], bx ; Trap_frame.SegGs = Trap_frame.V86Gs .text:00436C45 mov [ebp+34h], cx ; Trap_frame.SegEs = Trap_frame.V86Es .text:00436C49 mov [ebp+38h], dx ; Trap_frame.SegDs = Trap_frame.V86Ds .text:00436C4D jmp short loc_436C92 .text:00436C4D V86_kit3_a endp
可以看到V86_kit3_a中没有进行处理,只是将陷阱帧中保存的寄存器的值对应为虚拟86模式特有的值。
然后跟入CommonDispatchException 函数
.text:0043641C CommonDispatchException proc near ; CODE XREF: _KiTrap00-253p .text:0043641C ; _KiTrap00-247p _KiTrap00-23Bp .text:0043641C ; _KiTrap03+B3p _KiTrap0E+21Ap .text:0043641C ; sub_671B78+24p .text:0043641C .text:0043641C var_50= dword ptr -50h .text:0043641C var_4C= dword ptr -4Ch .text:0043641C var_48= dword ptr -48h .text:0043641C var_44= dword ptr -44h .text:0043641C var_40= dword ptr -40h .text:0043641C var_3C= byte ptr -3Ch .text:0043641C .text:0043641C sub esp, 50h ; EXCEPTION_RECORD结构空间,ExceptionRecord .text:0043641F mov [esp+50h+var_50], eax ; eax是上层调用设置的错误码,这里是 .text:0043641F ; ExceptionRecord->ExceptionCode = eax .text:00436422 xor eax, eax .text:00436424 mov [esp+50h+var_4C], eax ; ExceptionRecord->ExceptionFlags = 0 .text:00436428 mov [esp+50h+var_48], eax ; ExceptionRecord->ExceptionRecord = 0 .text:0043642C mov [esp+50h+var_44], ebx ; ebx是上层调用设置的异常发生处地址, .text:0043642C ; ExceptionRecord->ExceptionAddress = ebx .text:00436430 mov [esp+50h+var_40], ecx ; ExceptionRecord->NumberParameters = ecx .text:00436430 ; 表示ExceptionRecord->ExceptionInformation数组的数量 .text:00436434 cmp ecx, 0 ; ExceptionRecord->NumberParameters为零跳转 .text:00436437 jz short loc_436445 .text:00436439 lea ebx, [esp+50h+var_3C] ; ebx = ExceptionRecord->ExceptionInformation .text:0043643D mov [ebx], edx ; 开始填充ExceptionInformation的信息 .text:0043643F mov [ebx+4], esi .text:00436442 mov [ebx+8], edi .text:00436445 .text:00436445 loc_436445: ; CODE XREF: CommonDispatchException+1Bj .text:00436445 mov ecx, esp ; ecx = ExceptionRecord .text:00436447 test byte ptr [ebp+72h], 2 ; ebp还是指向TrapFrame,TrapFrame->EFlags, .text:00436447 ; 这里是EFlags的高2字节,判断是否为虚拟86模式 .text:0043644B jz short loc_436454 .text:0043644D mov eax, 0FFFFh ; ???? .text:00436452 jmp short loc_436457 .text:00436454 ; --------------------------------------------------------------------------- .text:00436454 .text:00436454 loc_436454: ; CODE XREF: CommonDispatchException+2Fj .text:00436454 mov eax, [ebp+6Ch] ; eax = TrapFrame->SegCs .text:00436457 .text:00436457 loc_436457: ; CODE XREF: CommonDispatchException+36j .text:00436457 and eax, 1 ; Cs的低两位得到处理器模式 .text:0043645A push 1 ; FirstChance .text:0043645C push eax ; PreviousMode .text:0043645D push ebp ; TrapFrame .text:0043645E push 0 ; ExceptionFrame .text:00436460 push ecx ; void * .text:00436461 call _KiDispatchException@20 ; 对异常进行分发处理 .text:00436466 mov esp, ebp ; 修正esp,然后执行退出操作 .text:00436468 jmp Kei386EoiHelper@0 .text:00436468 CommonDispatchException endp
可以看到CommonDispatchException也没有进行处理,只是构建了一个ExceptionRecord的结构体,就对异常进行了分发处理,进入了熟悉的KiDispatchException函数。
了解了陷阱帧的构建,再看idt表的基本结构,每个CPU的核心都有自己的idt表,
typedef struct _IDTR { USHORT limit; //整个表所占内存大小 ULONG base; //IDT表项起始地址 }IDTR,*PIDTR;
在我的虚拟机上 limit= 0x7ff (包含0) 0x800 = 2048 Entry每项大小8字节,就2048/8 = 256 成员,idt表有256个例程
可以使用kd> !idt -a 命令来查看idt表的详细信息,可以发现就是有256个例程
idt表中的每一项对应一种中断处理例程,结构体如下,我们最关心的是LowOffset和HiOffset这两个成员,他们组成了处理例程地址的高16位和低16位
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;
利用MAKELONG 的宏,可以得到处理例程的真正地址
#define MAKELONG(a, b) ((LONG)(((WORD)(((DWORD_PTR)(a)) & 0xffff)) | ((DWORD)((WORD)(((DWORD_PTR)(b)) & 0xffff))) << 16))
idt表的获得可以通过sidt指令或者时_KPCR中的成员获得,在内核态fs段寄存器是指向_KPCR结构。
kd> dt _kpcr ...... +0x038 IDT ; Ptr32 _KIDTENTRY ......
下面的代码打印了每次触发INT 3断点的地址,可以用OD下普通的断点进行测试,可以打印出断点的地址。
ULONG_PTR g_OrigKiTrap03; KIRQL Irql; _declspec(naked) void NewKiTrap03() { __asm { //测试 //jmp g_OrigKiTrap03 //构建Trap03的异常帧 //保存现场环境,和原始Trap03一样 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 DriverObject, IN PUNICODE_STRING RegistryString) { NTSTATUS Status = STATUS_SUCCESS; IDTR Idtr; PIDTENTRY pIdtArray = NULL; ULONG_PTR Index = 0; DriverObject->DriverUnload = UnloadDriver; __asm sidt Idtr //虚拟机是单核的,只用一个就可以了 if(KeGetIdt(&pIdtArray)) { DbgPrint("%x---%x\r\n",Idtr.base,Idtr.limit); for (Index =0;Index<(Idtr.limit+1)/sizeof(IDTENTRY);Index++) { DbgPrint("TrapHandle[%d]:%x\r\n",Index,MAKELONG(pIdtArray[Index].LowOffset,pIdtArray[Index].HiOffset)); } g_OrigKiTrap03 = MAKELONG(pIdtArray[3].LowOffset,pIdtArray[3].HiOffset); WPOFF(); pIdtArray[3].LowOffset = (ULONG_PTR)NewKiTrap03 & 0xFFFF; //低16位 pIdtArray[3].HiOffset = (ULONG_PTR)NewKiTrap03 >> 16; //高16位 WPON(); } //limit 0x7ff (包含0) 0x800 = 2048 Entry每项大小8字节,就2048/8 = 256 成员 //!idt -a 0ff = 256 //MAKELONG //#define MAKELONG(a, b) ((LONG)(((WORD)(((DWORD_PTR)(a)) & 0xffff)) | ((DWORD)((WORD)(((DWORD_PTR)(b)) & 0xffff))) << 16)) return Status; } BOOLEAN KeGetIdt(PIDTENTRY *pIdtArray) { ULONG Index,Affinity,CurrentAffinity; pfnKESETAFFINITYTHREAD fnpKeSetAffinityThread; UNICODE_STRING usFuncName; PIDTENTRY pIdtEntry; RtlInitUnicodeString(&usFuncName,L"KeSetAffinityThread"); fnpKeSetAffinityThread = (pfnKESETAFFINITYTHREAD)MmGetSystemRoutineAddress(&usFuncName); if (fnpKeSetAffinityThread==0) { return FALSE; } Affinity = KeQueryActiveProcessors(); //KeQueryActiveProcessors获取处理器相关的位图 //(这里的位图可以理解为个数,比如返回1代表一个处理器,返回3表示两个处理器,返回7表示三个处理器,依此类推。 //也就是说从有多少个处理器,那么Affinity的值就会从低位到高位依此填充多少位) CurrentAffinity = 1; Index = 0; while(Affinity) { //下面只是个简单的算法,使当前线程运行到不同的处理器上 Affinity &= ~CurrentAffinity; fnpKeSetAffinityThread(PsGetCurrentThread(),(KAFFINITY)CurrentAffinity); CurrentAffinity <<= 1; __asm{ push eax mov eax,fs:[0x38] mov pIdtEntry,eax pop eax } //得到我们要的东西 pIdtArray[Index] = pIdtEntry; Index++; } return TRUE; } 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); } VOID UnloadDriver(PDRIVER_OBJECT DriverObject) { //恢复 PIDTENTRY pIdtEntry; if (g_OrigKiTrap03 && KeGetIdt(&pIdtEntry)) { WPOFF(); pIdtEntry[3].LowOffset = g_OrigKiTrap03 & 0xFFFF; pIdtEntry[3].HiOffset = g_OrigKiTrap03 >> 16; WPON(); } }