本教程面向有C\C++基础的人,最好还要懂一些Windows编程知识
代码一律用Visual Studio 2013编译,如果你还在用VC6请趁早丢掉它...
写这个教程只是为了让玩家更好地体验所爱的单机游戏,顺便学到些逆向知识,我不会用网络游戏做示范,请自重
本章内容主要是认识钩子(hook)
(微软的解释:)钩子(hook)是一种截获事件的机制,一个截获某种事件的函数叫做钩子过程(hook procedure),钩子过程可以处理它接收的每个事件,然后可以改变或取消这个事件
微软提供了API来实现一些钩子:
// 安装钩子,参数是钩子类型,钩子过程,钩子过程所在模块句柄,钩子的目标线程ID
// 如果dwThreadId是0则会安装全局钩子(截获所有进程所有线程的事件)
// 这时除了几种钩子类型,其他的必须把钩子过程写在DLL中,系统会把这个DLL注入到其他进程(本章不讲)
// 返回钩子句柄
HHOOK WINAPI SetWindowsHookEx(
_In_ int idHook,
_In_ HOOKPROC lpfn,
_In_ HINSTANCE hMod,
_In_ DWORD dwThreadId
);
// 卸载钩子,参数是钩子句柄
BOOL WINAPI UnhookWindowsHookEx(
_In_ HHOOK hhk
);
// 把事件传递给下一个钩子
LRESULT WINAPI CallNextHookEx(
_In_opt_ HHOOK hhk,
_In_ int nCode,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
更多关于钩子的详细说明请看MSDN:
Hooks Overview
本章会介绍低级键盘钩子(WH_KEYBOARD_LL)和低级鼠标钩子(WH_MOUSE_LL)
低级键盘钩子
低级键盘钩子可以截获准备进入某个线程输入队列的键盘输入消息(键盘按下、键盘弹起),它是少数不用把钩子过程写在DLL的全局钩子之一,而且它只能作为全局钩子
另外还有一个普通键盘钩子(WH_KEYBOARD),它和低级键盘钩子的区别是它截获准备被GetMessage或PeekMessage返回的键盘消息,而且如果要截获其他进程的消息你只能把它写到DLL里然后注入到其他进程
那么问题来了,既然低级键盘钩子的钩子过程没有注入到其他进程,它是怎么被调用的?这个的实现是系统发送个消息到安装钩子的线程实现调用钩子过程,这也意味着安装低级键盘钩子的线程必须有消息循环
完整的监听键盘输入的例程:
#include <Windows.h>
// 钩子句柄
HHOOK g_hHook;
// 钩子过程
LRESULT CALLBACK kbdProc(int code, WPARAM wParam, LPARAM lParam)
{
if (code != HC_ACTION) // 不应该做处理
return CallNextHookEx(g_hHook, code, wParam, lParam);
PKBDLLHOOKSTRUCT param = (PKBDLLHOOKSTRUCT)lParam;
// 按下ESC键退出
if (param->vkCode == VK_ESCAPE)
PostQuitMessage(0);
// 取键名
char keyName[200];
GetKeyNameTextA(param->scanCode << 16, keyName, _countof(keyName));
// 判断是按下还是弹起
BOOL keyDown = (param->flags & (1 << 7)) == 0;
printf("%s %s\n", keyName, keyDown ? "按下" : "弹起");
// 把事件传递给下一个钩子
return CallNextHookEx(g_hHook, code, wParam, lParam);
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("开始监听键盘,按下ESC键退出\n");
// 安装低级键盘钩子
// GetModuleHandle(NULL)会返回主模块(exe模块)句柄
g_hHook = SetWindowsHookEx(WH_KEYBOARD_LL, kbdProc, GetModuleHandle(NULL), NULL);
// 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// 卸载钩子
UnhookWindowsHookEx(g_hHook);
return (int)msg.wParam;
}
当然,别想用这个盗QQ,有点安全意识的软件都会有密码输入保护的
另外一个钩子WH_JOURNALRECORD也可以用来记录键盘和鼠标消息,但是它不能取消事件
屏蔽A键和把X键替换成Y键的例程:
把钩子过程改成下面这样就行了
if (code != HC_ACTION) // 不应该做处理
return CallNextHookEx(g_hHook, code, wParam, lParam);
PKBDLLHOOKSTRUCT param = (PKBDLLHOOKSTRUCT)lParam;
// 按下ESC键退出
if (param->vkCode == VK_ESCAPE)
PostQuitMessage(0);
// 屏蔽A键
if (param->vkCode == 'A')
{
// 返回一个非0值会取消这个事件
return 1;
}
// 把X键替换成Y键
if (param->vkCode == 'X')
{
static const int scanCode = MapVirtualKey('Y', MAPVK_VK_TO_VSC);
BOOL keyDown = (param->flags & (1 << 7)) == 0;
// 模拟输入Y键,这里我就偷懒用keybd_event了...
keybd_event('Y', scanCode, keyDown ? 0 : KEYEVENTF_KEYUP, 0);
// 取消这个事件
return 1;
}
// 把事件传递给下一个钩子
return CallNextHookEx(g_hHook, code, wParam, lParam);
运行后在记事本按下A键和X键试试
对于用DInput输入的程序用低级键盘钩子屏蔽按键是没用的,毕竟这个也只是处理消息,不过可以用来改键(虽然原按键也输入了!)(和SendInput一样,keybd_event指定了扫描码DInput程序也会响应)
想要更底层的屏蔽和真正的改键就用上一章提到的Interception吧(那可是内核级的),这样对所有的程序都有效了
低级鼠标钩子
低级鼠标钩子和低级键盘钩子一样,只不过它截获的是鼠标事件,也有个普通鼠标钩子(WH_MOUSE),和低级鼠标钩子的区别参考低级键盘钩子和普通键盘钩子的区别
完整的屏蔽鼠标移动和鼠标右键的例程:
#include <Windows.h>
// 钩子句柄
HHOOK g_hHook;
// 钩子过程
LRESULT CALLBACK mouseProc(int code, WPARAM wParam, LPARAM lParam)
{
if (code != HC_ACTION) // 不应该做处理
return CallNextHookEx(g_hHook, code, wParam, lParam);
//PMSLLHOOKSTRUCT param = (PMSLLHOOKSTRUCT)lParam;
// 按下鼠标左键退出
if (wParam == WM_LBUTTONDOWN)
PostQuitMessage(0);
// 屏蔽鼠标移动、鼠标右键
if (wParam == WM_MOUSEMOVE || wParam == WM_RBUTTONDOWN || wParam == WM_RBUTTONUP)
{
// 返回一个非0值会取消这个事件
return 1;
}
// 把事件传递给下一个钩子
return CallNextHookEx(g_hHook, code, wParam, lParam);
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("开始监听鼠标,按下鼠标左键退出\n");
// 安装低级鼠标钩子
// GetModuleHandle(NULL)会返回主模块(exe模块)句柄
g_hHook = SetWindowsHookEx(WH_MOUSE_LL, mouseProc, GetModuleHandle(NULL), NULL);
// 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// 卸载钩子
UnhookWindowsHookEx(g_hHook);
return (int)msg.wParam;
}
那么键盘鼠标钩子除了盗号外还有什么用呢?比如某些游戏里用热键没用的话可以用键盘钩子作为热键,另外大部分电子教室锁定学生键盘鼠标也是用了低级键盘钩子和低级鼠标钩子
本章内容只是为了认识钩子嘛,以后会讲到更强大的钩子,这也是游戏作弊器的一个重要机制