R3卸载任意消息钩子

这是工作中遇到的一个问题。一个程序每次起来后会去挂鼠标键盘低级钩子,这类钩子恶心的地方是如果你用调试工具attach上去,鼠标键盘就会急剧的延时,就跟挂起差不多了,根本无法使用键盘鼠标。MSDN上面指明,设置LowLevelHooksTimeout可以帮助解决这个问题。但是无奈的是,似乎没起什么作用,渣英语,不知道是不是我的理解有误。

其他比较好的解决方案也有。比较好的一个就是用远程调试的方式,这种方式可以在hook存在的情况下调试程序,甚至调试hook的函数。另一个办法就是attach之前,卸载低级钩子。对于调试和钩子无关地方的时候,第二个选择也还是不错的。所以,Xuetr的卸载消息钩子的功能派上了用场。每次调试此程序之前,都先卸载钩子。但是还是有问题,我们都有这样的经验,在进行调试的时候经常需要restart程序,并且重新开始调试。这样可就恶心了,每次都要卸载一次钩子。于是,我就写了一个程序,循环查询低级鼠标键盘钩子,发现后立刻卸载,这样调试这个程序就会比较轻松了。

虽然说用驱动写这个功能看起来比较轻松,实际上R3实现也很简单。这里用到的关键之时是Desktop Heap会在GUI进程中映射到用户态内存上,这也就给了我们可乘之机。简单介绍一些Desktop Heap是什么。我们都知道一个桌面都有个Desktop Object的对象,而实际上美国Desktop Object都会有一个对应他的Desktop Heap。Desktop Heap主要存储用户交互对象(user interface objects),这其中就包括Window,Menu,Hook等等。既然Hook存储在Desktop heap,而且Desktop heap又刚好映射到R3内存,那么我们就可以顺利的读取他了。这里,可能会有一个疑问,怎么知道Hook存储在Desktop Heap,而不是Share Heap或者其他。实际上Windows的Win32k中有一个Handle Information Table,指明了每种Object的存储类型。

现在是已经知道了可以去读Hook对象,但是上哪去读就是要解决的问题了。这里就要提到老生常谈的Sharedinfo了。用户态的Sharedinfo获取方法很多,顺手就行。与上面问题相关的就是Sharedinfo里面会有一个Handle Entry Table,里面存储的就是包括Hook在内的User Object。Entry的结构如下(来自reactos):

1
2
3
4
5
6
7
8
9
10
11
typedef struct _HANDLEENTRY
{
PHEAD pHead;
PVOID pOwner;
BYTE bType;
BYTE bFlags;
WORD wUniq;
} HANDLEENTRY, *PHE, *PHANDLEENTRY;

其中pHead指向Object,bType表示object类型。HOOK的bType是5。所以这里我们只需要在bType为5的时候继续下面的操作。

pHead肯定是指向的内核内存,所以我们无法直接访问HOOK的内部情况。我们需要找到这个Object映射到R3的内存地址才行。幸运的是这种关系也比较简单明了。在Teb->Win32ClientInfo.ulClientDelta就存放了对应的关系Delta值。计算方法如下

ObjectInR3 = HANDLEENTRY.pHead - Teb->Win32ClientInfo.ulClientDelta。

20121226220202

这样也就得到了HOOK Object。接下来的事情就好办了,HOOK Object的第一项就是HHOOK。只需要UnhookWindowsHookEx((HHOOK)Hook->head.h);就能卸载钩子了。

NTInternals