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彈出對話框。這樣通過鍵盤鈎子注入進程的原型就搞定了。這一節就到這里。下一節我們利用這個鈎子實現自動喝葯的過程(比較慢,可能更新比較慢希望諒解)。