现在的位置: 首页 > 综合 > 正文

XP中Inline HOOK 8042驱动实现键盘钩子的方法

2013年10月27日 ⁄ 综合 ⁄ 共 4225字 ⁄ 字号 评论关闭
首先说说我写这个驱动的动机,说来特别搞笑。首先是QQ的Nportect有时候会搞得我的键盘按键混乱,这搞得我很郁闷。这使我对键盘产生了兴趣,但是真正让我动手写的原因却更有趣。公司有一台老机器给我们用于测试,但机器的键盘却不好用,我们只能把测试用的命令复制到文本中上传上去。如此的不便使我动手写了一个远程控制键盘的驱动,由于我不知道每个键的scancode,网上的资料又乱七八糟懒得仔细看了,所以顺编写了这个按键记录的东东,本文的重点是讨论这个按键记录的东东。(遗憾的是在我完成前测试机的键盘已经被换掉了)。

先简单谈谈键盘的工作原理(具体的资料请再网上搜索),顺便说一下本文涉及的所有知识是我通过调试、试验、查书自己理解出来的,如果有错误请您务必与我联系,我将十分感谢。键盘的工作原理十分简单,键盘可以理解为一个按键矩阵和一个8048芯片,每个键都有个坐标值(并不准确,真实情况并不是一一对应,所以非机械键盘一定会有按键冲突),8042能把坐标成scancode,如果你有一个硬件的记录器,接在他们中间,Ok,Game Over了,你已经搞定了,请点击窗口右上角的叉。和我一样贫困的你,显然没有钱购买一个硬件记录器,所以我们还得花点精力继续Hack吧。8042会把这个scancode发给pc上的8048,8048偷偷告诉从8259a,8259a又告诉另一个主8259a叫它通知CPU在哪个IRQ上产生了一个中断,完了CPU就忙着去处理它了……嗯大体经过就是这样啦,大体了解到这个程度就行了。

好了,那我们该怎么办呢,以你敏锐的hacker直觉会马上想到,IDTHOOK呗。但是以我经历过的惨痛的教训可以告诉你,我不能再系统的ISR之前就吧0x60端口的数据读出来,这样0x60里的数据会清空,系统就无法得到这次按键了。你想说充填数据么?放弃吧,对0x60的充填导致了数次的BSOD,因为充填数据又将触发中断造成递归调用,你的机器将死于栈溢。我还企图屏蔽中断,结果是死锁。这些失败最终我放弃了这个方案。如果你充填成功了,一定记得告诉我昂,我的机器是Sonys56c/b 系统是Winxp sp2。我希望这能让你少走弯路。难道没办法了么,我跟到了i8042驱动(i8042prt.sys)里,很明显处理按键中断的例程是I8042KeyboardInterruptService。首先我想到的是替换它,但是入口都是IN参数,实在无法改变什么。无奈之下我最终选择了InlineHook,hoho~

首先是当然是得到它的地址啦,从IDT下手,开始找吧~sidt得到idtr的48,取后两个16位MAKELONG,这是IDT的入口,IDT里放的都是门结构,什么中断门啊、陷阱门啊、调用门啊……,门都长得不一样看仔细。然后呢,这里我有点糊涂,xpsp2的中断调用号为啥有的是0x93有的是0x31捏?不管了,我的是0x93,用WinDbg很容易看出来。反正要查也不太难,这里我都写死了。废话少说,赶快找到找到IDT里第0x93个门看看吧。门结构看起来是这样的

The cpp code:
  1. typedef struct  
  2. {   
  3. unsigned short LowOffset;   
  4. unsigned short selector;   
  5. unsigned char unused_lo;   
  6. unsigned char segment_type:4; //0x0E is an interrupt gate   
  7. unsigned char system_segment_flag:1;   
  8. unsigned char DPL:2; // descriptor privilege level    
  9. unsigned char P:1; /* present */  
  10. unsigned short HiOffset;   
  11. } IDTENTRY;  

