windows钩子

来源:互联网 发布:大连库存 知乎 2017 编辑:程序博客网 时间:2024/05/16 17:42

钩子的概念:

钩子:是windows消息处理机制中的一个监视点,应用程序可以在这里安装一个子程序(钩子函数)以监视指定窗口某种类型的消息。

钩子函数:是一个处理消息的程序段,通过调用相关的API函数(SetWindowsHookEx()),把他挂入系统,每当特定的消息发出,当没有到达目的窗口前,钩子程序就先捕获该消息,这时,钩子函数既可以加工处理该消息,也可以不作处理而继续传递该消息。

钩子的安装与卸载:

SetWindowsHookEx函数把应用程序定义的钩子函数安装到系统中。该函数原型如下:

WINUSERAPIHHOOKWINAPISetWindowsHookExW(    __in int idHook,    __in HOOKPROC lpfn,    __in_opt HINSTANCE hmod,    __in DWORD dwThreadId);

idHook:指定了要安装的钩子类型;

dwThreadId:指定要与钩子函数关联的线程ID号,如果设为0,那么该钩子就是系统范围内的,即钩子函数将关联到系统内的所有线程。

lpfn:指定相应的钩子过程,也就是钩子函数的地址,如果dwThreadId参数为0,或者指定一个由其它进程创建的线程ID,那么参数lpfn指向的钩子函数必须位于一个DLL中。这是因为进程的地址空间是相互隔离的,发生事件的进程不能调用其它进程地址空间的钩子函数。如果钩子函数的实现代码在DLL中,在相关事件发生时,系统会把这个DLL插入到发生事件的进程的地址空间,使它能够调用钩子函数。这种需要把钩子函数写入DLL以便挂钩其它进程事件的钩子称为远程钩子

hmod:指定钩子函数所在DLL的句柄。如果不是远程钩子,那么该参数就设为NULL。

注意:多个钩子过程形成钩子链,最后安装的钩子过程总是排列在该链的前面。钩子会是系统变慢,因为增加了系统对每个消息的处理,应该在必要时才安装钩子,而且在不需要时应尽快移除。

函数UnhookWindowsHookEx(HHOOK hhk); 卸载钩子 // hhk为要卸载的钩子句柄

CallNextHookEx(): 把钩子信息传递给钩子链中写一个等待接收信息的钩子过程。

 

 

进程内钩子:

安装进程内钩子时,不需要用到DLL。

首先要声明钩子函数,下面分别声明了鼠标钩子函数和键盘钩子函数:

LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam);LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);

以及它们的实现:

//鼠标钩子函数LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam){return 1;}//键盘钩子函数LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam){//if(VK_SPACE == wParam)if(VK_F4 == wParam &&(1 == (lParam>>29 & 1)))return 1;elsereturn ::CallNextHookEx(g_hKeyboard, nCode, wParam, lParam);}

需要注意的地方:如果钩子函数返回非0值,表示已经对当前消息进行了处理,这样系统就不会再将这个消息传递给目标窗口过程了。

为了保存SetWindowsHookEx函数返回的钩子过程句柄,我们要定义连个全局变量来保存鼠标和键盘的钩子句柄:

HHOOK g_hMouse = NULL;HHOOK g_hKeyboard = NULL;

 

接下来就是安装钩子了,下面我们分别安装了鼠标和键盘钩子:

//安装鼠标钩子g_hMouse =::SetWindowsHookExA(WH_MOUSE, MouseProc, NULL, ::GetCurrentThreadId());//安装键盘钩子g_hKeyboard = ::SetWindowsHookExA(WH_KEYBOARD, KeyboardProc, NULL, ::GetCurrentThreadId());


其中GetCurrentThreadId()函数获取当前线程的ID。

关于键盘消息的处理

参数wParam是产生当前按键消息的键盘按键的虚拟键代码,这是Windows定义的,与设备无关。当按下键盘上的按键时,它实际上发送的是一个脉冲信号。Windows定义了一些虚拟键代码来表示这些信号,并由键盘设备驱动程序负责解释。 键盘虚拟键的宏都是以" VK_"开头的。

