在IDT,Interrupt Descriptor Table,中断描述符表中详述了IDT的概念以及三种不同的描述符,现在用代码来实现Hook 所有IDT,代码出自combojiang大牛的文章http://bbs.pediy.com/showthread.php?t=59867。
思路是这样的:
通过修改ISR的入口地址跳转到自己写的汇编代码,然后保存当前堆栈环境,再跳转到自己的处理函数,进行完自己的处理后(下面的处理只是简单的记录中断被调用的次数),再跳转回原始ISR入口地址。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | #include <ntddk.h> #include <stdio.h> #define START_IDT_OFFSET 0x00 #define MAX_IDT_ENTRIES 0xff #define MAKELONG(a, b) ((unsigned long) (((unsigned short) (a)) | ((unsigned long) ((unsigned short) (b))) << 16)) unsigned long g_i_count[MAX_IDT_ENTRIES]; unsigned long old_ISR_pointer[MAX_IDT_ENTRIES]; #pragma pack(1) typedef struct { unsigned short IDTLimit; //IDT范围 占8位 unsigned short LowIDTbase; //基地址低8位 unsigned short HiIDTbase; //基地址高8位 } IDTINFO; typedef struct { unsigned short LowOffset; //中断处理函数地址低16位 unsigned short selector; unsigned char unused_lo; unsigned char segment_type:4; //0x0E is an interrupt gate unsigned char system_segment_flag:1; unsigned char DPL:2; // descriptor privilege level unsigned char P:1; /* present */ unsigned short HiOffset; //中断处理函数地址高16位 } IDTENTRY; #pragma pack() #ifdef _DEBUG char jump_template[] = { 0x90, //nop, debug 0x60, //pushad 0x9C, //pushfd 0xB8, 0xAA, 0x00, 0x00, 0x00, //mov eax, AAh 0x90, //push eax 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, //call 08:44332211h 0x90, //pop eax 0x9D, //popfd 0x61, //popad 0xEA, 0x11, 0x22, 0x33, 0x44, 0x08, 0x00 //jmp 08:44332211h }; #else char jump_template[] = { 0x90, //nop, debug 0x60, //pushad 0x9C, //pushfd 0xB8, 0xAA, 0x00, 0x00, 0x00, //mov eax, AAh 0x50, //push eax 0x9A, 0x11, 0x22, 0x33, 0x44, 0x08, 0x00, //call 08:44332211h 0x58, //pop eax 0x9D, //popfd 0x61, //popad 0xEA, 0x11, 0x22, 0x33, 0x44, 0x08, 0x00 //jmp 08:44332211h }; #endif void __stdcall count_interruptes(unsigned long inumber) { //ebp+0x4h unsigned long* aCountP; //sub esp ,4 unsigned long aNumber; //sub esp ,4 // due to far call, we need to correct the base pointer // the far call pushes a double dword as the return address // and I don't know how to make the compiler understand this // is a __far __stdcall (or whatever it's called) // anyway: // // [ebp+0Ch] == arg1 // __asm{ mov eax,[ebp+0Ch] mov aNumber,eax } aNumber = aNumber & 0x000000FF; //不挂钩0xFF aCountP = &g_i_count[aNumber]; InterlockedIncrement(aCountP); } char* idt_detour_tablebase; void HookInterrupt() { unsigned long count; IDTINFO idt_info; IDTENTRY* idt_entries; IDTENTRY* i; unsigned long addr; char _t[255]; for(count = START_IDT_OFFSET; count <= MAX_IDT_ENTRIES; count++) { g_i_count[count] = 0; } //加载idt_info __asm sidt idt_info; idt_entries = (IDTENTRY*)MAKELONG(idt_info.LowIDTbase,idt_info.HiIDTbase); //保存旧的IDT指针 for(count = START_IDT_OFFSET; count <= MAX_IDT_ENTRIES; count++) { i = &idt_entries[count]; addr = MAKELONG(i->LowOffset,i->HiOffset); _snprintf(_t,253,"interrupt id: %d ISR:0x%08x \n",count,addr); DbgPrint(_t); old_ISR_pointer[count] = MAKELONG(idt_entries[count].LowOffset,idt_entries[count].HiOffset); } //设定欺骗表,分配足够多的内存给跳转模板,这块内存当然位于非分页中 idt_detour_tablebase = (char*)ExAllocatePool(NonPagedPool,sizeof(jump_template)*256); for(count = START_IDT_OFFSET; count <= MAX_IDT_ENTRIES; count++) { //总跳模板中偏移 = 模板单位大小*序号 int offset = sizeof(jump_template)*count; //入口处 = 总模板起始地址 + 偏移 char* entry_ptr = idt_detour_tablebase + offset; //拷贝计算出来的模板入口 memcpy(entry_ptr, jump_template,sizeof(jump_template)); #ifndef _DEBUG //发布模式 //把模板中0xAA改为序号 entry_ptr[4] = (char)count; //来统计中断被调用了多少回 *((unsigned long*)(&entry_ptr[10])) = (unsigned long)count_interruptes; #endif //修正原始函数入口 *((unsigned long*)(&entry_ptr[20])) = old_ISR_pointer[count]; //将中断表项修改为刚创建的新的跳转模板 __asm cli //屏蔽中断 idt_entries[count].LowOffset = (unsigned short)entry_ptr; idt_entries[count].HiOffset = (unsigned short)((unsigned long)entry_ptr>>16); __asm sti //恢复中断 } DbgPrint("Hook interrupt complete...."); } void UnHookInterrupt() { int i; IDTINFO idt_info; IDTENTRY* idt_entries; char _t[255]; //加载idt_info __asm sidt idt_info; idt_entries = (IDTENTRY*)MAKELONG(idt_info.LowIDTbase,idt_info.HiIDTbase); for(i = START_IDT_OFFSET; i <= MAX_IDT_ENTRIES; i++) { _snprintf(_t,253,"interrupt id: %d called %d times \n",i,g_i_count[i]); DbgPrint(_t); } DbgPrint("unhooking interrupt...."); for(i = START_IDT_OFFSET; i <= MAX_IDT_ENTRIES; i++) { __asm cli //屏蔽中断 idt_entries[i].LowOffset = (unsigned short)old_ISR_pointer[i]; idt_entries[i].HiOffset = (unsigned short)((unsigned long)old_ISR_pointer[i]>>16); __asm sti //恢复中断 } DbgPrint("unhooking interrupt complete...."); } VOID OnUnload( IN PDRIVER_OBJECT DriverObject ) { UnHookInterrupt(); DbgPrint("OnUnload\n"); } NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath ) { theDriverObject->DriverUnload = OnUnload; HookInterrupt(); return STATUS_SUCCESS; } |
加载运行后,使用PCHunter查看中断描述表,发现都被修改了。
然后通过反汇编可以看到,已经跳到我们自己写的汇编代码了。
第一个call是调用count_interruptes函数增加调用次数。
第二个jmp是跳转回原始的ISR入口地址。
卸载后可以看到每个中断的调用次数:
短短的几分钟内可以调用近5万次!!!