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 }
 
时间: 2024-10-13 23:40:02

IDT HOOK思路整理的相关文章

IDT Hook和GDT的学习

对于IDT第一次的认知是int 2e ,在系统调用的时候原来R3进入R0的方式就是通过int 2e自陷进入内核,然后进入KiSystemService函数,在根据系统服务调用号调用系统服务函数.而2e就是IDT(系统中断描述符表)中的索引位2e的项,而KiSystemService就是该项的例程函数,后来为了提升效率,有了系统快速调用,intel的的cpu通过sysenter指令快速进入内核,直接通过kiFastCallEntry函数调用系统服务函数,各种杀软也做了这个地方的Hook来监控系统调

快速排序思路整理

引言: 快速排序和归并排序是面试当中常常被问到的两种排序算法,在研究过数据结构所有的排序算法后,个人认为最复杂的当属快速排序.从代码量上来看,快速排序并不多,我之所以认为快排难以掌握是因为快排是一种递归算法,同时终止条件较多.如果你刚刚把快排的思路整理过一遍可能觉得不难,然而一个月之后呢? 面试要求的是临场发挥,如果不是烂熟于心,基本就卡壳了.在面试官眼里,你和那些完全不懂快速排序算法的菜逼是一样的,也许实际上你可能私底下已经理解很多遍了,然而并没卵.所以当下问题就是,如何将快排烂熟于心?我觉得

搜索与排名思路整理

学习<集体智慧编程>第4章的思路整理: 本章的主要内容就是建立一个模拟的全文搜索引擎,主要的大步骤为:1.检索网页,2.建立索引,3.对网页进行搜索 4.多种方式对搜索结果进行排名 一.检索网页:主要利用python写了一个爬虫软件,通过爬取一个网站上链接来不断的扩充爬取的内容.主要利用了python的urllib库和BeautifulSoup库.这部分比较简单,核心代码如下: def crawl(self,pages,depth=2): for i in range(depth): newp

16 飞机大战:思路整理、重要代码

思路整理 重要代码 0.重写方法万万检查记得要不要继承父类方法 def __init__(self): super().__init__() 1.创建游戏时钟:用来设置游戏刷新率 # 新建游戏时钟对象 self.clock = pygame.time.Clock() ... ... # 设置游戏刷新率 self.clock.tick(60) #60帧/s 2.精灵组 # 创建xx精灵 self.xx = Xx() #其中Xx是Xx类 # 创建xx精灵组 self.xx_group = pygam

能力库开发思路整理

能力库界面如下: 相关数据库表: 1 CREATE TABLE `base_ability` ( 2 `abillty_id` varchar(36) NOT NULL DEFAULT '' COMMENT '主键', 3 `ability_code` varchar(20) DEFAULT NULL, 4 `ability_name` varchar(20) DEFAULT NULL COMMENT '能力编号', 5 `ability_name_desc` varchar(255) DEFA

Canvas---Canvas图像处理、图片查看器实现思路整理、拖动边界控制

没想到一个图片查看器花了我这么多时间,而且没做好. 现在整理下思路,然后把不足的地方记一下,日后请教他人. 基本思路: 一.图片查看器功能---缩放 要实现自由缩放,先要实现图片对canvas的自适应,就是给你一张大图片,你能够把它合理缩放后恰好绘制在canvas中. 具体做法是:例如:图片为500*500,canvas为240*320,那就取缩放宽度为240,长度为240/500*500,利用缩放宽度与长度,绘制图片即可. 然后是自由缩放,这时,你的缩小放大对象只要是一个矩形就好,然后图片去适

Lync2013 升级Skype For Bussiness 2015 升级思路整理

最近做了次Lync 2013企业版升级到SFB 2015,期间碰到了各种问题.这里就专门整理下升级的思路. 至于升级过程实战的文章,后续有空再写写,其实还是很简单的. 后续todo:SFB 2015 后端alwaysOn建立,Lync 2013升级至SFB 2015并且后端进行AlwaysOn高可用建立(这个可能不靠谱--) 简要升级路线: Lync 2013:使用新的拓扑生成器生成新拓扑并发布,然后在池的每台关联服务器上就地升级功能升级 Lync 2010:首先升级至Lync 2013,然后使

Java做界面思路整理

说起大一就学过C++,但从未接触过VC++,至于做界面也是直到学java才开始,所以自己还是个新手啊... 步入正题,通过自己写的两个小程序,对做界面的思路进行一下整理. 首先,构想出自己想要实现的界面是什么样子.可以在纸上画出个轮廓(我是这么干的...),尽量详尽,比如点击按钮后的实现一个页面的跳转,跳转之后的页面也画出来.为什么要这样呢?都知道界面是由控件和容器组成的,画的目的就是清楚要用哪些组件,并且根据自己的界面,然后组织容器,再进而组织布局.对于布局可能会比较麻烦一点,这要根据你的窗口

内核编程键盘过滤几种方法思路整理

第一种:绑定kbdcalss驱动对象 kbdclass类驱动对象是键盘的最上层的驱动对象,对它的分发函数进行处理,则不用考虑底层的兼容性问题. 思路:首先使用ObReferenceObjectByName函数打开kbdclass驱动对象,然后使用DeviceObject指针和NextDevice指针遍历kbdclass驱动对象下所有的设备对象,每遍历一个则创建一个设备对象附加在其上,这样我们编写的驱动对象的分发函数,即可处理kbdclass类驱动对象的IRP.如图所示 第二种:直接替换kbdcl