孙鑫MFC学习笔记-21HOOK编程

9 篇文章 0 订阅

21.1.1基本知识

通过SetWindowsHookEx安装钩子:
HHOOK SetWindowsHookEx(
int idHook,//将要安装的钩子过程的类型
HOOKPROC lpfn,//指向相应的钩子过程
HINSTANCE hMod,//指定lpfn指向的钩子过程所在的DLL的句柄
DWORD dwThreadId//指定与钩子过程相关的线程标识
);
SetWindowsHookEx函数的作用是安装一个应用程序定义的钩子过程,并将其放到钩子链中。
最后安装的钩子过程总是排在该链的前面。

21.1.2 进程内钩子

1.安装鼠标钩子

  1. 在CinnerHookDlg::OnInitDialog()函数之前添加鼠标钩子过程代码
HHOOK g_hMouse = NULL;
LRESULT CALLBACK MouseProc(int nCode,
	WPARAM wParam,
	LPARAM lParam
	)
{
	return 1;
}
  1. 在CinnerHookDlg::OnInitDialog()函数return之前添加安装鼠标钩子过程代码
g_hMouse = SetWindowsHookEx(WH_MOUSE,MouseProc,NULL,GetCurrentThreadId());//GetCurrentThreadId():得到当前线程ID

安装鼠标钩子过程中,SetWindowsHookEx函数的入参:安装的钩子过程类型是WH_MOUSE,相应的钩子过程是MouseProc,钩子过程在当前进程中定义并且dwThreadId是当前进程的线程,所以这里hMod是NULL,dwThreadId设置为当前线程ID,该ID通过GetCurrentThreadId()函数获取。

2.安装键盘钩子

(1)屏蔽所有键盘按钮消息

类似于安装鼠标钩子的过程。

  1. 在CinnerHookDlg::OnInitDialog()函数之前添加键盘钩子过程代码
HHOOK g_hKeyboard = NULL;
LRESULT CALLBACK KeyboardProc(int code,
	WPARAM wParam,
	LPARAM lParam
	)
{
	return 1;
}
  1. 在CinnerHookDlg::OnInitDialog()函数return之前添加安装键盘钩子过程代码
g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, NULL, GetCurrentThreadId());
(2)仅屏蔽键盘的空格键和回车键消息
  1. 在CinnerHookDlg::OnInitDialog()函数之前添加键盘钩子过程代码
HHOOK g_hKeyboard = NULL;
LRESULT CALLBACK KeyboardProc(int code,
	WPARAM wParam,
	LPARAM lParam
	)
{
	//return 1;
	if (VK_SPACE == wParam || VK_RETURN == wParam)
	{
		return 1;
	}
	else
	{
		return CallNextHookEx(g_hKeyboard,code,wParam,lParam);//将按键消息传递给下一个钩子过程
	}
}
  1. 在CinnerHookDlg::OnInitDialog()函数return之前添加安装键盘钩子过程代码
g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, NULL, GetCurrentThreadId());得到当前线程ID

钩子过程通过wParam参数得到当前按下的是哪个按键。VK_SPACE宏是空格键的虚拟键码。VK_RETURN是回车键的虚拟键码。

(3)仅屏蔽键盘的组合键“Alt+F4”消息
  1. 在CinnerHookDlg::OnInitDialog()函数之前添加键盘钩子过程代码
LRESULT CALLBACK KeyboardProc(int code,
	WPARAM wParam,
	LPARAM lParam
	)
{
	//return 1;
	//if (VK_SPACE == wParam || VK_RETURN == wParam)
	if (VK_F4 == wParam && (1 == (lParam>>29 & 1)))
	{
		return 1;
	}
	else
	{
		return CallNextHookEx(g_hKeyboard,code,wParam,lParam);//将键盘消息传递给下一个钩子过程
	}
}
  1. 在CinnerHookDlg::OnInitDialog()函数return之前添加安装键盘钩子过程代码
g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, NULL, GetCurrentThreadId());

键盘按键为F4且Alt时,return 1,其余情况将键盘消息传递给下一个钩子过程。
这里按键F4用wParam 为VK_F4 宏来判断。
按键Alt用lParam第29位,值为1则是Alt键,值为0非Alt键。lParam右移29位,将第29位移动到最右侧,和1相与,值为1说明是Alt键,值为0说明非Alt键。

(4)只能按照某个特定键退出
  1. 在CinnerHookDlg::OnInitDialog()函数之前添加键盘钩子过程代码
