Windows核心编程_Hook

一、前言

HookWindows下的一个机制,Hook的中文意思是钩子的意思,顾名思义,钩子就是用来钩东西的,就好像钓鱼一样,你把鱼钩放入鱼塘里,钓到了某条鱼,即便我们不把鱼钓上来,我们可以通过鱼钩知道鱼在做什么,比如鱼飞速游动,鱼钩上的鱼线会做出反应,或者鱼原地不动,我们都可以通过鱼钩知道鱼在做什么!

Windows就像一个鱼塘,而程序,就是鱼塘里的鱼,而用来监视这些鱼的鱼钩就是Hook

众所周知,Windows平台上的程序是以事件驱动和消息为基础工作的,事件与消息是关联的,消息的触发来响应事件,比如我单击了一个应用程序上的按钮,那么此时这个应用程序会触发一个消息即为MK_LBUTTON消息被发送到系统的消息队列里(触发过程是由操作系统根据鼠标点击某个窗口上某个控件来触发的,原理上是由操作系统触发的消息),这些消息都是以数据结构形式存储的,每个队列里不仅存储消息还有触发的窗口句柄等参数信息,Windows会把这些消息在转发给指定窗口进程下的消息队列,然后由消息队列来处理或者由操作系统来默认处理,学过Win32编程和MFC编程的应该比较熟悉,这里的消息流程只是仅限于Windows提供的WindowsSDK库下的API接口开发的程序!


就好像控件一样,控件其实就相当于一个窗口,只是一个需要容器的窗口,也有自己的消息循环,学过COM组件开发的都知道基于COM组件开发ActiveX控件里也有一套消息循环机制和对应的响应事件,比如按钮的获取焦点对应的绘图函数!


其它框架不一定使用此方法,比如QT的信号和槽,但QT内部封装了Windows消息和事件驱动模型,原理上来说,你单击QT上的一个按钮还是会产生MK_LBUTTON消息并返回给对应的QT程序,还是会返回到程序的消息循环队列里去,只是QT用了信号和槽的方式来代替消息与事件,比如你用第三方软件给QT程序上的某个按钮发送MK_LBUTTON消息还是会响应对应的事件,因为QT上的控件都是基于Windows下的SDK接口来实现完成的,QT的核心还是:触发消息>系统消息>应用消息,和Windows一样,只是内部封装起来了!


. 钩子运行机制

1.1 钩子链表与钩子

钩子是Windows平台下的一个用于截获消息的程序段,Windows内部也在用它,当我们使用特定函数来安装一个钩子时,操作系统会给这个钩子分配一个钩子链表,并且这个钩子就相当于一小段程序,称为钩子子程序,这段程序会根据钩子类型的不同,来实现不同程度的消息截获,并且这个钩子链表里包含了这个钩子程序的地址,类型,回调函数的地址!

并且钩子子程序的优先级会高于应用程序,在接受消息时会被钩子子程序先行截获,操作系统会先把消息发送给钩子,由钩子决定这些消息是否发送下去,钩子可以拦截这些消息,可以决定这些消息的作用,甚至可以屏蔽这些消息不让传递到指定应用程序当中!

上面说过了每个程序上的钩子是由一个钩子链表来维护的,多个钩子那么链表上就会有多个子节点,当操作系统把消息传递给钩子时,会首先传递给链表首节点的钩子子程序,首节点的钩子可以决定这个消息是否传递给下一个钩子!

在安装钩子的时候是由顺序之分的,链表遵循的是先进后出,也就是说钩子链表的首节点始终是最后一个安装钩子的钩子子程序,并且每个进程下只能有一个钩子,如果重复安装钩子会安装失败!


1.2 钩子类型

1.1 全局钩子

全局钩子即用于钩系统消息队列里的消息,上面说过,所有程序触发的消息都会被操作系统发送到消息队列里(这样做的原因是根据鼠标点击屏幕像素点区域来确定点击的是哪个窗口,窗口上的哪个控件),在由操作系统将消息队列里的消息发送给指定窗口下的消息队列,全局钩子的作用就是钩系统级的消息队列,当操作系统将消息队列里的消息发送出去时会率先发送给全局钩子,并且由全局钩子截获,全局钩子决定这些消息的存亡,钩子可以决定处理或不处理,也可以决定处理完之后再发送给应用程序或者不处理直接发送给应用程序,不过这样做的话钩子的意义就不大了!


1.2 局部钩子

