给网游写一个挂吧(二) – 启动外挂上
前面的文章給網游寫一個掛吧?–?反反外掛驅動的驅動,我們已經可以訪問游戲的內存之后,接下來需要:
1.?????????找到游戲里關鍵元素的偏移量,比如生命值的內存的位置。一般來說,大部分大型3D游戲都是用C++編寫的,游戲里面的元素都是面向對象的,比如玩家是一個對象,那么生命值、魔法值之類的東西都是這個對象的一個屬性。按照C++的內存布局,一般來說,只要源代碼里的結構體不發生變化,屬性的偏移量一般來說都是一樣的。
2.?????????找到游戲里一些關鍵函數的地址,便于外掛程序來調用。
?
查找關鍵元素的偏移量和關鍵函數地址一般來說都是苦力活,當然也是智力活,需要你的逆向工程水平不錯,網上有些相關的教程,這里我就不再詳述了。
?
這里假設我們已經找到游戲的偏移量了,現在的問題是如何啟動外掛以操控游戲,一般來說有幾種選擇:
1.?????????要么是內掛,將掛注入到網游進程的內存空間里,這樣掛就相當于網游自己的一個組件,對游戲進程擁有絕對的訪問權,可以讀寫游戲的虛擬內存地址以及調用游戲內置的函數。這種做法的弊端是,如果游戲有非法組件檢測線程的話,很有可能被發現。
2.?????????要么是外掛,將掛作為一個獨立的進程,這樣掛可以通過Read/WriteVirtualMemory來讀寫游戲的內存,再通過CreateRemoteThread API啟動一個遠程線程來調用游戲內置的函數。這種做法可以查看文章:代碼注入的三種方法。
?
那本文我們講解第一種方法?-?內掛。并針對兩款游戲來說說注入內掛的方法:
?
DNF –?使用輸入法注入技術
輸入法注入技術的原理是,寫一個輸入法DLL并在系統中注冊,然后向游戲發送一個切換輸入法的消息?–?當然是切換到我們寫的輸入法,Windows會加載我們的輸入法DLL,在這個DLL的DllMain函數里,我們就可以完成一些內掛加載以及初始化的工作:
1.?????????首先寫一個輸入法DLL,隨便從網上下載一個示例用的輸入法源碼即可。
2.?????????在輸入法DLL的DllMain函數的DLL_PROCESS_ATTACH事件中,啟動外掛線程。
3.?????????在單獨的外掛進程里?–?一般來說這個進程就是用來給外掛用戶操作的一個Windows GUI程序,在合適的地方:
a)?????????用imm32.dll里的ImmInstallIMEw API函數在系統里注冊我們的輸入法。
b)?????????用FindWindows API查找到所有需要注入的窗口,這里就是獲取DNF的窗口句柄。
c)?????????最后用PostMessage WM_INPUTLANGCHANGEREQUEST消息強迫Windows針對DNF窗口切換我們的輸入法,從而達到加載內掛的目的。
?
關鍵代碼如下?–?整個程序大部分代碼都是用C#完成,稍后介紹選用C#的原因:
?
輸入法注入代碼C#部分:
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | public?bool?InjectDllToWindow(string?dllPath,?string?windowText =?"地下城與勇士", string?classText =?"地下城與勇士",bool?IfMonitor=true) { ??? InjectedDll = dllPath; ??? WindowText = windowText; ??? ClassText = classText; ? ????// 1,?注冊輸入法 ??? HKL = RegisterIME(); ????if?(HKL ==?IntPtr.Zero) ??? { ????????MessageBox.Show(string.Format("GetLastError: {0}", GetLastError())); ????????//2,如果注冊失敗,檢查是否已經被注冊 ??????? HKL = MImeFindByName(); ??? } ? ????if?(HKL ==?IntPtr.Zero) ??? { ??????? isRegister =?false; ????????return?false; ??? } ??? isRegister =?true; ? ????//3,把需要注入的dll傳遞給服務輸入法dll中 ??? IMESetPubString(dllPath, 0, 0, 0, 0); ? ????//4,查找所有需要注入的窗口 ????List<IntPtr> windowsToInject = FindWindows(classText, windowText); ? ????//5,注入輸入法到窗口 ????foreach?(IntPtr?window?in?windowsToInject) ??? { ??????? InjectToWindow(window); ??? } ? ??? WindowsHaveInjected = windowsToInject; ? ????if(IfMonitor) ??? { ????//6,開啟監視線程,監視新的窗口,一旦開啟,立刻注入 ????????WorkThread?thread =?new?WorkThread(MonitorDNFWindow); ??????? workThreadAsyncResult = thread.BeginInvoke(null,?null); ??? } ????return?true; } ? private?IntPtr?RegisterIME() { ????string?tempDir =?Environment.CurrentDirectory; ????Environment.CurrentDirectory =?Environment.SystemDirectory;//把工作目錄切換到系統目錄 ????IntPtr?hkl = ImmInstallIMEW(ImeName, ImeFriendlyName);?//安裝服務輸入法 ????Environment.CurrentDirectory=tempDir;?//切換回原目錄 ????return?hkl; } ? private?void?InjectToWindow(IntPtr?hWnd) { ??? PostMessage(hWnd, WM_INPUTLANGCHANGEREQUEST, (IntPtr)0x01, HKL); } |
?
在上面第9行調用47 – 54行的函數,將外掛的工作目錄切換到系統目錄,因為我們將輸入法放到系統目錄,方便系統查找,并安裝輸入法。
?
第25行里,設置在輸入法注入成功后,需要執行的操作,一般來說就是啟動掛了。有些內掛會在注入成功后,注冊一個快捷鍵,通過快捷鍵呼出一個窗口,這個窗口可以用來跟用戶操作界面通信,執行操作界面來的命令。然而,在某些游戲里,呼出的窗口會馬上被檢查到,我們這里將介紹在游戲進程里啟動.NET程序,啟動一個.NET Remoting服務的方式。
?
第31 - 34行,通過FindWindows系統調用枚舉系統上的窗口,找到目標窗口,執行注入操作,具體的注入操作參見56 – 59行的代碼。在.NET代碼里調用C/C++函數的方式,請參閱文章:使用Signature Tool自動生成P/Invoke調用Windows API的C#函數聲明。
?
輸入法C++部分關鍵代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved) { ???switch(fdwReason) ??? { ??????case?DLL_PROCESS_ATTACH: ????if?(CilentDLL==NULL) ??? { ??????? ??if?(lstrlen(g_IMEDLLString)>0) ??????? ? { ??????????? ? StartTheDotNetRuntime(); ??????? ? } ??? } ??????? ??break; ??? ??case?DLL_THREAD_ATTACH: ??????? ?break; ??? ??case?DLL_THREAD_DETACH: ??????? ?break; ??????case?DLL_PROCESS_DETACH: ????????break; ??????default: ????????break; ??? } ????return?true; } ? DWORD CALLBACK StartTheDotNetRuntime(LPVOID lp) { ??????? HRESULT hr = S_OK; ??????? ICLRMetaHost??? *m_pMetaHost = NULL; ??????? ICLRRuntimeInfo *m_pRuntimeInfo = NULL; ??????? ICLRRuntimeHost??? *pClrHost = NULL; ?????? hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*) &m_pMetaHost); if?(hr != S_OK) ????return?hr; hr = m_pMetaHost->GetRuntime (L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID*) &m_pRuntimeInfo); if?(hr != S_OK) ????return?hr; hr = m_pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*) &pClrHost ); ???????if?(FAILED(hr))?return?hr; ? ??? hr = pClrHost->Start(); ? ??? DWORD dwRet = 0; ??? hr = pClrHost->ExecuteInDefaultAppDomain( ??????? g_IMEDLLString, ??????? _T("ManagedDll.Program"), _T("Start"), _T("nothing to post"), &dwRet); ? ??? hr = pClrHost->Stop(); ? ??? pClrHost->Release(); ? ????return?S_OK; } |
?
在第10行代碼,輸入法注入成功后在游戲進程里啟動.NET虛擬機,這里啟動的4.0的運行庫?–?參看36行代碼,虛擬機成功啟動后,會返回一個ICLRRuntimeHost的COM接口,根據這個接口,外掛就可以創建托管代碼運行需要的應用程序域?–?參看45 – 47行。在應用程序域里執行代碼并不需要一個.exe的可執行文件,只需要是一個托管程序的DLL文件,這個DLL文件需要放在游戲的目錄里,因為我們的掛是運行在游戲的進程里,工作目錄也自然變成了游戲的工作目錄了。
?
在47行,我們可以看到,可以指定DLL內部任意一個類型的靜態函數作為入口點,下面是ManagedDll.Program.Start的源代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | namespace?ManagedDll { ?????public?class?Program ??? { ????????static?int?Start(string?argument) ??????? { ??????????? RemotingServer.Start(); ????????????while?(true) ??????????? { ????????????????Thread.Sleep(1000); ??????????? } ????????????return?0; ??????? } ??? } } |
?
在第7行,我們啟動了一個.NET Remoting服務(或者說是web服務,因為.NET Web服務本來就是基于Remoting的),等待任意一個地方的Remoting客戶端鏈接……對于在非托管進程當中啟動托管程序的方法,詳情請參看:將托管dll注入到非托管進程中。
?
設計考量
?
之所以選用C#的原因是:
1.?????????可以快速編程,而且有豐富的類庫。
2.?????????垃圾回收機制可以增強掛的穩定性,而且也不用考慮內存泄露的問題。
3.?????????有很強大的調試工具,以我經驗來看,暫時還沒有看到比VS更強大的調試工具。
4.?????????最后,在游戲進程里的內掛和外部供用戶配置的GUI程序需要通信,沒有比.NET Remoting更方便的東西了!
?
總結
以上是生活随笔為你收集整理的给网游写一个挂吧(二) – 启动外挂上的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 反调试技术二
- 下一篇: 给网游写一个挂吧(三) – 启动外挂下