Windows钩子的使用

咱们知道Windows中的窗口程序是基于消息,由事件驱动的,在某些状况下可能须要捕获或者修改消息,从而完成一些特殊的功能(MFC框架就利用Windows钩子对消息进行引导)。对于捕获消息而言,没法使用IAT或Inline Hook之类的方式去进行捕获,这就要用到接下来要介绍的Windows提供的专门用于处理消息的钩子函数。小程序

1. 挂钩原理

Windows下的应用程序大部分都是基于消息机制的,它们都会有一个消息过程函数,根据不一样的消息完成不一样的功能。Windows操做系统提供的钩子机制的做用就是用来截获和监视这些系统中的消息。Windows钩子琳琅满目,能够用来应对各类不一样的消息。框架

按照钩子做用的范围不一样,又能够分为局部钩子和全局钩子。局部钩子是针对某个线程的;而全局钩子则是做用于整个系统中基于消息的应用。全局钩子须要使用DLL文件,在DLL中实现相应的钩子函数。在操做系统中安装全局钩子后,只要进程接收到能够发出钩子的消息,全局钩子的DLL文件就会被操做系统自动或强行地加载到该进程中。所以,设置消息钩子,也能够达到DLL注入的目的。ide

2. 钩子函数

HHOOK SetWindowsHookEx(      

    int idHook,
    HOOKPROC lpfn,
    HINSTANCE hMod,
    DWORD dwThreadId
);

该函数的返回值是一个钩子句柄。参数介绍以下:函数

lpfn:指定Hook函数的地址。若是dwThreadId参数被赋值为0,或者被设置为一个其余进程中的线程ID,那么lpfn属于DLL中的函数过程。若是dwThreadId为当前进程中的一个线程ID,那么lpfn可使指向当前进程模块中的函数,固然,也可使DLL模块中的函数。spa

hMod:该参数指定钩子函数所在模块的模块句柄。即lpfn所在的模块句柄。若是dwThreadId为当前进程中的线程ID,且lpfn所指函数在当前进程中,则该参数被设置为NULL。操作系统

dwThreadId:指定须要被挂钩的线程ID号。若是设置为0,表示在全部基于消息的线程中挂钩;若是设置了具体的线程ID,表示在指定线程中挂钩。该参数影响上面两个参数的取值,同时也决定了该钩子是全局钩子仍是局部钩子。线程

idHook:该参数表示钩子的类型。经常使用的几种以下:debug

※  WH_GETMESSAGE调试

按照该钩子的做用是监视被投递到消息队列中的消息。也就是当调用GetMessage或PeekMessage函数时,函数从程序的消息队列中获取一个消息后调用该钩子。code

WH_GETMESSAGEG的钩子函数以下:

LRESULT CALLBACK GetMsgProc(      

    int code,		//hook code
    WPARAM wParam,		//removal option
    LPARAM lParam        //message
);

※  WH_MOUSE

该钩子用于监视鼠标消息。钩子函数以下:

LRESULT CALLBACK MouseProc(      

    int nCode,		//hook code
    WPARAM wParam,		//message identifier
    LPARAM lParam		//mouse coordinates
);

※  WH_KEYBOARD

该钩子用于监视键盘消息。钩子函数以下:

LRESULT CALLBACK KeyboardProc(      

    int code,		//hook code
    WPARAM wParam,		//virtual-key code
    LPARAM lParam		//keystroke-message information
);

※  WH_DEBUG

用于调试其它钩子。钩子函数以下:

LRESULT CALLBACK DebugProc(      

    int nCode,		//hook code
    WPARAM wParam,		//hook type
    LPARAM lParam		//debugging information
);

对于以上钩子函数的详情还请各位看客老爷们自行挪步到MSDN了。

移除先前用SetWindowsHookEx安装的钩子:

BOOL UnhookWindowsHookEx(      

    HHOOK hhk
);

惟一的参数是待移除的钩子句柄。

在实际应用中,能够屡次调用SetWindowsHookEx函数来安装钩子,并且能够安装多个一样类型的钩子。这样,钩子就会造成一条钩子链,最后安装的钩子会首先截获到消息。当该钩子对消息处理完毕后,能够选择返回或者把消息继续传递下去。若是是为了屏蔽某消息,能够在安装的钩子函数中直接返回非零值。若是但愿咱们的钩子函数处理完消息后能够继续传递给目标窗口,则必须选择将消息继续传递。继续传递消息的函数定义以下:

LRESULT CallNextHookEx(      

    HHOOK hhk,		//handle to current hook
    int nCode,		//hook code passed to hook procedure
    WPARAM wParam,		//value passed to hook procedure
    LPARAM lParam		//value passed to hook procedure
);

第一个参数是钩子句柄,就是调用SetWindowsHookEx函数的返回值;后面3个参数是钩子的参数,直接一次copy便可。例如:

HHOOK g_Hook = SetWindowsHookEx(…);
LRESULT CALLBACK GetMsgProc(      

    int code,		//hook code
    WPARAM wParam,		//removal option
    LPARAM lParam        //message
)
{
	return CallNextHookEx(g_Hook, code, wParam, lParam);
}


3. 钩子实例

Windows钩子的使用场景比较普遍,咱们就几种比较常见的状况作一个应用示例。

3.1全局键盘钩子

先新建一个DLL程序(这个不会能够看我之前的博客,这里就不重复了),咱们在头文件中增长两个导出函数和两个全局。

#define MY_API __declspec(dllexport)
extern "C" MY_API VOID SetHookOn();
extern "C" MY_API VOID SetHookOff();
HHOOK g_Hook = NULL;		//钩子句柄
HINSTANCE g_Inst = NULL;	//DLL模块句柄