局部钩子即只钩进程下的消息,当操作系统在将消息发送给各个程序时,操作系统不会把所有消息都发送给此钩子,而是只把当前进程下产生的消息在发送给进程下的消息队列时先发送给钩子子程序,而不是直接发给消息队列,由钩子子程序决定这些消息的处理方式!


  1.3 HOOK应用模式

观察模式:

最常用的应用模式,即简单创建一个Hook子程序,用于观察某些进程下的消息,并对其进行截获处理!


        注入模式:

即通过Hook子程序将DLL动态库注入到某个进程下,使其成为进程的一部分!


        替换模式(注入模式的一种)

利用Hook子程序将动态库注入到某个进程下,并拦截某个进程调用函数过程,将调用函数替换成自己DLL动态库函数!(黑客常用)


         插件模式(注入模式的一种)

将动态库函数注入到指定进程下,并协调调用动态库函数,扩展程序业务!


 修复模式(替换模式的一种)

利用Hook技术将某个消息对应的函数替换成新的执行函数!


其还有其他应用模式,这一般取决于用户怎么使用Hook,用它做些什么!


1.4 Hook的运行机制-

上面说过Hook的工作机制,Hook的子程序的生存周期是由用户而决定的,当我们安装Hook子程序之后,就意味着操作系统要建立一张钩子链表来维护它,并且每次传递消息都要率先传递给钩子子程序,如果钩子子程序没有做任何处理则再由钩子子程序发送给对应的进程,那么这样来来回回,就需要浪费了很多时间,耗费系统资源,所以当在使用完钩子之后建议立马卸载,避免影响系统运行效率,并且如果你安装了钩子,但是在程序结束时钩子都没有被卸载,那么操作系统会帮你卸载这些钩子!


其钩子链表,这里要说一下,倘若多个进程多个钩子,其这些钩子链表是由操作系统来维护的,操作系统会生成一张系统用的钩子链表,这个链表负责存储每个钩子的相关信息!


Hook的缺点非常明显,那就是当某个程序没有触发任何窗体消息时那么Hook永远不会被执行,其Windows下用于绘制窗口和处理事件驱动的模块是user32.dll,也就是说当一个窗口被创建和事件驱动等消息机制被创建出来时就会加载user32动态库,调用里面的API来完成,倘若某个进程在进行一些复杂的计算公式,不去调用user32那么Hook(仅限局部,因为不是所有的进程都会这样)将永远不会被执行!


. 实践

到了这一章相比各位的理论知识已经很足了,那么就可以开始进行实践了,毕竟实践来自于理论!

开始之前先介绍几个所需AIP函数:

1.SetWindowsHookEx

函数原型:

WINUSERAPI HHOOK WINAPI SetWindowsHookExW(    _In_ int idHook,    _In_ HOOKPROC lpfn,    _In_opt_ HINSTANCE hmod,    _In_ DWORD dwThreadId);
参数介绍:
_In_ int idHook:	钩子类型,可取以下值:
WH_MSGFILTER    = -1; 线程级; 截获用户与控件交互的消息
WH_JOURNALRECORD  = 0; 系统级; 记录所有消息队列从消息队列送出的输入消息, 在消息从队列中清除时发生; 可用于宏记录
WH_JOURNALPLAYBACK = 1; 系统级; 回放由 WH_JOURNALRECORD 记录的消息, 也就是将这些消息重新送入消息队列
WH_KEYBOARD    = 2; 系统级或线程级; 截获键盘消息
WH_GETMESSAGE   = 3; 系统级或线程级; 截获从消息队列送出的消息
WH_CALLWNDPROC   = 4; 系统级或线程级; 截获发送到目标窗口的消息, 在 SendMessage 调用时发生
WH_CBT       = 5; 系统级或线程级; 截获系统基本消息, 譬如: 窗口的创建、激活、关闭、最大最小化、移动等等有用的窗体控件消息
WH_SYSMSGFILTER  = 6; 系统级; 截获系统范围内用户与控件交互的消息
WH_MOUSE      = 7; 系统级或线程级; 截获鼠标消息
WH_HARDWARE    = 8; 系统级或线程级; 截获非标准硬件(非鼠标、键盘)的消息
WH_DEBUG      = 9; 系统级或线程级; 在其他钩子调用前调用, 用于调试钩子
WH_SHELL      = 10; 系统级或线程级; 截获发向外壳应用程序的消息
WH_FOREGROUNDIDLE = 11; 系统级或线程级; 在程序前台线程空闲时调用
WH_CALLWNDPROCRET = 12; 系统级或线程级; 截获目标窗口处理完毕的消息, 在 SendMessage 调用后发生