HHOOK g_hKeyboard = NULL;
HWND g_hWnd = NULL;
LRESULT CALLBACK KeyboardProc(int code,
	WPARAM wParam,
	LPARAM lParam
	)
{
	//return 1;
	//if (VK_SPACE == wParam || VK_RETURN == wParam)
	//if (VK_F4 == wParam && (1 == (lParam>>29 & 1)))
	//{
	//	return 1;
	//}
	//else
	//{
	//	return CallNextHookEx(g_hKeyboard,code,wParam,lParam);//将消息传递给下一个钩子过程
	//}
	if (VK_F2 == wParam)
	{
		::SendMessage(g_hWnd,WM_CLOSE,0,0);
		UnhookWindowsHookEx(g_hMouse);//移走鼠标钩子过程
		UnhookWindowsHookEx(g_hKeyboard);//移走键盘钩子过程
	}
	return 1;
}
  1. 在CinnerHookDlg::OnInitDialog()函数return之前添加安装键盘钩子过程代码
g_hWnd = m_hWnd;//获取目标窗口的句柄
g_hMouse = SetWindowsHookEx(WH_MOUSE,MouseProc,NULL,GetCurrentThreadId());//GetCurrentThreadId():得到当前线程ID
g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, NULL, GetCurrentThreadId());

这里的键盘钩子过程是一个全局函数,因此该函数内部只能用一个全局的SendMessage函数。SendMessage的第一个参数是目标窗口的句柄,该句柄由CinnerHookDlg类的成员变量m_hWnd获得。
UnhookWindowsHookEx(HHOOK hhk);从钩子链中移走一个已经安装的钩子

21.1.3 全局钩子

(1)动态链接库实现全局钩子过程

要屏蔽当前正在运行的所有进程的鼠标和键盘消息,安装钩子过程的代码放到动态链接库中实现。
新建-项目-win32项目-应用程序类型选择Dll,空项目。项目名称为Hook
添加Hook.cpp,添加代码如下


#include <Windows.h>
HHOOK g_hMouse = NULL;
HHOOK g_hKeyboard = NULL;
HWND g_hwnd;
HINSTANCE g_hInst;
//鼠标钩子过程,屏蔽所有鼠标消息
LRESULT CALLBACK MouseProc(int nCode,
	WPARAM wParam,
	LPARAM lParam
	)
{
	return 1;
}
LRESULT CALLBACK KeyboardProc(int code,
	WPARAM wParam,
	LPARAM lParam
	)
{
	if (VK_F2 == wParam)
	{
		SendMessage(g_hwnd, WM_CLOSE,0,0);//窗口句柄、关闭窗口消息
		UnhookWindowsHookEx(g_hMouse);
		UnhookWindowsHookEx(g_hKeyboard);
	}
	return 1;
}
_declspec(dllexport) void SetHook(HWND hwnd);//如果接口函数没有导出,不会生成lib文件,只有dll文件生成
//安装鼠标钩子过程的函数
void SetHook(HWND hwnd)
{
	g_hwnd = hwnd;
	//g_hMouse = (WH_MOUSE, MouseProc,GetModuleHandle("Hook"),0);//第四个参数dwThreadId=0,说明安装的钩子过程与运行在同一桌面上的所有线程相关了。
	//第三个参数是安装钩子过程所在的DLL模块句柄
	//两种方式得到该句柄,1.DllMain函数,加载DLL时,系统调用DllMain并传递当前的DLL模块的句柄。2.GetModuleHandle
	//动态链接库不可单独运行,必须被调用。编写一个客户端程序,加载这个DLL。
	如果接口函数没有导出,不会生成lib文件,只有dll文件生成.所以给Hook.dll代码中添加_declspec(dllexport) void SetHook();
	//要调试dll,将dll复制到运行程序同目录下。
	//留后门,传递HookTest的窗口句柄给键盘钩子过程
	g_hMouse = SetWindowsHookEx(WH_MOUSE, MouseProc, g_hInst, 0);//用g_hInst或者GetModuleHandle获取安装钩子过程所在的dll句柄都可以
	g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, GetModuleHandle(("Hook")), 0);
}

1.SetWindowsHookEx第三个参数是安装钩子过程所在的DLL模块句柄
有两种方式得到该句柄:方法(1).DllMain函数,加载DLL时,系统调用DllMain并传递当前的DLL模块的句柄。如代码中设置鼠标钩子。方法(2)用模块定义文件及GetModuleHandle,如代码中设置键盘钩子。
模块定义文件Hook.def内容:

