当前位置: 首页 > 科技观察

网络安全编程:Windows钩子函数

时间:2023-03-20 19:35:56 科技观察

Windows下的窗口应用程序是消息驱动的,但在某些情况下需要捕获或修改消息来完成一些特殊的功能。对于消息的捕获,不能使用IAT或者InlineHook等方法来捕获,但是Windows提供了专门用于处理消息的钩子函数。01Hook原理Windows下的大部分应用程序都是基于消息模式机制的,有一些CUI程序并不是基于消息的。Windows下的应用程序都有消息处理功能,根据不同的消息完成不同的功能。Windows操作系统提供的钩子机制的作用是拦截和监听系统中的消息。Windows操作系统提供了许多不同种类的挂钩,可以处理不同的消息。Windows系统提供的钩子根据钩子作用域分为局部钩子和全局钩子。局部钩子是针对一个线程的,而全局钩子是针对整个操作系统中基于消息的应用程序的。全局钩子需要用到DLL文件,钩子函数的代码存放在DLL文件中。操作系统安装全局钩子后,只要进程收到可以发出钩子的消息,操作系统就会自动或强制加载全局钩子的DLL文件到进程中。可见设置消息钩子也是DLL注入的一种方法。02钩子函数钩子函数主要有3个,分别是SetWindowsHookEx()、CallNextHookEx()和UnhookWindowsHookEx()。下面介绍如何使用这些功能。SetWindowsHookEx()函数的定义如下:HHOOKSetWindowsHookEx(intidHook,HOOKPROClpfn,HINSTANCEhMod,DWORDdwThreadId);这个函数的返回值是一个钩子句柄。该函数有4个参数,如下所述。lpfn:该参数指定了Hook函数的地址。如果dwThreadId参数赋值为0,或者设置为另一个进程中的线程ID,那么lpfn属于DLL中的函数过程。如果dwThreadId是当前进程中的线程ID,那么lpfn可以指向当前进程中的函数过程,也可以是DLL中的函数过程。hMod:该参数指定钩子函数所在模块的模块句柄。模块句柄是lpfn所在模块的句柄。如果dwThreadId是当前进程中的线程ID,并且lpfn指向的函数在当前进程中,那么hMod将被设置为NULL。dwThreadId:该参数设置为要挂钩的线程的ID号。如果设置为0,表示在所有线程中挂钩(这里的“所有线程”是指基于消息机制的所有线程)。如果指定为特定线程的ID号,则表示挂接指定线程。该参数影响以上两个参数的取值。该参数的值决定了钩子是全局钩子还是局部钩子。idHook:该参数表示钩子的类型。由于hook的种类很多,所以在所有的参数之后介绍。下面介绍几种常用的钩子,也可能是大家比较关心的几种。1.WH_GETMESSAGE安装这个钩子的作用是监听投递到消息队列的消息。即调用GetMessage()或PeekMessage()函数时,该函数在从程序的消息队列中获取消息后调用钩子。WH_GETMESSAGE钩子函数的定义如下:2.WH_MOUSE安装这个钩子的作用是监听鼠标消息。钩子函数定义如下:LRESULTCALLBACKMouseProc(intnCode,//钩子代码WPARAMwParam,//消息标识符LPARAMlParam//鼠标坐标);3.WH_KEYBOARD安装这个钩子的作用是监听键盘消息。钩子函数的定义如下:LRESULTCALLBACKKeyboardProc(intcode,//钩子码WPARAMwParam,//虚拟键码LPARAMlParam//按键信息);4.WH_DEBUG安装这个钩子的作用是调试其他钩子的钩子函数。钩子函数的定义如下:LRESULTCALLBACKDebugProc(intnCode,//钩子代码WPARAMwParam,//钩子类型LPARAMlParam//调试信息);从上面的钩子函数定义可以看出,每个钩子函数的定义都是一样的。每种类型的钩子监视和拦截不同的消息。虽然它们的定义相同,但函数参数的含义不同。接着介绍另一个与hook相关的函数:UnhookWindowsHookEx(),定义如下:BOOLUnhookWindowsHookEx(HHOOKhhk//handleofthehookfunction);此函数用于删除以前使用SetWindowsHookEx()安装的挂钩。该函数只有一个参数,即钩子句柄,即调用该函数通过指定的钩子句柄移除对应的钩子。在操作系统中,可以重复使用SetWindowsHookEx()函数来安装一个钩子,可以安装多个相同类型的钩子。这样,挂钩形成了一个挂钩链,安装的最后一个挂钩最先拦截消息。当钩子处理完消息后,会选择返回,或者选择继续传递消息。一般情况下,如果想屏蔽消息,直接在钩子函数中返回一个非零值即可。比如在自己的程序中想屏蔽鼠标消息,可以在安装的鼠标钩子函数中直接返回一个非零值即可。如果消息在经过钩子函数后还能继续投递到目标窗口,则必须选择继续投递消息。使消息继续传递的函数定义如下:LRESULTCallNextHookEx(HHOOKhhk,//当前钩子的句柄intnCode,//传递给钩子函数的钩子代码WPARAMwParam,//传递给的值钩子函数LPARAMlParam//传递给钩子函数的值);该函数有4个参数。第一个参数是钩子句柄,是调用SetWindowsHookEx()函数的返回值;接下来的三个参数是钩子函数的参数,可以直接依次复制。例如:HHOOKg_Hook=SetWindowsHook(...);LRESULTCALLBACKGetMsgProc(intcode,//hookcodeWPARAMwParam,//removeoptionLPARAMlParam//message){returnCallNextHookEx(g_Hook,code,wParam,lParam);}03Hook实例Windowshook应用比较广泛。无论是安全产品还是恶意软件,甚至是正规软件,都会用到Windows提供的hook函数。1.全局键盘钩子接下来,编写一个可以拦截键盘消息的钩子程序。它的功能很简单,就是显示被按下的键对应的字符。既然要拦截键盘消息,那肯定是拦截系统范围的键盘消息,所以需要安装一个全局钩子,这需要DLL文件的支持。让我们先创建一个新的DLL文件。在这个DLL文件中,需要定义两个导出函数和两个全局变量。定义如下:extern"C"__declspec(dllexport)VOIDSetHookOn();extern"C"__declspec(dllexport)VOIDSetHookOff();//HookhandleHHOOKg_Hook=NULL;//DLL模块句柄HINSTANCEg_Inst=NULL;在DllMain()函数中,需要保存DLL模块的句柄,方便安装全局钩子。代码如下:BOOLAPIENTRYDllMain(HANDLEhModule,DWORDul_reason_for_call,LPVOIDlpReserved){//保存DLL的模块句柄g_Inst=(HINSTANCE)hModule;returnTRUE;}安装和卸载钩子函数如下:VOIDSetHookOn(){//安装hookg_Hook=SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,g_Inst,0);}VOIDSetHookOff(){//UnhookWindowsHookEx(g_Hook);}对于Windowshooks,以上步骤基本是必须的,或者区别不大,关键在于钩子函数的实现。这里是获取键盘按下的按键,钩子函数如下://钩子函数LRESULTCALLBACKKeyboardProc(intcode,//钩子码WPARAMwParam,//虚拟键码LPARAMlParam//按键构建信息){if(code<0){returnCallNextHookEx(g_Hook,code,wParam,lParam);}if(code==HC_ACTION&&lParam>0){charszBuf[MAXBYTE]={0};GetKeyNameText(lParam,szBuf,MAXBYTE);MessageBox(NULL,szBuf,NULL,MB_OK);}returnCallNextHookEx(g_Hook,code,wParam,lParam);}关于钩子函数,这里简单说明一下,首先是进入钩子函数的第一个判断。if(code<0){returnCallNextHookEx(g_Hook,code,wParam,lParam);}如果code的值小于0,必须调用CallNextHookEx(),继续传递消息,不处理消息,返回到CallNextHookEx()函数的返回值。这是MSDN上的要求。if(code==HC_ACTION&&lParam>0){charszBuf[MAXBYTE]={0};GetKeyNameText(lParam,szBuf,MAXBYTE);MessageBox(NULL,szBuf,NULL,MB_OK);}如果code等于HC_ACTION,表示消息包含关键消息;如果是WM_KEYDOWN,则显示与键对应的文本。编译并链接DLL文件。为了测试DLL文件,新建一个MFCDialog工程,添加两个按钮,如图1所示。图1键盘钩子的测试程序为两个按钮添加代码,如下:在这里添加控件通知的handlerSetHookOff();}直接调用DLL文件导出这两个函数,但是必须在使用前声明这两个函数,否则编译器会因为找不到这两个函数的原型而导致连接失败。定义如下:extern"C"VOIDSetHookOn();外部"C"VOIDSetHookOff();编译链接,提示错误,内容如下:Linking...HookTestDlg.obj:errorLNK2001:unresolvedexternalsymbol_SetHookOnHookTestDlg.obj:errorLNK2001:unresolvedexternalsymbol_SetHookOffestDebug:fatalerrorLNK1120:2unresolvedexternalsErrorexecutinglink.exe.HookerrorTest(s),0-3warningTest(s)(s)从给出的提示可以看出是连接错误,找不到外部符号。将DLL编译链接后生成的DLL文件和LIB文件复制到测试工程目录下,并将LIB文件添加到工程中。在代码中加入如下语句:#pragmacomment(lib,KeyBoradHookTest)再次连接,成功!运行测试程序,点击“HookOn”按钮,按键盘任意键,会出现提示对话框,如图2。图2截获的键盘输入从图2可以看出,当一个键按下键盘上的键,程序将捕获该键。至此,键盘钩子的示例程序就完成了。2.低级键盘挂钩。防数据泄露软件通常会禁止PrintScreen键,以防止数据通过截图和保存为图片的方式泄露。这种软件实现起来比较简单,但是要让功能更加强大,还是需要下功夫的。除了兼容性好的底层驱动设计外,防数据泄露软件还需要有完善的规则设置。此外,还需要软件安全人员对其进行各种攻击,以防止数据泄露防护软件被恶意人员以各种理由破解。以下是禁用PrintScreen键的方法。其实很简单,只要安装低级键盘钩子(WH_KEYBOARD_LL)就大功告成了。普通键盘钩子(WH_KEYBOARD)不能过滤一些系统键。在低级键盘钩子的回调函数中,判断是否是PrintScreen键,如果是则直接返回TRUE(前面说了如果要屏蔽某个消息,那么在钩子函数中处理消息后,直接返回一个非零值),如果没有,传递给下一个钩子链。代码如下:extern"C"__declspec(dllexport)BOOLSetHookOn(){if(g_hHook!=NULL){returnFALSE;}g_hHook=SetWindowsHookEx(WH_KEYBOARD_LL,LowLevelKeyboardProc,g_hIns,NULL);if(NULL==g_hHook){MessageBox(NULL,"error",MB_ICONSTOP);returnFALSE;}returnTRUE;}extern"C"__declspec(dllexport)BOOLSetHookOff(){if(g_hHook==NULL){returnFALSE;}UnhookWindowsHookEx(g_hHook);g_hHook=NULL;returnTRUE;}LRESULTCALLBACKLowLevelKeyboardProc(intnCode,WPARAMwParam,LPARAMlParam){KBDLLHOOKSTRUCT*Key_Info=(KBDLLHOOKSTRUCT*)lParam;if(HC_ACTION==nCode){if(WM_KEYDOWN==wParam||WM_SYSKEYDOWNif==wParam{o->vkCode==VK_SNAPSHOT){returnTRUE;}}}returnCallNextHookEx(g_hHook,nCode,wParam,lParam);}代码量很短,但是正是这段短代码防止了数据泄露。当然,这段代码不能保护数据免受攻击者的攻击,而且保护很弱。任何保护都有办法突破,攻击无处不在,攻击者会想方设法突破所有保护。3、使用hooks进行DLL注入Windows提供了多种类型的hooks,其中一种非常有用,那就是WH_GETMESSAGEhooks。它可以轻松地将DLL文件注入所有基于消息的程序。在某些情况下,需要DLL文件来完成一些功能,但功能完成时DLL需要在目标进程的空间中。这时候就需要使用WH_GETMESSAGE消息将DLL注入到目标进程中。代码很简单,这里直接是DLL文件的代码,如下:HHOOKg_HHook=NULL;HINSTANCEg_hInst=NULL;VOIDDoSomeThing(){/*......实现功能的代码......*/}BOOLWINAPIDllMain(HINSTANCEhinstDLL,//DWORDfdwReasonoftheDLLmodulehandle,//原因调用函数LPVOIDlpvReserved//Reserved){switch(fdwReason){caseDLL_PROCESS_ATTACH:{g_hInst=hinstDLL;DoSomeThing();break;}}returnTRUE;}LRESULTCALLBACKGetMsgProc(intcode,//hookcodeWPARAMwParam,//removeoptionLPARAMlParam//message){returnCallNextHookEx(g_HHook,code,wParam,lParam);}VOIDSetHookOn(){g_HHook=SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,g_hInst,0);}VOIDSetHookOff(){UnhookWindowsHookEx(g_HHook);}整个代码是这样的。只要知道当需要将DLL广泛注入到基于消息的进程中时,可以使用此方法。