__in HOOKPROC lpfn:回调函数地址
其回调函数原型为:
LRESULT CALLBACK name(int nCode, WPARAM wParam, LPARAM lParam)
name可以随便起,这里来解释一下回调函数的参数:
int nCode:消息代码
 WPARAM wParam:附加参数一
LPARAM lParam:附加参数二
返回值:LRESULT类型(便于传递hook,后面详细介绍)
__in HINSTANCE hMod:DLL实列句柄
__in DWORD dwThreadId:要挂钩的线程ID
函数作用:设置钩子

2.CallNextHookEx

函数原型:

LRESULT
WINAPI
CallNextHookEx(
    _In_opt_ HHOOK hhk,
    _In_ int nCode,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam);
参数介绍:
 _In_opt_ HHOOK hhk:下一个钩子子程的句柄
_In_ int nCode: 钩子消息代码,此消息代码会被传递给下一个钩子处理
 _In_ WPARAM wParam:消息附加参数一
 _In_ LPARAM lParam:消息附加参数二

函数作用:将消息传递给下一个钩子子程序,比如你有多个钩子,当你回调函数处理完之后,可以使用此函数将消息传递给下一个钩子子程处理,也可以选择不传递,在windows里最新钩子子程序每次会第一个获取到此消息,上面说过钩子链表这里就不多说了,同时此函数也可以用来屏蔽消息,后面详细说明!

返回值:LRESULT类型,返回值是hook钩子类型代码

3.UnhookWindowsHookEx

函数原型:


BOOL WINAPI UnhookWindowsHookEx( __in HHOOK hhk);
参数介绍:
__in HHOOK hhk:要删除的hook钩子句柄
返回值:成功true否则false

函数作用:释放钩子

下面开始几个列子讲解此函数应该怎样使用:


1.截获当前线程键盘消息(局部钩子)

这里我们先新建一个win32程序


#include "stdafx.h"
#include <windows.h>

HWND hWnd;	//progman

//消息函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	//判断消息ID
	switch (uMsg){
	
	case WM_DESTROY:    // 窗口销毁消息
		PostQuitMessage(0);   //  发送退出消息
		return 0;
	}
	// 其他的消息调用缺省的消息处理程序
	return DefWindowProc(hwnd, uMsg, wParam, lParam);

}
// 3、注册窗口类型
BOOL RegisterWindow(LPCSTR lpcWndName, HINSTANCE hInstance)
{
ATOM nAtom = 0;
	// 构造创建窗口参数
	WNDCLASS wndClass = { 0 };
	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WindowProc;      // 指向窗口过程函数
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hIcon = NULL;
	wndClass.hCursor = NULL;
	wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = lpcWndName;    // 注册的窗口名称,并非标题,以后创建窗口根据此注册的名称创建
	nAtom = RegisterClass(&wndClass);
	return TRUE;
}
//创建窗口(lpClassName 一定是已经注册过的窗口类型)
HWND CreateMyWindow(LPCTSTR lpClassName, HINSTANCE hInstance)
{
	HWND hWnd = NULL;
	// 创建窗口
	hWnd = CreateWindow(lpClassName, "test", WS_OVERLAPPEDWINDOW^WS_THICKFRAME, 0, 0, 1000, 800, NULL, NULL, hInstance, NULL);
	return hWnd;
}
//显示窗口
void DisplayMyWnd(HWND hWnd)
{
	//获得屏幕尺寸

	int scrWidth = GetSystemMetrics(SM_CXSCREEN);
	int scrHeight = GetSystemMetrics(SM_CYSCREEN);
	RECT rect;
	GetWindowRect(hWnd, &rect);
	ShowWindow(hWnd, SW_SHOW);
	//重新设置rect里的值
	rect.left = (scrWidth - rect.right) / 2;
	rect.top = (scrHeight - rect.bottom) / 2;
	//移动窗口到指定的位置
	SetWindowPos(hWnd, HWND_TOP, rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW);
	UpdateWindow(hWnd);
}

void doMessage()        // 消息循环处理函数
{
	MSG msg = { 0 };
	// 获取消息
	while (GetMessage(&msg, NULL, 0, 0)) // 当接收到WM_QIUT消息时,GetMessage函数返回0,结束循环
	{
		DispatchMessage(&msg); // 派发消息,到WindowPro函数处理
	}
}

