人, 既无虎狼之爪牙,亦无狮象之力量,却能擒狼缚虎,驯狮猎象,无他,唯智慧耳。
全部博文(167)
分类: WINDOWS
2012-05-10 14:31:20
HOOK学习笔记
一、概述
千呼万唤始出来,终于开始学习Win32 HOOK机制了。虽然说HOOK早已不是什么新技术,但是对于自己而言却是第一次接触。之前更多地只是从检测木马的钩子中感性地认识HOOK。由于之前从来没有真正接触过,所以一上来缺的知识模块比较多。作为第一篇关于HOOK的学习笔记,先就HOOK的基本知识做个小结,然后给出一个简单的通过控制台实现的键盘钩子例子。本篇笔记结构如下:
l Windows消息机制
l Windows HOOK机制(全局HOOK)
l 核心函数
l 简单程序示例
l 问题
二、Windows消息机制
Windows操作系统是建立在事件驱动机制之上的,系统各部分之间的沟通也都是通过消息的相互传递而实现的。但在通常情况下,应用程序只能处理来自进程内部的消息或是从其他进程发过来的消息(借助进程间通信技术,如剪贴板,管道,邮槽等),如果需要对在进程外传递的消息进行拦截处理就必须采取一种称为HOOK的技术。HOOK作为Windows操作系统中非常重要的一种系统接口,用它可以轻松截获并处理在其他应用程序之间传递的消息,并由此可以完成一些普通应用程序难以实现的特殊功能。首先我们对第一次接触Windows编程的同学们简单介绍一下Windows的消息机制。
我们知道Windows系统各个部分的通信是通过发送Message进行的,这里的其实是一个MSG结构体:
typedef struct tagMSG {
HWND ;
UINT ;
WPARAM ;
LPARAM ;
DWORD ;
POINT ;
} MSG;
具体的用法感兴趣的同学可以去查阅下MSDN,这里还是啰嗦一句,学习Windows编程的
朋友们还是一定要去下载一个MSDN,用起来真的很方便。大致的过程是当系统I/O上发生一个事件时,系统捕获该事件,并向指定的应用程序的消息队列发送一个消息,应用程序从消息队列中顺次取出一个消息,交由系统调度相应的窗口回调程序进行消息处理。这里我们给出一个结构图方便大家理解。
这里可以看到,从OS捕捉到消息开始处理,到最后交还给OS调度回调函数,就像走了一个循环,我自己理解这也是为什么叫做“回调函数”的原因之一。接下来我们要进行的HOOK就是在上面的第二步和第三步之间进行的额外工作。
三、Windows HOOK机制
HOOK(钩子)的本质是一段用以处理系统消息的程序,通过系统调用,将其挂入到系统。钩子的种类有很多,每一种钩子负责截获并处理相应的消息。钩子机制允许应用程序截获并处理发往指定窗口的消息或特定事件,其监视的窗口既可以是本进程内的也可以是由其他进程所创建的。在特定的消息发出并到达目的窗口之前,HOOK程序先行截获此消息并得到对其的控制权。此时在钩子函数中就可以对截获的消息进行各种修改处理,甚至强行终止该消息的继续传递。
这里自己一开始没有弄明白的地方是,HOOK的多少是按照HOOK的种类来划分的,比如WH_MOUSE, WH_KEYBOARD等。但是每一个HOOK实质上都由系统维护着一个指针列表,其指针指向HOOK的各个处理函数,我们称之为HOOK子程。当调用SetWindowsHookEx()时在该HOOK链的最开始安装一个新的HOOK子程,有时不影响理解时我们也说成是安装一个新HOOK,最早的HOOK则放在链表的最后(有点类似于栈)。当HOOK监视的消息出现时,操作系统调用链表开始处的第一个HOOK子程进行处理,也就是最后加入的HOOK优先获得控制权。这里的HOOK处理函数必须是一个回调函数,而且不能定义为类成员函数,必须为普通的C函数。在使用钩子时根据其监视范围的不同可以将其分为全局钩子和线程钩子两大类,其中线程钩子指定某个线程ID(可以是当前线程),只能监视该线程;全局钩子可以对同一个窗口下的所有线程进行监视。这里的全局HOOK的本质还是由触发HOOK机制的线程调用自身进程空间中的代码进行处理,所以我们的HOOK子程代码必须映射进该线程所在的进程的地址空间,即通过DLL的方式实现。为了方便大家理解,这里我们也给出一个图示:
首先我们编写HOOK驱动器,将HOOK.dll映射进内存中,安装好HOOK后,进行HOOK监视:
四、核心函数
使用Windows HOOK所需要的核心函数不多,只有四个:
SetWindowsHookEx():安装一个HOOK
HOOK 子程:HOOK的处理函数,如GetMsgProc, KeyboardProc等
CallNextHookEx():调用HOOK链的下一个HOOK子程
UnhookWindowsHookEx():卸载一个HOOK
HHOOK SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId
);
第一个参数idHook用来标识HOOK类型,比如鼠标信息用WH_MOUSE,键盘消息用WH_KEYBOARD等,更多参数和功能请查阅MSDN
第二个参数lpfn指向一个具体的HOOK子程,用于实际处理拦截的消息
第三个参数hMod用来标识HOOK子程所在的模块,如果是一个全局HOOK,则是一个载入内存的DLL句柄(使用GetModuleHandle得到);如果是一个内部线程HOOK,则为NULL即可。
第四个参数dwThread指明HOOK的范围,如果是0则表示监视运行在同一个窗口下的所有线程,否则指定一个具体的线程ID即可。
BOOL UnhookWindowsHookEx(
HHOOK hhk
);
卸载一个HOOK,参数为SetWindowsHookEx()的返回值
LRESULT CallNextHookEx(
HHOOK hhk,
int nCode,
WPARAM wParam,
LPARAM lParam
);
这里的nCode需要我们填写传送个下一个HOOK子程的代码,如果是HC_ACTION,则wParam和lParam包含消息信息;如果是HC_NOREMOVE则wParam和lParam包含消息信息外,消息不能从消息队列中移除
LRESULT CALLBACK KeyboardProc(
int code,
WPARAM wParam,
LPARAM lParam
);
这里主要是nCode参数,需要根据这个参数的值决定如何处理消息。如果nCode的值小于0,则必须返回CallNexHookEx(),nCode可以取HC_ACTION和HC_REMOVE两个参数值,由系统负责写入,我们只是进行判断。详细的使用方法请查阅MSDN吧。我们下面给出一个HOOK键盘输入的控制台例子。
五、简单程序示例
首先是我们编写的采用隐式DLL的HOOK的DLL文件
//采用隐式链接提供钩子函数SetHook()安装一个全局键盘钩子
#include
#include
#define MYAPI extern "C" _declspec(dllexport) //导出函数声明,extern "C"要放在最前面
HHOOK hHook = NULL;
LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam)
{
if (code >= 0)
return 1; //消息不再传递个下一个HOOK子程,也不会再发送给目的窗口
else
return CallNextHookEx(hHook, HC_ACTION, wParam, lParam);
}
MYAPI int SetHook()
{
hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, GetModuleHandleA("KeyboardHookDLL.dll"), 0);
if (hHook == NULL)
{
printf("SetWindowsHookEx() error :%d\n", GetLastError());
return -1;
}
return 0;
}
MYAPI int StopHook()
{
if (UnhookWindowsHookEx(hHook) == FALSE)
{
printf("UnhookWindowsHookEx() error :%d\n");
return -1;
}
return 0;
}
MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal0
接下来是控制台程序,负责安装/卸载HOOK:
//一个负责安装、卸载键盘钩子的控制台程序
#include
#include
//链接指定库文件
#pragma comment(lib, "KeyboardHookDLL.lib")
//隐式DLL可执行模块中也必须声明DLL导入函数
extern "C" _declspec(dllimport) int SetHook();
extern "C" _declspec(dllimport) int StopHook();
int main(int argc, char *argv[])
{
char YesNo;
printf("这是一个关于全局键盘钩子的测试...\n");
printf("安装全局键盘钩子...\n");
SetHook();
printf("是否卸载键盘钩子: (Y or N)\n");
YesNo = getchar();
if (YesNo == 'Y' || YesNo == 'y')
{
printf("开始卸载钩子...\n\n");
StopHook();
printf("钩子已经卸载...\n");
}
system("pause");
return 0;
}
执行测试结果:
在word中输入时会弹出360警告,说明此时正将我们的HOOK.dll注入到word进程中,允许后在word中的所有键盘操作就都失效了,有兴趣的同学们可以自己试一下哦^_^
六、问题
在运行程序的时候发现了一个问题,就是当我们的控制台程序退出后,HOOK就不能使用了,当然,控制台不是正常退出,没有运行StopHook,但是为什么HOOK就已经失效了呢?难道要使HOOK起作用,我们的HOOK驱动要一直运行吗?