什麼是中斷?數組
指當出現須要時,CPU暫時中止當前程序的執行轉而執行處理新狀況的程序和執行過程。即在程序運行過程當中,系統出現了一個必須由CPU當即處理的狀況,此時,CPU暫時停止程序的執行轉而處理這個新的狀況的過程就叫作中斷。數據結構
好比:除零(0號中斷)、斷點(3號中斷)、系統調用(2e號中斷)、以及異常處理等都會引起中斷,因此天然須要相應的中斷例程去進行處理。ide
這樣操做系統就會用數據結構來維護這些中斷例程,這個數據結構就是IDT(Interrupt Descriptor Table)。函數
中斷描述表佈局
IDT表的長度與地址是由CPU的IDTR寄存器來描述的。IDTR寄存器共有48位,高32位是IDT表的基地址,低16位是IDT的長度。測試
typedef struct _IDTR{ USHORT IDT_limit; USHORT IDT_LOWbase; USHORT IDT_HIGbase; }IDTR,*PIDTR; IDTR idtr; __asm SIDT idtr;
能夠經過以上SIDT指令能夠讀取IDTR寄存器。而後經過MAKEWORD宏把高位與地位組合起來就能夠得到IDT表的基地址了。ui
簡單來講,IDT表是一張位於物理內存的線性表,共有256個表項。在32位模式下,每一個IDT表項的長度是8個字節(64 bit),IDT表的總長度是2048字節。編碼
kd> r idtr idtr=8003f400 kd> r idtl idtl=000007ff
經過Windbg命令 r idtr、r idtl能夠讀取IDT表的基地址與邊界。spa
如圖能夠清晰的看見每個表項了,但是每個表項8字節都表明什麼意思呢?操作系統
IDT表中每一項也稱爲「門描述符」,之因此這樣稱呼,是由於IDT表項的基本用途就是引領CPU從一個空間到另外一個空間去執行,每一個表項好像是一個空間到另外一個空間的大門。
IDT表中能夠包含如下3種門描述符:
任務門描述符:用於任務切換,裏面包含用於選擇任務狀態段(TSS)的段選擇子。可使用JMP或CALL指令經過任務門來切換到任務門所指向的任務,當CPU由於中斷或異常轉移到任務門時,也會切換到指定任務。
中斷門描述符:用於描述中斷例程的入口。
陷阱門描述符:用於描述異常處理例程的入口。
如下爲三種門描述符的內存佈局:
結構體定義爲
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;
其中DPL表明描述符優先級,用於優先級控制,P是段存在標誌,段選擇子用來選擇一個段描述符(LDT或GDT)偏移部分用來指定段中偏移。二者共同準確的定義一個內存地址,對於中斷門和陷阱門,他們指定的就是中斷或異常處理例程的地址,對於任務門它們指定的就是任務狀態段地址。
也就是說段選擇子提供一個所謂的段基地址,那麼處理例程 = 段基址 + 段偏移。
那麼HOOK方式就有兩種了:更改段偏移或者更改段基址。
咱們先忽略段選擇子提供的段基址,先簡單的認爲門描述符提供的高16位和低16位的段內偏移就是該中斷的處理例程(其實原本就是,由於32位操做系統中,已經弱化了段基址的概念,運用了平坦模型,也就是說段基址就是0)。
而後咱們看看HOOK代碼:
#ifndef CXX_IDTHOOK_H # include "IDTHook.h" #endif #define WORD USHORT #define DWORD ULONG ULONG g_InterruptFun = 0; #define MAKELONG(a, b) ((LONG)(((WORD)(((DWORD_PTR)(a)) & 0xffff)) \ | ((DWORD)((WORD)(((DWORD_PTR)(b)) & 0xffff))) << 16)) NTKERNELAPI VOID KeSetSystemAffinityThread ( KAFFINITY Affinity ); NTKERNELAPI VOID KeRevertToUserAffinityThread ( VOID ); PULONG GetKiProcessorBlock() { ULONG* KiProcessorBlock = 0; KeSetSystemAffinityThread(1); //使當前線程運行在第一個處理器上 _asm { push eax mov eax,FS:[0x34] add eax,20h mov eax,[eax] mov eax,[eax] mov eax,[eax+218h] mov KiProcessorBlock,eax pop eax } KeRevertToUserAffinityThread(); return KiProcessorBlock ; } void PageProtectOn() { __asm{//恢復內存保護 mov eax,cr0 or eax,10000h mov cr0,eax sti } } void PageProtectOff() { __asm{//去掉內存保護 cli mov eax,cr0 and eax,not 10000h mov cr0,eax } } void _stdcall FilterInterruptFun() { DbgPrint("CurrentProcess : %s",(char*)PsGetCurrentProcess()+0x174); } _declspec(naked) void Fake_InterruptFun() { _asm{ pushad pushfd push fs push 0x30 pop fs call FilterInterruptFun; pop fs popfd popad jmp g_InterruptFun } }; NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObj, IN PUNICODE_STRING pRegistryString) { IDTR Idtr; PIDTENTRY pIdtEntry; ULONG ulIndex = 0 ; ULONG* KiProcessorBlock; pDriverObj->DriverUnload = DriverUnload; KiProcessorBlock = GetKiProcessorBlock(); DbgPrint("%X\r\n",KiProcessorBlock); while (KiProcessorBlock[ulIndex]) { pIdtEntry = *(PIDTENTRY*)(KiProcessorBlock[ulIndex] - 0x120 + 0x38) ; DbgPrint("IDT Base:%X\r\n",pIdtEntry); g_InterruptFun = MAKELONG(pIdtEntry[3].LowOffset,pIdtEntry[3].HiOffset); DbgPrint("InterruptFun3:%X\r\n",g_InterruptFun); PageProtectOff(); pIdtEntry[3].LowOffset = (unsigned short)((ULONG)Fake_InterruptFun & 0xffff); pIdtEntry[3].HiOffset = (unsigned short)((ULONG)Fake_InterruptFun >> 16); PageProtectOn(); ulIndex++; } return STATUS_SUCCESS; } VOID DriverUnload(IN PDRIVER_OBJECT pDriverObj) { return; }
稍微解釋一下,裏面得到IDT表的時候沒有經過寄存器IDTR進行讀取,是由於對於多核CPU來講不必定只有一個IDT表,而經過IDTR來讀取只能讀到一份表,因此HOOK IDT的時候必定要注意多核問題。
系統維護了一個全局的處理器數組KiProcessorBlock,其中每一個元素對應於一個處理器的KPRCB 對象。
kd> dt _KPCR nt!_KPCR +0x000 NtTib : _NT_TIB +0x01c SelfPcr : Ptr32 _KPCR +0x020 Prcb : Ptr32 _KPRCB +0x024 Irql : UChar +0x028 IRR : Uint4B +0x02c IrrActive : Uint4B +0x030 IDR : Uint4B +0x034 KdVersionBlock : Ptr32 Void +0x038 IDT : Ptr32 _KIDTENTRY +0x03c GDT : Ptr32 _KGDTENTRY +0x040 TSS : Ptr32 _KTSS +0x044 MajorVersion : Uint2B +0x046 MinorVersion : Uint2B +0x048 SetMember : Uint4B +0x04c StallScaleFactor : Uint4B +0x050 DebugActive : UChar +0x051 Number : UChar +0x052 Spare0 : UChar +0x053 SecondLevelCacheAssociativity : UChar +0x054 VdmAlert : Uint4B +0x058 KernelReserved : [14] Uint4B +0x090 SecondLevelCacheSize : Uint4B +0x094 HalReserved : [16] Uint4B +0x0d4 InterruptMode : Uint4B +0x0d8 Spare1 : UChar +0x0dc KernelReserved2 : [17] Uint4B +0x120 PrcbData : _KPRCB
在0X120處,有個_KPRCB結構。再來看看這個結構。
kd> dt _KPCR nt!_KPCR +0x000 NtTib : _NT_TIB +0x01c SelfPcr : Ptr32 _KPCR +0x020 Prcb : Ptr32 _KPRCB +0x024 Irql : UChar +0x028 IRR : Uint4B +0x02c IrrActive : Uint4B +0x030 IDR : Uint4B +0x034 KdVersionBlock : Ptr32 Void +0x038 IDT : Ptr32 _KIDTENTRY +0x03c GDT : Ptr32 _KGDTENTRY +0x040 TSS : Ptr32 _KTSS +0x044 MajorVersion : Uint2B +0x046 MinorVersion : Uint2B +0x048 SetMember : Uint4B +0x04c StallScaleFactor : Uint4B +0x050 DebugActive : UChar +0x051 Number : UChar +0x052 Spare0 : UChar +0x053 SecondLevelCacheAssociativity : UChar +0x054 VdmAlert : Uint4B +0x058 KernelReserved : [14] Uint4B +0x090 SecondLevelCacheSize : Uint4B +0x094 HalReserved : [16] Uint4B +0x0d4 InterruptMode : Uint4B +0x0d8 Spare1 : UChar +0x0dc KernelReserved2 : [17] Uint4B +0x120 PrcbData : _KPRCB kd> dt _PRCB Symbol _PRCB not found. kd> dt _KPRCB nt!_KPRCB +0x000 MinorVersion : Uint2B +0x002 MajorVersion : Uint2B +0x004 CurrentThread : Ptr32 _KTHREAD +0x008 NextThread : Ptr32 _KTHREAD +0x00c IdleThread : Ptr32 _KTHREAD +0x010 Number : Char +0x011 Reserved : Char +0x012 BuildType : Uint2B +0x014 SetMember : Uint4B +0x018 CpuType : Char +0x019 CpuID : Char +0x01a CpuStep : Uint2B +0x01c ProcessorState : _KPROCESSOR_STATE +0x33c KernelReserved : [16] Uint4B +0x37c HalReserved : [16] Uint4B +0x3bc PrcbPad0 : [92] UChar +0x418 LockQueue : [16] _KSPIN_LOCK_QUEUE +0x498 PrcbPad1 : [8] UChar +0x4a0 NpxThread : Ptr32 _KTHREAD +0x4a4 InterruptCount : Uint4B +0x4a8 KernelTime : Uint4B +0x4ac UserTime : Uint4B +0x4b0 DpcTime : Uint4B +0x4b4 DebugDpcTime : Uint4B +0x4b8 InterruptTime : Uint4B +0x4bc AdjustDpcThreshold : Uint4B +0x4c0 PageColor : Uint4B +0x4c4 SkipTick : Uint4B +0x4c8 MultiThreadSetBusy : UChar +0x4c9 Spare2 : [3] UChar +0x4cc ParentNode : Ptr32 _KNODE +0x4d0 MultiThreadProcessorSet : Uint4B +0x4d4 MultiThreadSetMaster : Ptr32 _KPRCB +0x4d8 ThreadStartCount : [2] Uint4B +0x4e0 CcFastReadNoWait : Uint4B +0x4e4 CcFastReadWait : Uint4B +0x4e8 CcFastReadNotPossible : Uint4B +0x4ec CcCopyReadNoWait : Uint4B +0x4f0 CcCopyReadWait : Uint4B +0x4f4 CcCopyReadNoWaitMiss : Uint4B +0x4f8 KeAlignmentFixupCount : Uint4B +0x4fc KeContextSwitches : Uint4B +0x500 KeDcacheFlushCount : Uint4B +0x504 KeExceptionDispatchCount : Uint4B +0x508 KeFirstLevelTbFills : Uint4B +0x50c KeFloatingEmulationCount : Uint4B +0x510 KeIcacheFlushCount : Uint4B +0x514 KeSecondLevelTbFills : Uint4B +0x518 KeSystemCalls : Uint4B +0x51c SpareCounter0 : [1] Uint4B +0x520 PPLookasideList : [16] _PP_LOOKASIDE_LIST +0x5a0 PPNPagedLookasideList : [32] _PP_LOOKASIDE_LIST +0x6a0 PPPagedLookasideList : [32] _PP_LOOKASIDE_LIST +0x7a0 PacketBarrier : Uint4B +0x7a4 ReverseStall : Uint4B +0x7a8 IpiFrame : Ptr32 Void +0x7ac PrcbPad2 : [52] UChar +0x7e0 CurrentPacket : [3] Ptr32 Void +0x7ec TargetSet : Uint4B +0x7f0 WorkerRoutine : Ptr32 void +0x7f4 IpiFrozen : Uint4B +0x7f8 PrcbPad3 : [40] UChar +0x820 RequestSummary : Uint4B +0x824 SignalDone : Ptr32 _KPRCB +0x828 PrcbPad4 : [56] UChar +0x860 DpcListHead : _LIST_ENTRY +0x868 DpcStack : Ptr32 Void +0x86c DpcCount : Uint4B +0x870 DpcQueueDepth : Uint4B +0x874 DpcRoutineActive : Uint4B +0x878 DpcInterruptRequested : Uint4B +0x87c DpcLastCount : Uint4B +0x880 DpcRequestRate : Uint4B +0x884 MaximumDpcQueueDepth : Uint4B +0x888 MinimumDpcRate : Uint4B +0x88c QuantumEnd : Uint4B +0x890 PrcbPad5 : [16] UChar +0x8a0 DpcLock : Uint4B +0x8a4 PrcbPad6 : [28] UChar +0x8c0 CallDpc : _KDPC +0x8e0 ChainedInterruptList : Ptr32 Void +0x8e4 LookasideIrpFloat : Int4B +0x8e8 SpareFields0 : [6] Uint4B +0x900 VendorString : [13] UChar +0x90d InitialApicId : UChar +0x90e LogicalProcessorsPerPhysicalProcessor : UChar +0x910 MHz : Uint4B +0x914 FeatureBits : Uint4B +0x918 UpdateSignature : _LARGE_INTEGER +0x920 NpxSaveArea : _FX_SAVE_AREA +0xb30 PowerState : _PROCESSOR_POWER_STATE
在0x38的地方是否是看到了咱們熟悉的IDT表。
咱們在Windbg下dd KiProcessBlock
kd> dd KiProcessorBlock 80553e40 ffdff120 00000000 00000000 00000000 80553e50 00000000 00000000 00000000 00000000 80553e60 00000000 00000000 00000000 00000000 80553e70 00000000 00000000 00000000 00000000
由於是虛擬機裏面作的測試,因此只是單核CPU,那麼問題來了,如何得到KiProcessBlock?
在Win732位與XP中它是未導出的,那麼能夠用IDA在ntosknl導出表搜索,找到哪一個函數中用了這個變量就能夠用這個函數加硬編碼的方式進行強行定位,上次看到一篇帖子是如何得到系統未導出的全局變量,是經過遍歷未公開的結構體,我就試了試,沒想到還成功了,而後就用了這種方法,其實解決多核問題還有好多種方法,不必定侷限於哪種。
下面着重講解下,如何修改段基址的方法實現HOOK,而後躲過xuetr的檢測。
IA-32處理器有三種描述符表:全局描述符表GDT,局部描述符表LDT,中斷描述符表IDT。
GDT表是全局的,一個系統中一般只有一個GDT表,供系統中全部程序和任務進行使用。LDT與任務相關,每一個任務能夠有一個LDT,也可讓多個任務共享一個LDT。
咱們用WINDBG觀察下GDT
kd> r gdtr
gdtr=8003f000
得到GDT的基地址以後觀察下它的內存
是否是和IDT表同樣啊,這時候裏面的一項叫作段描述符。
這個圖就是段描述符的內存結構。有點看不清,將就一下,詳情能夠參考張銀奎老師的《軟件調試》。
typedef struct _KGDTENTRY { USHORT LimitLow; USHORT BaseLow; union { struct { UCHAR BaseMid; UCHAR Flags1; // Declare as bytes to avoid alignment UCHAR Flags2; // Problems. UCHAR BaseHi; } Bytes; struct { ULONG BaseMid : 8; ULONG Type : 5; ULONG Dpl : 2; ULONG Pres : 1; ULONG LimitHi : 4; ULONG Sys : 1; ULONG Reserved_0 : 1; ULONG Default_Big : 1; ULONG Granularity : 1; ULONG BaseHi : 8; } Bits; } HighWord; } KGDTENTRY, *PKGDTENTRY;
段選擇子:
還記得門描述符裏的選擇子嗎?選擇子的做用就是選擇一個段描述符,至關於索引。
段選擇子的T1位表明要索引的段描述符表,T1=0表示全局描述符表,T1=1表示局部描述符表。
段選擇子的高13位是描述符索引,即要選擇的段描述符在T1所表示的段描述符表中的索引號。由於這裏使用的是13位,意味着最多可索引8192個描述符,因此GDT和LDT表的最大表項數都是8192.由於X86CPU最多支持256箇中斷向量,因此IDT 表的最多表項數是256.
而後咱們來看看如何修改段基址來實現IDT HOOK。
要是段內偏移不變,那麼必須知足:原始偏移 + newbase = newfuntion
那麼:newbase = newfuntion - 原始偏移
而後把newbase分解了再填到段描述符中就能夠了嗎?答案是不能夠,由於一個段描述符可能會有不少程序在用,若是無端修改段描述符那麼就會產生不可預料的錯誤,那麼咱們如何修改段基址呢?
咱們能夠變通一下,由於段描述符表裏有不少空的未利用的,咱們能夠把相應的門描述符裏的段選擇子改掉,讓它去選擇一個空的段描述符,咱們把原先段選擇符內容拷貝過來再修改這個段描述符就能夠達到目的了!
#include "ntifs.h" #define WORD USHORT #define DWORD ULONG #define MAKELONG(a, b) ((LONG)(((WORD)(((DWORD_PTR)(a)) & 0xffff)) \ | ((DWORD)((WORD)(((DWORD_PTR)(b)) & 0xffff))) << 16)) 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 _KGDTENTRY { USHORT LimitLow; USHORT BaseLow; union { struct { UCHAR BaseMid; UCHAR Flags1; // Declare as bytes to avoid alignment UCHAR Flags2; // Problems. UCHAR BaseHi; } Bytes; struct { ULONG BaseMid : 8; ULONG Type : 5; ULONG Dpl : 2; ULONG Pres : 1; ULONG LimitHi : 4; ULONG Sys : 1; ULONG Reserved_0 : 1; ULONG Default_Big : 1; ULONG Granularity : 1; ULONG BaseHi : 8; } Bits; } HighWord; } KGDTENTRY, *PKGDTENTRY; //global USHORT g_FilterJmp[3]; ULONG g_uOrigInterruptFunc; void PageProtectOn() { __asm{//恢復內存保護 mov eax,cr0 or eax,10000h mov cr0,eax sti } } void PageProtectOff() { __asm{//去掉內存保護 cli mov eax,cr0 and eax,not 10000h mov cr0,eax } } USHORT g_u_cs; void __stdcall FilterInterrupt() { KdPrint(("%s---%X",(char*)PsGetCurrentProcess()+0x16c,g_u_cs)); } __declspec(naked) void NewInterrupt3OfOrigBase() { __asm{ pushad pushfd push fs push 0x30 pop fs call FilterInterrupt pop fs popfd popad jmp g_uOrigInterruptFunc } } __declspec(naked) void NewInterrupt3() { __asm{ mov g_u_cs,cs jmp fword ptr[g_FilterJmp] } } ULONG GetInterruptFuncAddress(ULONG InterruptIndex) { IDTR idtr; IDTENTRY *pIdtEntry; __asm SIDT idtr; pIdtEntry = (IDTENTRY *)MAKELONG(idtr.IDT_LOWbase,idtr.IDT_HIGbase); return MAKELONG(pIdtEntry[InterruptIndex].LowOffset,pIdtEntry[InterruptIndex].HiOffset); } ULONG GetNewBase(ULONG NewInterruptFunc,ULONG OrigInterruptOffset) { return (NewInterruptFunc - OrigInterruptOffset); } VOID SetInterrupt(ULONG InterruptIndex,ULONG uNewBase,BOOLEAN bIsNew) { ULONG u_fnKeSetTimeIncrement; UNICODE_STRING usFuncName; ULONG u_index; ULONG *u_KiProcessorBlock; IDTENTRY *pIdtEntry; PKGDTENTRY pGdt; RtlInitUnicodeString(&usFuncName,L"KeSetTimeIncrement"); u_fnKeSetTimeIncrement = (ULONG)MmGetSystemRoutineAddress(&usFuncName); if (!MmIsAddressValid((PVOID)u_fnKeSetTimeIncrement)) { return; } u_KiProcessorBlock = *(ULONG**)(u_fnKeSetTimeIncrement + 44); u_index = 0; while (u_KiProcessorBlock[u_index]) { pIdtEntry = *(IDTENTRY**)(u_KiProcessorBlock[u_index] - 0xE8); pGdt = *(PKGDTENTRY*)(u_KiProcessorBlock[u_index] - 0xE4); PageProtectOff(); if (bIsNew) { pIdtEntry[InterruptIndex].selector = 0xA8; //10101 000 //低1 2位 RPL用於檢測權限 低 3 位用於選擇 GDT 或者 LDT 高五位用於表明表中索引號 這個索引是21 RtlCopyMemory(&pGdt[21],&pGdt[1],sizeof(KGDTENTRY)); pGdt[21].BaseLow = (USHORT)(uNewBase&0xffff); pGdt[21].HighWord.Bytes.BaseMid = (UCHAR)((uNewBase>>16)&0xff); pGdt[21].HighWord.Bytes.BaseHi = (UCHAR)(uNewBase>>24); //把原來的段描述符拷過來 修改段描述符的基址 }else{ pIdtEntry[InterruptIndex].selector = 0x8; memset(&pGdt[21],0,sizeof(KGDTENTRY)); } PageProtectOn(); u_index++; } } VOID HookInterruptFunc(ULONG InterruptIndex,ULONG NewInterruptFunc) { ULONG uNewBase; g_uOrigInterruptFunc = GetInterruptFuncAddress(InterruptIndex); uNewBase = NewInterruptFunc - g_uOrigInterruptFunc; //段基地址 + g_uOrigInterruptFunc = NewInterruptFunc *(ULONG*)g_FilterJmp = (ULONG)NewInterrupt3OfOrigBase; g_FilterJmp[2] = 0x8; SetInterrupt(InterruptIndex,uNewBase,TRUE); } void UnHookInterruptFunc(ULONG InterruptIndex) { SetInterrupt(InterruptIndex,0,FALSE); } VOID MyUnload(PDRIVER_OBJECT pDriverObject) { UnHookInterruptFunc(3); } NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING Reg_Path) { HookInterruptFunc(3,(ULONG)NewInterrupt3); pDriverObject->DriverUnload = MyUnload; return STATUS_SUCCESS; }