// 入口函数
int WINAPI WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpCmdLine,
	int nShowCmd)
{
	HWND hWnd = NULL;
	LPCTSTR lpClassName = "MyWnd";  // 注册窗口的名称
	RegisterWindow(lpClassName, hInstance);
	hWnd = CreateMyWindow(lpClassName, hInstance);
	DisplayMyWnd(hWnd);
	doMessage();
	return 0;
}

win32窗口创建代码基本上写完了,我们先定义一个全局变量hook方便回调函数传递hook


//hook
HHOOK hook;
在定义一个回调函数:
//hook回调函数
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	//拦截所有的键盘消息
	MessageBox(NULL, "dd", "键盘消息被触发了", 0);
	return CallNextHookEx(hook, nCode, wParam, lParam);
}

这里来说一下为什么回调函数的类型是LRESULT,通过上面的代码可以发现CallNextHookEx函数返回值是LRESULT类型的,所以想要兼容此函数返回值也必须是LRESULT类型的,其返回值是钩子类型的代码,这个我们无需关系!

注册hook

hook = SetWindowsHookEx(WH_KEYBOARD, LowLevelMouseProc, NULL, GetCurrentThreadId());	//GetCurrentThreadId()API函数可以调用线程的线程ID

WH_KEYBOARD只拦截键盘消息

运行效果:

这个时候我们随便按下一个按键:



成功拦截到此消息了,这里我们在把回调函数里的代码稍微更改一下,只对空格键有效:


//拦截空格消息
	if (wParam == VK_SPACE)
		MessageBox(NULL, "空格被按下了, "键盘消息被触发了", 0);
	return CallNextHookEx(hook, nCode, wParam, lParam);
//VK_SPACE是空格的键代码,这里我们没有使用键代码到字符消息之间的映射转换,所以我们要直接使用键代码来比对,键盘消息的附加键代码存放在wParam参数里

运行结果:



无论我们按下什么按键都没有反应,这里我们按下空格试试:






完整代码:


#include "stdafx.h"
#include <windows.h>

HWND hWnd;	//progman

//消息函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	//判断消息ID
	switch (uMsg){
	
	case WM_DESTROY:    // 窗口销毁消息
		PostQuitMessage(0);   //  发送退出消息
		return 0;
	}
	// 其他的消息调用缺省的消息处理程序
	return DefWindowProc(hwnd, uMsg, wParam, lParam);

}
// 3、注册窗口类型
BOOL RegisterWindow(LPCSTR lpcWndName, HINSTANCE hInstance)
{
	ATOM nAtom = 0;
	// 构造创建窗口参数
	WNDCLASS wndClass = { 0 };
	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WindowProc;      // 指向窗口过程函数
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hIcon = NULL;
	wndClass.hCursor = NULL;
	wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = lpcWndName;    // 注册的窗口名称,并非标题,以后创建窗口根据此注册的名称创建
	nAtom = RegisterClass(&wndClass);
	return TRUE;
}
//创建窗口(lpClassName 一定是已经注册过的窗口类型)
HWND CreateMyWindow(LPCTSTR lpClassName, HINSTANCE hInstance)
{
	HWND hWnd = NULL;
	// 创建窗口
	hWnd = CreateWindow(lpClassName, "test", WS_OVERLAPPEDWINDOW^WS_THICKFRAME, 0, 0, 1000, 800, NULL, NULL, hInstance, NULL);
	return hWnd;
}
//显示窗口
void DisplayMyWnd(HWND hWnd)
{
	//获得屏幕尺寸

	int scrWidth = GetSystemMetrics(SM_CXSCREEN);
	int scrHeight = GetSystemMetrics(SM_CYSCREEN);
	RECT rect;
	GetWindowRect(hWnd, &rect);
	ShowWindow(hWnd, SW_SHOW);
	//重新设置rect里的值
	rect.left = (scrWidth - rect.right) / 2;
	rect.top = (scrHeight - rect.bottom) / 2;
	//移动窗口到指定的位置
	SetWindowPos(hWnd, HWND_TOP, rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW);
	UpdateWindow(hWnd);
}