怎么判断组合键???  例如 Alt+F4

在键盘钩子函数中,还有另外一个参数lParam,它是一个32为的整数,它的每一位或某些位表示特定的含义,用来指定按键重复的次数、扫描码、扩展键标记、上下文代码等标记。

判断组合键Alt+F4:VK_F4 == wParam && (1 == (lParam>>29 & 1))

如何让程序在按下某个特定的按键时退出???  例如  按下F2退出程序

为了让程序退出,可以利用函数SendMessage()向主程序发送WM_CLOSE消息。

定义一个全局变量 g_hWnd 来保存窗口句柄:

HWND g_hWnd = NULL;

保存窗口句柄:

g_hWnd = m_hWnd;

发送WM_CLOSE消息:

if(VK_F2 == wParam){::SendMessageA(g_hWnd, WM_CLOSE, 0, 0);::UnhookWindowsHookEx(g_hKeyboard);::UnhookWindowsHookEx(g_hMouse);}return 1;



全局钩子:
 

如果想要屏蔽当前正在运行的所有进程的鼠标消息和键盘消息,那么安装钩子过程的代码必须放到动态链接库中去实现。如果想要让安装的钩子过程与所有进程相关,应该将SetWindowsHookEx函数的第四个参数设置为0,并将它的第三个参数指定为安装钩子过程的代码所在的DLL的句柄。

首先,为了安装全局钩子,我们必须先生成一个.dll文件,于是,创建一个DLL工程:Hook, 用来生成Hook.dll

在Hook工程中的dllmain.cpp文件中完成钩子鼠标函数的定义和安装鼠标钩子:

//鼠标钩子函数LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam){return 1;}//安装鼠标钩子过程的函数void SetHook(){g_hMouse = ::SetWindowsHookEx(WH_MOUSE, MouseProc, ::GetModuleHandle("Hook"), 0);}


为了安装钩子过程,需要指定安装钩子过程所在DLL模块的句柄(SetWindowsHookEx的第三个参数),我们有两种方法来获得。

第一种:为DLL程序提供DllMain函数,当第一次加载DLL时,系统会调用这个函数,并传递当前DLL模块的句柄。因此,我们可以定义一个全局的实例变量g_hInst来保存系统传递来的DLL模块句柄,之后就可以在调用SetHook函数中使用这个句柄了。

第二种:调用GetModuleHandle函数来得到指定的DLL模块的句柄。GetModuleHandle函数返回值是HMODULE类型,HMODULE和HINSTANCE类型可以通用。

//第一种方法g_hMouse = ::SetWindowsHookExA(WH_MOUSE, MouseProc, g_hInst, 0);//第二种方法//g_hMouse = ::SetWindowsHookEx(WH_MOUSE, MouseProc, ::GetModuleHandle("Hook"), 0);


在Hook工程中要将SetHook函数声明为导出函数,并且要解决掉名字改变问题,所以我的SetHook函数如下:

