游戏外挂编程三之游戏进程钩子
Q Q:408365330 E-Mail:egojit@qq.com
游戏外挂,首先要做的就是将我们的代码放到游戏进程中去,以此来达到“不可告人的目的”。这里我就介绍一种比较常用的方法。就是进程钩子的方式将DLL放到游戏进程中去。其实这也是一些木马盗取账号和密码的方式。我们这里只是讲解通过一个进程钩子实现代码注入游戏进程。由第二节我们知道怎么去写一个C++MFC的DLL。这里我们首先新建一个DLL。首先和大家说下这里我会用到4个Window AP函数。
HWND FindWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName );
这个API是找到一个给定类名或者窗口名称的窗口句柄。参数就不用介绍了,顾名思义,第一个参数就是窗口类名称,第二个参数就是窗口名称。我们在这里只是用第二个参数。第一个参数放NULL。窗口名称我们可以通过Spy++去获取这是VS的一个工具:
打开这个工具。
将那个圆拖到游戏标题栏后松开我们就可以看到这个游戏窗口的标题了。我们根据这个标题去找窗口句柄。
DWORD GetWindowThreadProcessId( HWND hWnd, LPDWORD lpdwProcessId );
这个API返回创建指定窗口的线程ID,MSDN上这样说的 “This function retrieves the identifier of the thread that created the specified window and, optionally, the identifier of the process that created the window”
参数hWnd是窗口句柄。就是FindWindow函数返回的值。lpdwProcessId是创建窗口的进程标志ID,它是一个输出参数,也就是一个指针。就和C#中的out参数差不多。这个参数可以放NULL,如果不是NULL,它会返回创建指定窗口的进程标志。
HHOOK SetWindowsHookEx( int idHook,HOOKPROC lpfn,HINSTANCE hMod, DWORD dwThreadId);
这个API就是设置钩子。第一个参数是钩子类型。钩子类型具体有哪些可以参照MSDN这里我们用到的是WH_KEYBOARD。第二个参数就是一个回调函数。回调函数的格式如下:
LRESULT CALLBACK KeyboardProc( int code, WPARAM wParam, LPARAM lParam );
第三个参数就是DLL的模块句柄,我们可以通过API函数来获取
HMODULE GetModuleHandle( LPCTSTR lpModuleName);
这个时候大家就会疑惑了SetWindowsHookEx的第三个参数是HINSTANCE类型,但是GetModuleHandle返回的却是HMODULE,这个能对上吗?其实能对上。你可以看看定义它的头文件,我们能看到typedef HINSTANCE HMODULE 这样大家就很容易看出它们其实就是一个东西。这在Windows 核心编程中经常碰到这种情况。我们在写Windows游戏外挂的时候,最好要去研究下Windows 核心编程。推荐一本比较耐看的书《windows核心编程》,我的是最新版.这本书的作者绝对Windows系统有很高的研究。值得反复去看,去研究。不跑题了,继续……。GetModuleHandle的第一个参数就是要注入的DLL路径可以是相当也可以绝对,当然我推荐相对路径。SetWindowsHookEx的最后一个参数就是GetWindowThreadProcessId返回的值。通过这样的讲解大家应该了解了。
下面就是实实在在的编码。我们新建一个MFC DLL(我这个DLL的名称是GameHookDLL)。
///钩子回调函数 LRESULT CALLBACK KeyboardProc( int code, // hook code WPARAM wParam, // virtual-key code LPARAM lParam // keystroke-message information ){ AFX_MANAGE_STATE(AfxGetStaticModuleState()); if(wParam==VK_F1&&((lParam&(1<<31))==0)){ AfxMessageBox(L"F1键在游戏窗口被按下了!"); } return CallNextHookEx(0,code,wParam,lParam); } //(LPWSTR)"YB_OnlineClient" void SetHook(LPWSTR prc_name){ AFX_MANAGE_STATE(AfxGetStaticModuleState()); HWND hd=FindWindow(NULL,prc_name); if(hd==NULL) { AfxMessageBox(L"请打开输入的程序进程"); return; } DWORD dwid=GetWindowThreadProcessId(hd,NULL); // HINSTANCE hdll=::GetModuleHandleW(L"GameHookDLL.dll"); SetWindowsHookEx(WH_KEYBOARD,&KeyboardProc,hdll,dwid); }
这是我在DLL中添加的两个函数,上面是SetWindowsHookEx的回调处理函数。对了,这个回调函数一定别忘了最后一行
return CallNextHookEx(0,code,wParam,lParam);如果回调KeyboardProc 中的code值小于0它会跳过去然后call下一个消息MSDN中的原文是:If code is less than zero, the hook procedure must pass the message to the CallNextHookEx function without further processing and should return the value returned by CallNextHookEx。所以我建议还是自己研究MSDN,比较我个人能力有限,说不定理解的有误,当然在这里如果有什不正确或者理解有偏差的地方希望大家谅解。这里我们DLL只需要对外暴露第二个函数void SetHook(LPWSTR prc_name)。至于怎么暴露,自己去看第二节。
if(wParam==VK_F1&&((lParam&(1<<31))==0)){ AfxMessageBox(L"F1键在游戏窗口被按下了!"); }
wParam==VK_F1表示我们按下的F1键。(lParam&(1<<31))==0对lParam参数不熟悉的就不好理解了。lParam的第31位如果是0表示按下,如果是1表示按键弹起。我们这里是判断F1按键被按下。
如果没有(lParam&(1<<31))==0我们按下F1键将会弹出两次,一次是按下时弹出,一次是F1弹起式弹出。所以要保证lParam的第31位是0我们才弹出对话框。1<<31是10000000000000000000
0000000000后面有31个0而lParam的0~30位我们不确定但是我们&一下肯定都是0,然后第31位是0最后结果肯定是0这样就实现了判断。
MSDN中远英文是:
lParam[in] Specifies the repeat count, scan code, extended-key flag, context code, previous key-state flag, and transition-state flag. For more information about the lParam parameter, see Keystroke Message Flags. This parameter can be one or more of the following values.
- 0-15.Specifies the repeat count. The value is the number of times the keystroke is repeated as a result of the user's holding down the key.
- 16-23.Specifies the scan code. The value depends on the OEM.
- 24.Specifies whether the key is an extended key, such as a function key or a key on the numeric keypad. The value is 1 if the key is an extended key; otherwise, it is 0.
- 25-28.Reserved.
- 29.Specifies the context code. The value is 1 if the ALT key is down; otherwise, it is 0.
- 30.Specifies the previous key state. The value is 1 if the key is down before the message is sent; it is 0 if the key is up.
- 31.Specifies the transition state. The value is 0 if the key is being pressed and 1 if it is being released.
接下来我们去新建一个MFC exe程序。在这个程序中去调用这个void SetHook(LPWSTR prc_name)函数。把窗口名称作为参数传过去。这里我添加的是MFC 简单对话框。对话框的布局如图:
然后给文本框关联上CString类型的变量txt_prc_name。在按钮事件中添加注册钩子代码:
void CGameWGClientDlg::OnBnClickedOk() { UpdateData(true); LPWSTR s1=(LPWSTR)(LPCTSTR)txt_prc_name; SetHook(s1); // TODO: 在此添加控件通知处理程序代码 //CDialogEx::OnOK(); }
这些只需要你掌握一点MFC知识。LPWSTR s1=(LPWSTR)(LPCTSTR)txt_prc_name;这个经过两次装换,主要是CString类型没法直接装换成LPWSTR类型。所以就这样处理了。好代码搞定,来看效果:
在MFC客户程序中输入我们用Spy++找出的游戏窗口名称,然后点击确定这样钩子就被注册到了游戏进程中。这时候我们在登陆框中随便输入,直到我们输入F1弹出对话框。这样通过键盘钩子注入进程的原型就搞定了。这一节就到这里。下一节我们利用这个钩子实现自动喝药的过程(比较慢,可能更新比较慢希望谅解)。