void doMessage()        // 消息循环处理函数
{
	MSG msg = { 0 };
	// 获取消息
	while (GetMessage(&msg, NULL, 0, 0)) // 当接收到WM_QIUT消息时,GetMessage函数返回0,结束循环
	{
		DispatchMessage(&msg); // 派发消息,到WindowPro函数处理
	}
}
//hook
HHOOK hook;
//hook回调函数
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	//拦截空格消息
	if (wParam == VK_SPACE)
		MessageBox(NULL, "空格被按下了", "键盘消息被触发了", 0);
	return CallNextHookEx(hook, nCode, wParam, lParam);
}
// 入口函数
int WINAPI WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpCmdLine,
	int nShowCmd)
{
	HWND hWnd = NULL;
	LPCTSTR lpClassName = "MyWnd";  // 注册窗口的名称
	RegisterWindow(lpClassName, hInstance);
	hWnd = CreateMyWindow(lpClassName, hInstance);
	//注册hook
	hook = SetWindowsHookEx(WH_KEYBOARD, LowLevelMouseProc, NULL, GetCurrentThreadId());
	DisplayMyWnd(hWnd);
	doMessage();
	return 0;
}

上面说过CallNextHookEx函数可以屏蔽消息,不让发送到目标窗口,这里来仔细解释一下:

当钩子被触发时调入到回调函数时,上面也说过,钩子回调函数会在消息到达目标窗口之前获取到此消息,当我们处理完之后,到return时,如果不调用CallNextHookEx函数,直接返回return 0;那么此消息将永远不会被发送给目标窗口,这里来解释一下:

CallNextHookEx的第一个参数是要传递的hook句柄,倘若没有hook了,那么此句柄一定是当前hook子程的句柄,如果是当前的而非下一个,此函数就会认为hook句柄无效,直接将消息再次传递给目标窗口,其整个过程是由内核态来完成的,当前句柄是否有效可以通过钩子链表来确定当前是哪个hook在执行!

下面我们修改一下代码,屏蔽指定窗口所有消息:


//屏蔽所有消息:
	return 0;//无需关心类型,LRESULT其定义是long类型
完整代码:
#include "stdafx.h"
#include <windows.h>

HWND hWnd;	//progman

//消息函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	//判断消息ID
	switch (uMsg){
	
	case WM_DESTROY:    // 窗口销毁消息
		PostQuitMessage(0);   //  发送退出消息
		return 0;
	}
	// 其他的消息调用缺省的消息处理程序
	return DefWindowProc(hwnd, uMsg, wParam, lParam);

}
// 3、注册窗口类型
BOOL RegisterWindow(LPCSTR lpcWndName, HINSTANCE hInstance)
{
	ATOM nAtom = 0;
	// 构造创建窗口参数
	WNDCLASS wndClass = { 0 };
	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WindowProc;      // 指向窗口过程函数
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hIcon = NULL;
	wndClass.hCursor = NULL;
	wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = lpcWndName;    // 注册的窗口名称,并非标题,以后创建窗口根据此注册的名称创建
	nAtom = RegisterClass(&wndClass);
	return TRUE;
}
//创建窗口(lpClassName 一定是已经注册过的窗口类型)
HWND CreateMyWindow(LPCTSTR lpClassName, HINSTANCE hInstance)
{
	HWND hWnd = NULL;
	// 创建窗口
	hWnd = CreateWindow(lpClassName, "test", WS_OVERLAPPEDWINDOW^WS_THICKFRAME, 0, 0, 1000, 800, NULL, NULL, hInstance, NULL);
	return hWnd;
}
//显示窗口
void DisplayMyWnd(HWND hWnd)
{
	//获得屏幕尺寸

	int scrWidth = GetSystemMetrics(SM_CXSCREEN);
	int scrHeight = GetSystemMetrics(SM_CYSCREEN);
	RECT rect;
	GetWindowRect(hWnd, &rect);
	ShowWindow(hWnd, SW_SHOW);
	//重新设置rect里的值
	rect.left = (scrWidth - rect.right) / 2;
	rect.top = (scrHeight - rect.bottom) / 2;
	//移动窗口到指定的位置
	SetWindowPos(hWnd, HWND_TOP, rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW);
	UpdateWindow(hWnd);
}

void doMessage()        // 消息循环处理函数
{
	MSG msg = { 0 };
	// 获取消息
	while (GetMessage(&msg, NULL, 0, 0)) // 当接收到WM_QIUT消息时,GetMessage函数返回0,结束循环
	{
		DispatchMessage(&msg); // 派发消息,到WindowPro函数处理
	}
}
//hook
HHOOK hook;
//hook回调函数
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	//屏蔽所有消息:
	return 0;
}
// 入口函数
int WINAPI WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpCmdLine,
	int nShowCmd)
{
	HWND hWnd = NULL;
	LPCTSTR lpClassName = "MyWnd";  // 注册窗口的名称
	RegisterWindow(lpClassName, hInstance);
	hWnd = CreateMyWindow(lpClassName, hInstance);
	//注册hook
	hook = SetWindowsHookEx(WH_KEYBOARD, LowLevelMouseProc, NULL, GetCurrentThreadId());
	DisplayMyWnd(hWnd);
	doMessage();
	return 0;
}