在DllMain中保存该DLL模块的句柄,以方便安装全局钩子。

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{

	//保存DLL模块句柄
	g_Inst = (HINSTANCE)hModule;
      return TRUE;
}

安装与卸载钩子的函数以下:

VOID SetHookOn()
{
	//安装钩子
	g_Hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_Inst, 0);
}

VOID SetHookOff()
{
	//卸载钩子
	UnhookWindowsHookEx(g_Hook);
}

钩子函数的实现以下:

//钩子函数
LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam)
{
	if(code < 0)
	{
		//若是code小于0,必须调用CallNextHookEx传递消息,不处理该消息,并返回CallNextHookEx的返回值。
		return CallNextHookEx(g_Hook, code, wParam, lParam);
	}

	if(code == HC_ACTION && lParam > 0)
	{
		//code等于HC_ACTION,表示消息中包含按键消息
		//若是为WM_KEYDOWN,则显示按键对应的文本
		char szBuf[MAXBYTE] = {0};
		GetKeyNameText(lParam, szBuf, MAXBYTE);
		MessageBox(NULL, szBuf, "提示", MB_OK);
	}

	return CallNextHookEx(g_Hook, code, wParam, lParam);

}

编译连接后产生咱们须要的.dll和.lib文件,而后新建一个项目来导入动态库内容调用相关函数。

新建项目以下:



首先导入库:

#pragma comment (lib, "全局钩子.lib")

声明将要调用的函数(不声明连接时将报错):

extern "C" VOID SetHookOn();
extern "C" VOID SetHookOff();

在按钮事件中调用导出函数:

void CHookDebugDlg::OnHookon() 
{
	SetHookOn();
}

void CHookDebugDlg::OnHookoff() 
{
	SetHookOff();
}

执行结果以下:


3.2低级键盘钩子

数据防泄漏软件一般会精致PrintScreen键,防止经过截屏将数据保存为图片而致使数据泄密。下面咱们也能够模仿一下,简单的实现该功能。这里须要注意的是,普通的键盘钩子(WH_KEYBOARD)是没法过滤一些系统按键的,得经过安装低级键盘钩子(WH_KEYBOARD_LL)来达到目的。

在低级键盘钩子的回调函数中,判断是否为PrintScreen键,若是是,则直接返回TRUE;若是不是,则传递给下一个钩子处理。

具体DLL中的实现代码以下:

BOOL SetHookOn()
{
	if(g_Hook != NULL)
	{
		return FALSE;
	}
	//安装钩子
	g_Hook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, g_Inst, 0);
	if(NULL == g_Hook)
	{
		MessageBox(NULL, "安装钩子出错!", "ERROR", MB_ICONSTOP);
		return FALSE;
	}

	return TRUE;
}

BOOL SetHookOff()
{
	if(g_Hook == NULL)
	{
		return FALSE;
	}
	//卸载钩子
	UnhookWindowsHookEx(g_Hook);
	g_Hook = NULL;
	return TRUE;
}

//钩子函数
LRESULT CALLBACK LowLevelKeyboardProc(int code, WPARAM wParam, LPARAM lParam)
{
	KBDLLHOOKSTRUCT *Key_Info = (KBDLLHOOKSTRUCT *)lParam;

	if(HC_ACTION == code)
	{
		if(WM_KEYDOWN == wParam || WM_SYSKEYDOWN == wParam)
		{
			if(Key_Info->vkCode == VK_SNAPSHOT)
			{
				MessageBox(NULL, "该键已禁用!", "ERROR", MB_ICONSTOP);
				return TRUE;
			}
		}
	}

	return CallNextHookEx(g_Hook, code, wParam, lParam);

}

依然利用前面的小程序,执行后按下PrintScreen键,效果以下:




可能在编译时会报错,说WH_KEYBOARD_LL和KBDLLHOOKSTRUCT未定义,此时能够在文件开头加上以下代码:

#define WH_KEYBOARD_LL 13

typedef struct tagKBDLLHOOKSTRUCT {

DWORD vkCode;

DWORD scanCode;

DWORD flags;

DWORD time;

DWORD dwExtraInfo;

} KBDLLHOOKSTRUCT, FAR *LPKBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;

其实在winuser.h中已有定义,可是多是兼容的缘故用不了。

3.3钩子注入DLL

利用WH_GETMESSAGE钩子,能够方便地将DLL文件注入到全部基于消息机制的程序中。由于有时候可能须要DLL文件完成一些工做,可是工做时须要DLL在目标进程的空间中。这个时候,就能够将DLL注入目标进程来完成相关的功能。

主要的代码以下:

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
	//保存DLL模块句柄
	g_Inst = (HINSTANCE)hModule;

    switch (ul_reason_for_call)
	{
		case DLL_PROCESS_ATTACH:
			{
				DoSomething();
				break;
			}
		case DLL_THREAD_ATTACH:
		case DLL_THREAD_DETACH:
		case DLL_PROCESS_DETACH:
			break;
    }
    return TRUE;
}
VOID SetHookOn()
{
	g_Hook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, g_Inst, 0);
}

VOID SetHookOff()
{
	UnhookWindowsHookEx(g_Hook);
}

LRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
{
	return CallNextHookEx(g_Hook, code, wParam, lParam);
}

VOID DoSomething()
{
	MessageBox(NULL, "Hello,我被执行了!", "提示", MB_OK);
}

执行效果图:


 须要注意的是,此处执行的DoSomething并非导出函数哦。