extern "C" _declspec(dllexport) void SetHook(){//第一种方法g_hMouse = ::SetWindowsHookExA(WH_MOUSE, MouseProc, g_hInst, 0);//第二种方法//g_hMouse = ::SetWindowsHookEx(WH_MOUSE, MouseProc, ::GetModuleHandle("Hook"), 0);}


整个dllmain.cpp文件如下:

// dllmain.cpp : 定义 DLL 应用程序的入口点。#include "stdafx.h"#include <Windows.h>HHOOK g_hMouse = NULL;HINSTANCE g_hInst;BOOL APIENTRY DllMain( HMODULE hModule,                       DWORD  ul_reason_for_call,                       LPVOID lpReserved ){g_hInst = hModule;switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE;}//鼠标钩子函数LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam){return 1;}//安装鼠标钩子过程的函数extern "C" _declspec(dllexport) void SetHook(){//第一种方法g_hMouse = ::SetWindowsHookExA(WH_MOUSE, MouseProc, g_hInst, 0);//第二种方法//g_hMouse = ::SetWindowsHookEx(WH_MOUSE, MouseProc, ::GetModuleHandle("Hook"), 0);}

最后,我们就可以生成Hook.dll文件了。

 

接下来就是测试这个DLL文件,我们新建一个基于对话框的MFC工程。

1.在工程中要调用Hook.dll文件中的SetHook函数,所以应在调用前进行声明,并引入Hook.lib这个导入库文件:

#pragma comment (lib, "F:\\windows\\000MFC\\HookTest\\Debug\\Hook.lib")extern "C" _declspec(dllimport) void SetHook();


2.在需要的地方就可以直接调用SetHook函数了。例如在OnInitDialog函数中直接调用SetHook函数。

 

接下来,做一件有趣的事情,我们屏蔽掉所有键盘消息,但是除下F2键(我们要为程序开个后门,让程序退出),当按下F2键是程序退出,按下其他键不做任何处理。怎么实现呢???

为了退出程序,应该向调用进程的主窗口发送一个WM_CLOSE消息。但是在动态链接库中如何才能得到调用进程的主窗口句柄呢

很好解决,我们在调用DLL中的导出函数时,可以将主窗口的句柄作为参数传递给导出函数,我们在DLL中用一个全局变量将主窗口的句柄保存起来。这样问题就轻松解决了!

DLL工程中:

HWND g_hWnd;  //用来保存主窗口的句柄

SetHook函数接收主窗口句柄作为参数:

void SetHook(HWND hwnd){g_hWnd = hwnd;//第一种方法g_hMouse = ::SetWindowsHookExA(WH_MOUSE, MouseProc, g_hInst, 0);//第二种方法//g_hMouse = ::SetWindowsHookEx(WH_MOUSE, MouseProc, ::GetModuleHandle("Hook"), 0);g_hKeyboard = ::SetWindowsHookExA(WH_KEYBOARD, KeyboardProc, g_hInst, 0);}

HookTest工程中:

SetHook(m_hWnd);   将主窗口句柄传递给DLL中的函数SetHook。

注意:上面的例子中,当切换到其他进程的情况下,按下F2键并不能终止HookTest程序,这是为什么呢?

我们知道,DLL可以被多个进程供享DLL的代码和数据,但是如果多个进程共享同一份可写入的数据的话,你可以想象一下会有什么后果。为了解决这个问题,windows中采用了写入时复制机制。当DLL有一个数据被两个进程共享,如果第二个进程想修改DLL数据页面上的数据,操作系统会分配一个新的页面,并将数据页面上的数据复制一份到这个新的页面中。

 

那么,怎么才能使在切换到其他进程的情况下,仍然可以使HookTest程序退出

解决方法:为Hook.dll创建一个新的节,并将此节设置为一个共享的节,然后将全局变量:g_hWnd放到此节中,让该全局变量在多个进程间共享。

利用dumpbin -headers [.dll路径] 查看dll中各节的列表信息。

接下来我们为Hook.dll创建一个新的节:MySec,并将全局变量g_hWnd放到此节中。创建节可以用指令#pragma data_seg来实现。

#pragma data_seg("MySec")HWND g_hWnd = NULL;#pragma data_seg()#pragma comment(linker, "/section:MySec,RWS")


注意:g_hwnd一定要初始化,否则不会放到新的节中。新建的节的权限为read 和 write,我们要将它设置为共享的节,可以使用#pragma comment(linker, "/section:MySec,RWS")。 这里将指令类型指定为linker,表明该行代码用来指定连接选项。而字符串"/section:MySec,RWS"的含义是表明将MySec这个节设置为读(R)写(W)共享(S)类型。

为了将节设置为共享类型,我们还可以使用def文件:在DEF文件中利用SEGMENTS关键字来实现。

例如:

SEGMENTS

MySec READ WRITE SHARED

这里不区分大小写,但不能是缩写。

原创粉丝点击