最后别忘记在不用的时候使用UnhookWindowsHookEx将其释放掉

局部钩子说完了,那么该说说全局的了,全局即无论任何窗口按下此消息都有反应,全局钩子必须的回调函数必须是一个DLL,因为虚拟内存的原因,详细可以参考博主关于对虚拟内存和操作系统内核下虚拟保护的相关文章!

使用dll可以将代码注入到指定进程下,这样就可以自由调用了:

这里我们写一个dll动态库:


头文件:

#pragma once
#include <windows.h>
extern "C" _declspec(dllexport) LRESULT LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam);
extern "C" _declspec(dllexport) void setHook(HHOOK hook);


dll文件:

#include "hookdll.h"
#pragma data_seg("MyData")
HHOOK hook_ = 0;//共享数据段
#pragma data_seg()
LRESULT  LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam){
	//拦截所有的键盘消息
	MessageBox(NULL, L"dd", L"键盘消息被触发了", 0);
	return CallNextHookEx(hook_, nCode, wParam, lParam);
}
void setHook(HHOOK hook){
	hook_ = hook;
}

然后我们要将dll文件拷贝到我们的工程目录下,并编写导入代码:

//dll
	HMODULE dll = LoadLibrary("ConsoleApplication3.dll");
	if (dll == NULL) {
		DWORD err = GetLastError();
		return -1;
	}
	HOOKPROC addr = (HOOKPROC)GetProcAddress(dll, "LowLevelMouseProc");
	typedef void(*FunPtr)(HHOOK hook);//定义函数指针来指向Dll动态库里的函数  
	FunPtr funPtr = (FunPtr)GetProcAddress(dll, "setHook");	//set hook func
	//注册hook
	hook = SetWindowsHookEx(WH_KEYBOARD, addr, dll,0);
	funPtr(hook);	//set hook

注意SetWindowsHookEx最后一个参数更改为0,也就是全局的,无论任何窗口触发键盘消息,那么此dll里的回调函数都会被装载进指定窗口进程下,并被执行!
运行结果:
我们在vs编辑界面下按下一个按键试试:


成功让此dll加载进系统队列中

完整代码:

#include "stdafx.h"
#include <windows.h>

HWND hWnd;	//progman

//消息函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	//判断消息ID
	switch (uMsg){
	
	case WM_DESTROY:    // 窗口销毁消息
		PostQuitMessage(0);   //  发送退出消息
		return 0;
	}
	// 其他的消息调用缺省的消息处理程序
	return DefWindowProc(hwnd, uMsg, wParam, lParam);

}
// 3、注册窗口类型
BOOL RegisterWindow(LPCSTR lpcWndName, HINSTANCE hInstance)
{
	ATOM nAtom = 0;
	// 构造创建窗口参数
	WNDCLASS wndClass = { 0 };
	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WindowProc;      // 指向窗口过程函数
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hIcon = NULL;
	wndClass.hCursor = NULL;
	wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = lpcWndName;    // 注册的窗口名称,并非标题,以后创建窗口根据此注册的名称创建
	nAtom = RegisterClass(&wndClass);
	return TRUE;
}
//创建窗口(lpClassName 一定是已经注册过的窗口类型)
HWND CreateMyWindow(LPCTSTR lpClassName, HINSTANCE hInstance)
{
	HWND hWnd = NULL;
	// 创建窗口
	hWnd = CreateWindow(lpClassName, "test", WS_OVERLAPPEDWINDOW^WS_THICKFRAME, 0, 0, 1000, 800, NULL, NULL, hInstance, NULL);
	return hWnd;
}
//显示窗口
void DisplayMyWnd(HWND hWnd)
{
	//获得屏幕尺寸

	int scrWidth = GetSystemMetrics(SM_CXSCREEN);
	int scrHeight = GetSystemMetrics(SM_CYSCREEN);
	RECT rect;
	GetWindowRect(hWnd, &rect);
	ShowWindow(hWnd, SW_SHOW);
	//重新设置rect里的值
	rect.left = (scrWidth - rect.right) / 2;
	rect.top = (scrHeight - rect.bottom) / 2;
	//移动窗口到指定的位置
	SetWindowPos(hWnd, HWND_TOP, rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW);
	UpdateWindow(hWnd);
}