聪明的你一定知道MAKELONG(LowOffset,HiOffset)将指引你去那,我比较笨找了很久才知道,传说中有这么个结构叫_KINTERRUPT,呵呵,问问WinDbg它长什么样吧
>!dt _KINTERRUPT
Int2B        Type             0x000
Int2B        Size              0x002
_LIST_ENTRY    InterruptListEntry   0x004
Ptr32        ServiceRoutine   0x00c
Ptr32 Void     ServiceContext   0x010
Uint4B       SpinLock         0x014
Uint4B      TickCount        0x018
Ptr32 Uint4B   ActualLock       0x01c
Ptr32       DispatchAddress  0x020
Uint4B      Vector           0x024
UChar       Irql             0x028
UChar       SynchronizeIrql  0x029
UChar       FloatingSave     0x02a
UChar       Connected        0x02b
Char       Number           0x02c
UChar       ShareVector      0x02d
_KINTERRUPT_MODE Mode             0x030
Uint4B      ServiceCount     0x034
Uint4B      DispatchCount    0x038
[106] Uint4B   DispatchCode     0x03c

忠实可靠的WinDbg不厌其烦的告诉你这就是传说中的_KINTERRUPT,我在惊叹WinDbg的博学多才之余很快地发现了原来MAKELONG(LowOffset,HiOffset)龌龊的指向了DispatchCode这里,而I8042KeyboardInterruptService的地址安详的躺在ServiceRoutine处。秘密一览无余了,哈哈。接下来是我最擅长的类型猜解,恩,很有难度,比1难多了……

我们轻易的就得到了入口地址,看看它都干了些什么吧(不能贴出所有代码,有长度限制,只能这样了)
f9a1c495 6a18            push    18h
f9a1c497 68a8f7a1f9      push    offset i8042prt!`string'+0x154 (f9a1f7a8)
f9a1c49c e8ff000000      call    i8042prt!_SEH_prolog (f9a1c5a0)
……
f9a1c4d1 8d45e3          lea     eax,[ebp-1Dh]
f9a1c4d4 50              push    eax
f9a1c4d5 6a01            push    1
f9a1c4d7 e818ffffff      call    i8042prt!I8xGetByteAsynchronous (f9a1c3f4)
f9a1c4dc 8d864a010000    lea     eax,[esi+14Ah]
f9a1c4e2 8a08            mov     cl,byte ptr [eax]
f9a1c4e4 888e4b010000    mov     byte ptr [esi+14Bh],cl
f9a1c4ea 8a4de3          mov     cl,byte ptr [ebp-1Dh]
……
f9a1c593 e848000000      call    i8042prt!_SEH_epilog (f9a1c5e0)
f9a1c598 c20800          ret     8
以你精湛的汇编功底一定比我看得快,其实我压根就没看,跟到I8xGetByteAsynchronous这里面看看,果然在这读端口了,返回后eax就是数据,好吧,不用往下看了,开始表演杂技吧,把你的代码贴进来吧!!从偏移0xf9a1c4dc - 0xf9a1c495出开始覆盖代码吧!!关于运行时补丁的方法这里不再赘述了,可以阅读著名的Detours: Binary Interception of Win32 Functions。

文章不错!但是关于键盘原理的描述上有些小语误:

很早以前,键盘端的芯片叫8048、PC端的芯片叫8042,为了兼容早期6针脚的 XT/AT 键盘,不知道是什么组织一共设计了三套键盘扫描码(我怀疑是IBM最早定义的)。

键盘端8048检测到矩阵状态变化时产生扫描码2,传输到PC端后8042又会转码成扫描码1(因为6针脚的 XT/AT 键盘用 Scan Code 1,做芯片的时候要考虑的比较多)

然后8042触发8259A级联第一片的IRQ1,再下面就是你Hook的舞台了~

由于实模式和保护模式的中断是不同的,所以需要重定位。用 Alexander 写的 Interrupt Hook 来看(代码和Rootkit.com的代码基本类似),可以很容易的发现,Win32在保护模式下将8259A的中断地址(IRQ0 - IRQ15)重定位到端口 0x30 - 0x3F,所以,这就是“xpsp2的中断调用号为啥有的是0x93有的是0x31捏?”的原因 —— 0x93的确是真正的键盘中断端口,我没研究过,但我怀疑这是Win键盘驱动造成的(键盘驱动还要干好多事,光是支持组合键这功能写代码就能把人烦死...)。

最后,现在的机器里都没有这些芯片了,这是最老早的 IBM PC 的设计,现在它们都集成在南桥里了,台湾的威盛搞这个很悍(收复台湾...)。另外,买一个硬件键盘嗅探器没多少钱。

看代码去了~ 呵呵

抱歉!评论已关闭.