LIBRARY Hook
EXPORTS
SetHook  @2

记得一定要配置模块定义文件到dll的属性中:
工程属性–>linker—>input---->Module Definition File
在这里插入图片描述

2.如果接口函数没有导出,不会生成lib文件,只有dll文件生成.所以给Hook.dll代码中添加_declspec(dllexport) void SetHook();
3.键盘钩子过程留后门F2,防止将所有进程的鼠标和键盘消息都屏蔽后,卡死的情况。当键盘按下F2,时,发送WM_CLOSE消息给调用进程的主窗口,SendMessage需要主窗口的句柄,由SetHook函数传递m_hWnd给g_hwnd 。
上述货物安装钩子过程所在的DLL模块句柄要用的.DllMain函数代码如下:
dllmain.cpp

#include<Windows.h>

extern HINSTANCE g_hInst;
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
	g_hInst = hModule;
	return TRUE;

}

动态链接库不可单独运行,必须被调用。编写一个客户端程序,加载这个DLL。
新建-项目-基于对话框的MFC项目-项目名称为HookTest
配置上面动态链接库项目Hook生成的Hook.lib文件:HookTest属性-链接器-输入-值设置为:…\Debug\Hook.lib
HookTestDlg.cpp文件中添加安装钩子代码:

_declspec(dllimport) void SetHook(HWND hwnd);
// CHookTestDlg 消息处理程序

BOOL CHookTestDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();
	...
	// TODO:  在此添加额外的初始化代码
	int cxScreen, cyScreen;
	cxScreen = GetSystemMetrics(SM_CXSCREEN);
	cyScreen = GetSystemMetrics(SM_CYSCREEN);
	SetWindowPos(&wndTopMost, 0, 0, cxScreen, cyScreen, SWP_SHOWWINDOW);//置顶HookTest的对话框窗口
	SetHook(m_hWnd);
	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

其中,窗口置顶并占据整个屏幕用SetWindowPos实现。

(2)共享节实现全局变量在多个进程间共享

上面的全局钩子程序,在不切换进程时按下F2键HookTest程序退出,在切换进程之后,按下F2键HookTest程序没有退出。原因是WINDOWS采用了**写入时复制时机制。**即就是DLL有个数据被两个进程共享,当第2个进程想修改DLL数据页面2的数据,操作系统会将数据2页面的数据复制一份,新建一个页面,断开数据2页面与第2个进程之间的映射关系,将新页面与第2个进程映射,这样修改第2个进程的页面数据相当于修改新页面的额数据。而第1个进程仍然用DLL的原始数据,第一个进程和第二个进程在修改之前共享同一份数据。修改后不是同一个页面上的数据。
所以上面程序HookTest,在调用SetHook函数时,全局窗口变量g_hWnd发生变化,操作系统采用了写入时复制时机制,为HoolTest进程 重新分配一个页面,随后对g_hWnd变量的设置值就是在新页面中进行的,在切换到其他进程之后,g_hWnd仍然是原始数据,所以按下F2后HookTest不能退出。
想要切换进程后,按下F2,HookTest仍能退出,需要采用共享节的方法。给Hook.dll中新建并共享一个节,将全局窗口句柄变量g_hWnd放入该节中,该全局变量在多个进程之间共享。
定义共享结点有两种方式:
1.节设置为R(读)W(写)S(共享)类型,例如

#pragma data_seg(".MySec")
HWND g_hwnd = NULL;
#pragma data_seg()
#pragma comment(linker,"/section:.MySec,RWS");//将.MySec这个节设置为共享节

2.用SECTIONS关键字实现,例如
Hook.def文件中添加

LIBRARY Hook
EXPORTS
SetHook  @2
SECTIONS
MySec READ WRITE SHARED  

显示动态链接库导出函数的命令:dumpbin -exports Hook.dll
显示动态链接库的节,命令:dumpbin -headers Hook.dll

在这里插入图片描述
通过共享节方式,所有进程都共享g_hwnd的同一份数据。当运行HookTest程序后,g_hwnd为HookTest程序的主窗口句柄,切换到其他窗口,g_hwnd值未变,仍然为HookTest程序的主窗口句柄。按下F2,发送WM_CLOSE消息,HookTest退出。

总结

1.Hook编程实现进程内钩子和全局钩子安装

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值