void doMessage()        // 消息循环处理函数
{
	MSG msg = { 0 };
	// 获取消息
	while (GetMessage(&msg, NULL, 0, 0)) // 当接收到WM_QIUT消息时,GetMessage函数返回0,结束循环
	{
		DispatchMessage(&msg); // 派发消息,到WindowPro函数处理
	}
}
//hook
HHOOK hook;

// 入口函数
int WINAPI WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpCmdLine,
	int nShowCmd)
{
	HWND hWnd = NULL;
	LPCTSTR lpClassName = "MyWnd";  // 注册窗口的名称
	RegisterWindow(lpClassName, hInstance);
	hWnd = CreateMyWindow(lpClassName, hInstance);
	//dll
	HMODULE dll = LoadLibrary("ConsoleApplication3.dll");
	if (dll == NULL) {
		DWORD err = GetLastError();
		return -1;
	}
	HOOKPROC addr = (HOOKPROC)GetProcAddress(dll, "LowLevelMouseProc");
	typedef void(*FunPtr)(HHOOK hook);//定义函数指针来指向Dll动态库里的函数  
	FunPtr funPtr = (FunPtr)GetProcAddress(dll, "setHook");	//set hook func
	//注册hook
	hook = SetWindowsHookEx(WH_KEYBOARD, addr, dll,0);
	funPtr(hook);	//set hook  共享hook句柄
	DisplayMyWnd(hWnd);
	doMessage();
	return 0;
}

dll:
.h
#pragma once
#include <windows.h>
extern "C" _declspec(dllexport) LRESULT LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam);
extern "C" _declspec(dllexport) void setHook(HHOOK hook);
.cpp
#include "hookdll.h"
#pragma data_seg("MyData")
HHOOK hook_ = 0;//共享数据段
#pragma data_seg()
LRESULT  LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam){
	//拦截所有的键盘消息
	MessageBox(NULL, L"dd", L"键盘消息被触发了", 0);
	return CallNextHookEx(hook_, nCode, wParam, lParam);
}
void setHook(HHOOK hook){
	hook_ = hook;
}

注意这里回调函数声明没有包含CALLBACK修饰符了,因为在.h文件里已经声明此函数的入栈方式,所以重复声明会被编译器视为无效,那么在加载时就会出现找不到指定dll函数,CALLBACK是入栈方式修饰符,详细可以查看博主对入栈方式方面介绍的博客!

那么你也可以通过此方法来入侵其他程序!把SetWindowsHookEx最后一个参数改成指定窗口进程下的线程id即可!

那么下面来做一个个小示列:

这里博主已经事先写好一个简单的Windows窗口了:


那么我们通过FindWindow获取窗口句柄,在通过句柄获取窗口执行线程,在通过设置Hook来监视消息!



HWND hwnd = FindWindow(NULL, "1");
DWORD ProcessID;	//进程ID
DWORD ThreadID;	//线程ID
ThreadID = GetWindowThreadProcessId(hwnd, &ProcessID);	//获取窗口的进程ID和线程ID
hook = SetWindowsHookEx(WH_KEYBOARD, addr, dll, ThreadID);

运行效果:


可以看到成功触发消息!

注意如果你想要拦截某个控件的消息,上面说过了,控件也是一个独立的线程,有自己的消息队列,并且也是一个窗口,只是一个需要容器的窗口,所以可以通过FindWindowEx来获取子窗口找到指定控件,并拦截!

这里在补充一点,经过博主的测试:比如你向一个edit控件输入字符,那么容器窗口也会获取到键盘消息,edit也会获取到此消息,这两个线程的消息队列都会获取到此消息,因为你在容器窗口下对任何控件进行操作,容器窗口都会获取到此消息,控件线程消息队列也会获取到此消息。并且双方是互不干扰的,你拦截容器窗口的这个消息屏蔽,控件还是一样能收到,所以我们要屏蔽控件消息才行!


下面我们使用此方法来监视一个窗口是否被激活:

注意使用WH_CBT消息来监视:

在开始之前先介绍一下几个基于WH_CBT消息的几个消息:

HCBT_CREATEWND:窗口创建消息,当一个线程调用createwindow创建窗口完成之后,windows会向监视WM_CBT消息的钩子发送此消息,并且wParam里面包含了窗口句柄,要用(HWND)的形式强转得到!

HC_ACTION:键盘消息,即当窗口被触发WM_CBT消息时带有键盘消息!

HCBT_ACTIVATE:窗口被激活消息,与HCBT_CREATEWND一样,在窗口获取激活消息之前,此消息会被转发到钩子消息里!

这些都是钩子内部定义的WM_CBT消息!

其余的最大化,最小化均可以使用Windows内部定义的消息!

这里我们只使用HCBT_ACTIVATE来监视窗口是否被激活!

HHOOK hook = SetWindowsHookEx(WH_CBT, addr, dll, ThreadID);
	BOOL bol = (hook != NULL);	//逻辑表达式
	if (bol == true){
		MessageBox(NULL, NULL, "hook ok!", 0);
	}
	funPtr();	//set hook  共享hook句柄
dll:
LRESULT  LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam){
	     switch (nCode)
		     {
		         case HCBT_ACTIVATE:
			         {
									   HDC dc = GetDC(NULL);
									   TextOut(dc, 20, 20, L"JH", 2);
				         }
			       default:
				             break;
			     }

	return CallNextHookEx(hook_, nCode, wParam, lParam);
}

这里肯定有人会问了,为什么使用dc而不是massgebox

这里要说一点:如果你使用一切带有user32.dll动态库的api,那么就会造成消息队列堵塞,因为massgebox是一个对话框在创建时会触发HCBT_CREATEWNDHCBT_ACTIVATE等消息,并且massgebox api还会阻塞函数,从而导致消息队列堵塞,那么出现这种情况windows会出现异常问题,但是当你在钩一些键盘消息时使用这个无所谓,因为这些消息并不是特别重要的消息,不会导致窗口出现异常,但是当你使用一些窗口重要消息时,比如create或激活,一旦阻塞则无法创建窗口或激活显示窗口,就会导致窗口异常,但是我们可以拦截此消息,不让它转发给窗口,但是不能导致消息队列阻塞,否则windows会直接删除此窗口!

比如:

当我在监视一个线程的创建消息:HCBT_CREATEWND

那么此线程开始创建一个窗口了,触发了此消息,但是我们内部想要在创建时在额外在此线程创建的窗口上附加一个按钮,于是我们调用了:

CreateWindowEx函数来创建一个窗口,CreateWindowEx API MSDN给出的解释是创建出来的窗口归调用者线程所有,那么此时,我们是在HCBT_CREATEWND里创建的,于是CreateWindowEx函数在创建完成之后又触发了HCBT_CREATEWND消息,于是我们又重复的去创建了这个窗口,于是消息队列堵塞了,重复这个消息,所以出现了消息队列堵塞,循环创建,导致内存爆满,直接被Windows给卡嚓掉了!

那么为什么GetDc里的dc不是窗口句柄而是NULL

答:如果使用hWnd还是会被咔嚓掉,经过本人多次测试验证得出:GetDc会调用窗口的user32.dll,来获取窗口里的绘图DC,还是会导致产生一些让消息队列堵塞的消息!

最后得出的结论是使用WM_CBT消息不能调用任何会调用user32.dllapi,否则就会直接被windows给杀掉,可能在触发此关于窗口创建最小化或最大化的消息时,窗口已经被删除了,然后要重新绘制整个窗口区域,所以导致调用任何窗体消息时会崩溃,并且本人在查了很多相关文档,大致说的都是不要在WM_CBT获取到的消息里调用任何可能阻塞消息队列的API,也就是说我们只能用来做一些相关的初始化,或者拦截进程不被创建之类的!

拦截进程不被创建非常简单:

SetWindowsHookEx(WM_CBT, addr, dll, 0);


0即为全局系统钩子

这样我们钩住全局的系统钩子产生任何消息我们都会率先拦截,如果拦截到HCBT_CREATEWND消息可以直接return,不让其转发,这样这个窗口就不会被创建!

注意这里要重点说一下WM_CREATE函数,此函数是在CreateWindowEx函数被请求调用时触发的,注意是请求调用,也就是说还没有被调用,如果我们拦截,那么这个窗口永远不会被创建!


最后在说一下,不同位的dll不能被附加到不同的位的程序上,因为有很多不可避免的因素在里面:

不同位的dll使用的字节占用不同,如果运行在32位机器上那么就无法正常运行,因为寻址原因!

所以windows可以从pe头文件里找出是多少位的并加以区分!





猜你喜欢

转载自blog.csdn.net/bjbz_cxy/article/details/80774803