戰(zhàn):從線(xiàn)程發(fā)包到HOOK加密解密的逆向分析)
1. 項(xiàng)目概述一場(chǎng)從“數(shù)據(jù)流”切入的攻防實(shí)戰(zhàn)如果你是一名對(duì)游戲安全、逆向工程感興趣的開(kāi)發(fā)者或安全研究員那么UE4引擎的游戲絕對(duì)是一個(gè)絕佳的“練手場(chǎng)”。這個(gè)項(xiàng)目標(biāo)題“UE4游戲安全實(shí)戰(zhàn)從線(xiàn)程發(fā)包到HOOK加密解密的逆向分析”聽(tīng)起來(lái)技術(shù)棧很深但它本質(zhì)上描繪了一條非常清晰的實(shí)戰(zhàn)路徑從最外圍的網(wǎng)絡(luò)數(shù)據(jù)包捕獲與分析開(kāi)始深入到游戲內(nèi)部處理數(shù)據(jù)的線(xiàn)程最終定位并干預(yù)其核心的加密解密邏輯。這就像一場(chǎng)外科手術(shù)目標(biāo)不是破壞游戲而是徹底理解其內(nèi)部通信與數(shù)據(jù)保護(hù)機(jī)制。對(duì)于想學(xué)習(xí)游戲反外掛、協(xié)議分析、或是單純想理解現(xiàn)代游戲客戶(hù)端如何保護(hù)自身數(shù)據(jù)的開(kāi)發(fā)者來(lái)說(shuō)這條路徑涵蓋了從入門(mén)到進(jìn)階的核心技能。為什么是UE4因?yàn)樗餍辛?。從?dú)立游戲到3A大作虛幻引擎4以及現(xiàn)在的UE5構(gòu)建了海量的游戲世界。引擎的流行意味著其底層架構(gòu)、內(nèi)存管理、網(wǎng)絡(luò)模塊和常見(jiàn)的加密模式都有很強(qiáng)的規(guī)律性。一旦你掌握了一套針對(duì)UE4的分析方法論你就能觸類(lèi)旁通快速切入許多不同的游戲。這次實(shí)戰(zhàn)我們將模擬一個(gè)典型場(chǎng)景分析一款使用UE4開(kāi)發(fā)的網(wǎng)絡(luò)游戲目標(biāo)是理解其客戶(hù)端與服務(wù)器之間的通信協(xié)議特別是加密部分并嘗試通過(guò)HOOK技術(shù)來(lái)動(dòng)態(tài)加解密數(shù)據(jù)。這絕不是為了制作外掛而是為了深入理解安全機(jī)制的實(shí)現(xiàn)與潛在的脆弱點(diǎn)這對(duì)于構(gòu)建更安全的游戲或進(jìn)行安全審計(jì)至關(guān)重要。2. 核心思路拆解庖丁解牛層層遞進(jìn)面對(duì)一個(gè)復(fù)雜的UE4游戲客戶(hù)端直接一頭扎進(jìn)數(shù)GB的二進(jìn)制文件里尋找加密函數(shù)無(wú)異于大海撈針。一個(gè)高效、結(jié)構(gòu)化的分析思路是成功的關(guān)鍵。我們的整體策略可以概括為“由外而內(nèi)動(dòng)靜結(jié)合”。2.1 第一階段網(wǎng)絡(luò)層監(jiān)聽(tīng)與協(xié)議初探一切始于數(shù)據(jù)流。游戲客戶(hù)端與服務(wù)器的所有交互最終都體現(xiàn)為網(wǎng)絡(luò)上的數(shù)據(jù)包。因此我們的第一步永遠(yuǎn)是抓包。使用像Wireshark這樣的專(zhuān)業(yè)工具或者針對(duì)游戲進(jìn)程更精準(zhǔn)的WinPcap/Raw Socket工具捕獲游戲運(yùn)行時(shí)的網(wǎng)絡(luò)流量。這個(gè)階段的目標(biāo)不是解密內(nèi)容而是觀察連接模式是TCP長(zhǎng)連接還是UDP服務(wù)器IP和端口是什么數(shù)據(jù)包特征是否有明顯的包頭比如長(zhǎng)度字段、命令字?jǐn)?shù)據(jù)包是明文還是肉眼可見(jiàn)的亂碼加密跡象交互頻率哪些操作如移動(dòng)、攻擊、打開(kāi)背包會(huì)觸發(fā)發(fā)包觸發(fā)前后的數(shù)據(jù)包變化有什么規(guī)律通過(guò)對(duì)比不同操作下的數(shù)據(jù)包我們可以初步判斷協(xié)議的大致結(jié)構(gòu)。例如移動(dòng)操作可能發(fā)送包含坐標(biāo)、方向的小數(shù)據(jù)包而登錄操作則可能發(fā)送包含賬號(hào)密碼顯然是加密的的較大數(shù)據(jù)包。這個(gè)階段建立起的“數(shù)據(jù)包-游戲行為”映射關(guān)系是后續(xù)逆向分析的路標(biāo)。2.2 第二階段內(nèi)存定位與線(xiàn)程分析網(wǎng)絡(luò)數(shù)據(jù)包最終是由游戲進(jìn)程內(nèi)的某個(gè)或某幾個(gè)線(xiàn)程組包并發(fā)送的。我們的下一個(gè)目標(biāo)就是在內(nèi)存中找到負(fù)責(zé)“發(fā)包”的關(guān)鍵代碼位置。這里動(dòng)態(tài)分析工具大顯身手。使用Cheat Engine進(jìn)行內(nèi)存掃描這是逆向UE4游戲的經(jīng)典入門(mén)步驟。因?yàn)閁E4有一套相對(duì)固定的對(duì)象管理系統(tǒng)UObject、GNames、GObjects等我們可以利用這些特征快速定位游戲世界中的實(shí)體比如玩家自身的角色對(duì)象。通過(guò)Cheat Engine掃描玩家角色的生命值、坐標(biāo)等已知變化的數(shù)據(jù)我們可以找到這些數(shù)據(jù)在內(nèi)存中的地址。更重要的是通過(guò)查找“訪(fǎng)問(wèn)該地址的代碼”我們往往能順藤摸瓜找到讀寫(xiě)這些數(shù)據(jù)的函數(shù)而這些函數(shù)很可能就在處理游戲邏輯和網(wǎng)絡(luò)同步的線(xiàn)程上下文中。線(xiàn)程發(fā)包函數(shù)定位UE4的網(wǎng)絡(luò)層通?;谄渥陨淼腘etDriver和Channel體系。發(fā)送數(shù)據(jù)最終會(huì)調(diào)用到FSocket::Send或sendto這樣的系統(tǒng)API。我們可以通過(guò)調(diào)試器在這些系統(tǒng)API上設(shè)置斷點(diǎn)當(dāng)游戲發(fā)送數(shù)據(jù)包時(shí)斷點(diǎn)觸發(fā)。此時(shí)查看調(diào)用堆棧Call Stack就能清晰地看到游戲內(nèi)部從業(yè)務(wù)邏輯到網(wǎng)絡(luò)層的完整調(diào)用鏈。堆棧中屬于游戲模塊而非系統(tǒng)DLL的、層級(jí)較高的函數(shù)很可能就是我們要找的“線(xiàn)程發(fā)包函數(shù)”比如某個(gè)UPlayerController的ServerMove函數(shù)實(shí)現(xiàn)或者某個(gè)RPC遠(yuǎn)程過(guò)程調(diào)用的發(fā)送函數(shù)。2.3 第三階段加密函數(shù)識(shí)別與HOOK介入找到了發(fā)包函數(shù)我們就逼近了加密發(fā)生的時(shí)刻。通常游戲會(huì)在數(shù)據(jù)組包完成之后、調(diào)用系統(tǒng)發(fā)送函數(shù)之前對(duì)數(shù)據(jù)包進(jìn)行加密。識(shí)別加密函數(shù)在發(fā)包函數(shù)內(nèi)部或附近進(jìn)行代碼分析使用IDA Pro或Ghidra進(jìn)行靜態(tài)分析配合x(chóng)64dbg動(dòng)態(tài)調(diào)試。尋找一些典型特征對(duì)數(shù)據(jù)緩沖區(qū)進(jìn)行循環(huán)操作、調(diào)用一些看起來(lái)像XOR、AES_encrypt、或自定義的復(fù)雜位運(yùn)算函數(shù)、或者有明顯的密鑰調(diào)度過(guò)程。也可以通過(guò)對(duì)比加密前后緩沖區(qū)的內(nèi)容變化來(lái)輔助判斷。HOOK技術(shù)選型為了動(dòng)態(tài)地分析或干預(yù)加密解密過(guò)程我們需要“鉤住”HOOK關(guān)鍵函數(shù)。這里有幾個(gè)主流選擇Detours/MinHook這是Windows平臺(tái)上成熟穩(wěn)定的API HOOK庫(kù)通過(guò)修改函數(shù)頭部的指令實(shí)現(xiàn)跳轉(zhuǎn)。它穩(wěn)定可靠適合HOOK游戲內(nèi)部函數(shù)或簡(jiǎn)單的系統(tǒng)API。Frida一個(gè)動(dòng)態(tài)插樁工具包它通過(guò)注入一個(gè)JavaScript運(yùn)行時(shí)到目標(biāo)進(jìn)程讓你能夠用JavaScript腳本動(dòng)態(tài)地HOOK函數(shù)、修改參數(shù)、監(jiān)視調(diào)用。它的優(yōu)勢(shì)是腳本化、跨平臺(tái)支持Android/iOS/Windows等動(dòng)態(tài)交互能力極強(qiáng)特別適合快速原型分析和復(fù)雜邏輯的跟蹤。自定義Inline Hook最底層的方式直接編寫(xiě)匯編代碼修改目標(biāo)函數(shù)開(kāi)頭的幾個(gè)字節(jié)跳轉(zhuǎn)到你自己的代碼。這種方式最靈活但實(shí)現(xiàn)復(fù)雜穩(wěn)定性需要精心處理如保存寄存器狀態(tài)、處理線(xiàn)程安全。在本實(shí)戰(zhàn)中為了平衡效率與靈活性我們可能會(huì)選擇MinHook來(lái)HOOK關(guān)鍵的內(nèi)部加密函數(shù)因?yàn)樗鼘?duì)Windows原生程序支持好同時(shí)在需要快速探索和動(dòng)態(tài)修改邏輯時(shí)可以輔助使用Frida進(jìn)行腳本化的偵查和測(cè)試。注意在實(shí)際操作中務(wù)必明確你的目的。如果是學(xué)習(xí)與研究應(yīng)在自己擁有合法權(quán)限的環(huán)境如自己編譯的測(cè)試游戲、明確允許安全研究的游戲中進(jìn)行。未經(jīng)授權(quán)對(duì)他人運(yùn)營(yíng)的游戲進(jìn)行逆向和HOOK可能違反用戶(hù)協(xié)議甚至法律。3. 工具鏈準(zhǔn)備與環(huán)境搭建工欲善其事必先利其器。一套順手的工具鏈能極大提升逆向分析的效率和體驗(yàn)。以下是針對(duì)本次UE4游戲安全實(shí)戰(zhàn)的核心工具推薦與配置要點(diǎn)。3.1 靜態(tài)分析工具代碼地圖繪制者靜態(tài)分析工具用于在不運(yùn)行程序的情況下反匯編二進(jìn)制文件分析其代碼結(jié)構(gòu)、函數(shù)調(diào)用關(guān)系和邏輯流。IDA Pro主力逆向工程領(lǐng)域的“瑞士軍刀”。它強(qiáng)大的反匯編引擎、圖形化視圖、交叉引用Xrefs功能和豐富的插件生態(tài)如FindCrypt用于識(shí)別加密常量使其成為靜態(tài)分析的不二之選。對(duì)于UE4游戲可以利用其識(shí)別C的RTTI運(yùn)行時(shí)類(lèi)型信息和虛表結(jié)構(gòu)幫助還原類(lèi)層次。Ghidra免費(fèi)替代/輔助美國(guó)國(guó)家安全局開(kāi)源的工具功能同樣強(qiáng)大完全免費(fèi)。它的反編譯能力有時(shí)比IDA更出色能生成可讀性更高的偽C代碼。可以配合IDA使用利用Ghidra進(jìn)行深入的代碼邏輯分析用IDA進(jìn)行快速的導(dǎo)航和標(biāo)注。關(guān)鍵插件與腳本UE4逆向輔助腳本GitHub上存在一些針對(duì)UE4引擎的IDA Python腳本或Ghidra腳本可以自動(dòng)解析GNames、GObjects等UE4全局表將內(nèi)存地址解析為可讀的類(lèi)名和函數(shù)名這是逆向UE4游戲的“開(kāi)圖”神器。FindCrypt / Signsrch這些插件/工具可以?huà)呙瓒M(jìn)制文件識(shí)別其中使用的加密算法如AES、RSA、MD5的常量S盒、魔數(shù)是快速定位加密函數(shù)的關(guān)鍵。3.2 動(dòng)態(tài)調(diào)試工具實(shí)時(shí)現(xiàn)場(chǎng)偵察兵動(dòng)態(tài)調(diào)試工具用于在游戲運(yùn)行時(shí)實(shí)時(shí)監(jiān)控和修改其內(nèi)存、寄存器、執(zhí)行流程。x64dbg / OllyDbg強(qiáng)大的Windows用戶(hù)態(tài)調(diào)試器。x64dbg對(duì)64位程序支持更好是現(xiàn)代游戲的首選。我們主要用它來(lái)下斷點(diǎn)、跟蹤執(zhí)行流、查看和修改內(nèi)存/寄存器、分析調(diào)用堆棧。在定位發(fā)包線(xiàn)程和加密函數(shù)時(shí)動(dòng)態(tài)調(diào)試是必不可少的。Cheat Engine雖然常被看作“修改器”但其內(nèi)存掃描、指針查找、代碼注入和調(diào)試器功能極其強(qiáng)大。對(duì)于快速定位游戲數(shù)據(jù)血量、坐標(biāo)和查找訪(fǎng)問(wèn)這些數(shù)據(jù)的代碼CE的效率無(wú)與倫比。它的“找出是什么訪(fǎng)問(wèn)了這個(gè)地址”功能是逆向數(shù)據(jù)流的神器。調(diào)試器配置要點(diǎn)隱藏調(diào)試器很多游戲帶有反調(diào)試保護(hù)會(huì)檢測(cè)調(diào)試器的存在。x64dbg和CE都有插件或選項(xiàng)可以隱藏自身如ScyllaHide插件。在開(kāi)始調(diào)試前務(wù)必先配置好反反調(diào)試措施否則游戲可能會(huì)崩潰或退出。符號(hào)文件如果游戲發(fā)布時(shí)附帶PDB調(diào)試符號(hào)文件某些開(kāi)發(fā)版或測(cè)試版可能泄露一定要加載它。這將直接顯示函數(shù)名和部分?jǐn)?shù)據(jù)結(jié)構(gòu)讓逆向難度直線(xiàn)下降。硬件斷點(diǎn)對(duì)于檢測(cè)非常敏感的函數(shù)軟件斷點(diǎn)INT 3可能被游戲檢測(cè)到。此時(shí)可以使用硬件斷點(diǎn)對(duì)執(zhí)行、讀寫(xiě)內(nèi)存設(shè)斷它們更難被檢測(cè)。3.3 網(wǎng)絡(luò)與HOOK工具數(shù)據(jù)流與邏輯攔截器Wireshark網(wǎng)絡(luò)協(xié)議分析的標(biāo)準(zhǔn)工具。配置過(guò)濾器只捕獲目標(biāo)游戲進(jìn)程的流量減少干擾。學(xué)會(huì)使用其“追蹤TCP流”功能可以重組完整的應(yīng)用層對(duì)話(huà)。Process Monitor微軟的Sysinternals工具套件之一。它可以實(shí)時(shí)監(jiān)控進(jìn)程的文件、注冊(cè)表、網(wǎng)絡(luò)和進(jìn)程活動(dòng)。有時(shí)可以用來(lái)輔助分析游戲啟動(dòng)時(shí)加載了哪些DLL、讀取了哪些配置文件可能包含密鑰。MinHook一個(gè)輕量級(jí)的HOOK庫(kù)。我們將它編譯成DLL注入到游戲進(jìn)程中用于HOOK我們找到的關(guān)鍵加密/解密函數(shù)。它的API簡(jiǎn)潔穩(wěn)定性高。Frida動(dòng)態(tài)插樁框架。我們需要在PC上安裝frida-tools并準(zhǔn)備一個(gè)JavaScript腳本。對(duì)于被分析的游戲進(jìn)程可以通過(guò)frida命令附著Attach上去或者將Frida注入到進(jìn)程中。隨后腳本中編寫(xiě)的HOOK邏輯就會(huì)生效。Frida非常適合快速測(cè)試HOOK點(diǎn)是否有效以及動(dòng)態(tài)地修改函數(shù)參數(shù)和返回值。3.4 開(kāi)發(fā)與輔助環(huán)境Visual Studio用于編譯我們自己的HOOK DLL、測(cè)試代碼。需要熟悉C和Windows API編程。Python用于編寫(xiě)自動(dòng)化腳本例如解析內(nèi)存數(shù)據(jù)、與調(diào)試器交互通過(guò)pykd或frida的Python綁定、批量處理分析結(jié)果。一個(gè)干凈的測(cè)試環(huán)境最好是虛擬機(jī)快照。因?yàn)槟嫦蚍治鲞^(guò)程中游戲崩潰、被檢測(cè)導(dǎo)致封號(hào)是家常便飯。一個(gè)可以快速恢復(fù)的測(cè)試環(huán)境能節(jié)省大量時(shí)間。4. 實(shí)戰(zhàn)演練定位線(xiàn)程發(fā)包與加密函數(shù)理論準(zhǔn)備就緒現(xiàn)在讓我們進(jìn)入實(shí)戰(zhàn)環(huán)節(jié)。假設(shè)我們分析的游戲叫“FantasyWorld”一個(gè)典型的UE4第三人稱(chēng)MMORPG。4.1 步驟一網(wǎng)絡(luò)行為建模與抓包首先正常啟動(dòng)游戲并登錄。打開(kāi)Wireshark開(kāi)始捕獲所有流量。在游戲中執(zhí)行幾個(gè)清晰的操作序列原地站立不動(dòng)捕獲“空閑狀態(tài)”流量。向前走幾步停止。打開(kāi)背包再關(guān)閉。對(duì)怪物進(jìn)行一次普通攻擊。停止抓包在Wireshark中設(shè)置過(guò)濾器ip.addr 游戲服務(wù)器IP。觀察不同操作對(duì)應(yīng)的數(shù)據(jù)包。你可能會(huì)發(fā)現(xiàn)即使站立不動(dòng)也有規(guī)律的小心跳包比如每2秒一個(gè)長(zhǎng)度固定。移動(dòng)時(shí)會(huì)連續(xù)產(chǎn)生多個(gè)小包長(zhǎng)度相近。打開(kāi)背包和攻擊時(shí)會(huì)產(chǎn)生一個(gè)或幾個(gè)明顯更大的數(shù)據(jù)包。將“移動(dòng)開(kāi)始”和“移動(dòng)結(jié)束”附近的數(shù)據(jù)包單獨(dú)保存出來(lái)。對(duì)比它們的內(nèi)容雖然大部分是亂碼但可能會(huì)發(fā)現(xiàn)包頭有規(guī)律比如前2個(gè)字節(jié)是包長(zhǎng)度接著2個(gè)字節(jié)可能是命令號(hào)Opcode。這個(gè)初步的協(xié)議結(jié)構(gòu)猜測(cè)需要后續(xù)在內(nèi)存中驗(yàn)證。4.2 步驟二使用Cheat Engine定位關(guān)鍵數(shù)據(jù)與代碼啟動(dòng)Cheat Engine附加到“FantasyWorld.exe”進(jìn)程。掃描玩家坐標(biāo)在游戲中讓角色移動(dòng)到某個(gè)特定位置如X100.0 Y200.0 Z50.0。在CE中使用“浮點(diǎn)數(shù)”類(lèi)型進(jìn)行首次掃描。然后移動(dòng)角色到另一個(gè)位置進(jìn)行“再次掃描”直到篩選出少量地址。通過(guò)“手動(dòng)添加地址”并修改數(shù)值在游戲中驗(yàn)證哪個(gè)地址真正控制角色位置。找到準(zhǔn)確的坐標(biāo)地址通常是三個(gè)連續(xù)的浮點(diǎn)數(shù)代表X, Y, Z。查找訪(fǎng)問(wèn)代碼在坐標(biāo)地址上右鍵“找出是什么訪(fǎng)問(wèn)了這個(gè)地址”。CE會(huì)顯示一個(gè)空白列表?;氐接螒蜃尳巧苿?dòng)。列表中會(huì)出現(xiàn)訪(fǎng)問(wèn)該地址的匯編指令。記錄下這些指令的地址如Game.exe123ABC。分析調(diào)用上下文在x64dbg中附加游戲進(jìn)程轉(zhuǎn)到上一步記錄的指令地址。在此處設(shè)置斷點(diǎn)?;氐接螒蛞苿?dòng)角色斷點(diǎn)命中?,F(xiàn)在查看調(diào)用堆棧。堆棧中會(huì)顯示是從哪個(gè)函數(shù)調(diào)用到這個(gè)寫(xiě)入坐標(biāo)的指令的。這個(gè)上層函數(shù)很可能就是處理玩家輸入鍵盤(pán)/鼠標(biāo)并更新坐標(biāo)、同時(shí)準(zhǔn)備發(fā)送移動(dòng)數(shù)據(jù)包的邏輯所在。這個(gè)函數(shù)就是我們接近網(wǎng)絡(luò)層的第一個(gè)重要跳板。4.3 步驟三追溯至網(wǎng)絡(luò)發(fā)送函數(shù)在找到的坐標(biāo)更新函數(shù)中我們需要尋找與網(wǎng)絡(luò)發(fā)送相關(guān)的調(diào)用。在x64dbg中單步執(zhí)行F7/F8這個(gè)函數(shù)觀察它是否調(diào)用了諸如send、WSASend、或UE4自身的網(wǎng)絡(luò)發(fā)送函數(shù)名稱(chēng)可能包含Send、Flush、Replicate等。一個(gè)更直接的方法是在Wireshark確認(rèn)的游戲發(fā)包時(shí)刻在x64dbg中對(duì)WSASend或sendto設(shè)置斷點(diǎn)。當(dāng)斷點(diǎn)觸發(fā)時(shí)仔細(xì)查看調(diào)用堆棧。忽略ws2_32.dll和ntdll.dll中的系統(tǒng)函數(shù)在游戲模塊如Game.exe中的、最靠近用戶(hù)邏輯的那個(gè)函數(shù)極有可能就是我們要找的“最終發(fā)包函數(shù)”。我們稱(chēng)它為SendPacket_Final。在SendPacket_Final函數(shù)內(nèi)部設(shè)置斷點(diǎn)再次觸發(fā)移動(dòng)操作。觀察函數(shù)參數(shù)通常第一個(gè)參數(shù)是Socket句柄第二個(gè)參數(shù)是指向發(fā)送緩沖區(qū)的指針第三個(gè)參數(shù)是緩沖區(qū)長(zhǎng)度。我們的核心目標(biāo)就是發(fā)送緩沖區(qū)和緩沖區(qū)長(zhǎng)度。4.4 步驟四識(shí)別加密邏輯現(xiàn)在我們?cè)赟endPacket_Final函數(shù)入口處斷住。查看指向緩沖區(qū)的指針比如在RCX寄存器或棧上。使用x64dbg的內(nèi)存窗口查看該緩沖區(qū)的內(nèi)容。記錄明文如果存在在函數(shù)入口處緩沖區(qū)里的數(shù)據(jù)可能已經(jīng)是加密后的。我們需要向上回溯。在調(diào)用SendPacket_Final之前數(shù)據(jù)是如何被填入這個(gè)緩沖區(qū)的回溯加密點(diǎn)從SendPacket_Final的調(diào)用者開(kāi)始逆向分析代碼。尋找對(duì)發(fā)送緩沖區(qū)進(jìn)行填充和處理的循環(huán)或函數(shù)調(diào)用。關(guān)鍵線(xiàn)索包括循環(huán)處理對(duì)緩沖區(qū)逐字節(jié)或逐塊進(jìn)行運(yùn)算的循環(huán)。常量參與代碼中引用了某些固定的數(shù)值數(shù)組可能是S盒或大整數(shù)可能是密鑰或初始化向量IV。特定函數(shù)調(diào)用調(diào)用了名稱(chēng)可疑的函數(shù)如sub_XXXXXX但其內(nèi)部有大量位運(yùn)算xor,shl,shr,ror或查表操作。對(duì)比驗(yàn)證在疑似加密函數(shù)調(diào)用前和調(diào)用后分別查看緩沖區(qū)內(nèi)容。如果調(diào)用后數(shù)據(jù)變得完全不可讀且長(zhǎng)度可能發(fā)生變化如AES加密后長(zhǎng)度對(duì)齊到16字節(jié)那這里就是加密函數(shù)。使用靜態(tài)分析輔助將疑似加密函數(shù)的地址記下來(lái)到IDA Pro中查看其反編譯代碼。結(jié)合FindCrypt插件的結(jié)果看是否能識(shí)別出標(biāo)準(zhǔn)的加密算法如AES、TEA、XXTEA等。UE4游戲也常用其自帶的加密庫(kù)或簡(jiǎn)單的XOR流加密。假設(shè)我們找到了一個(gè)函數(shù)它接收原始數(shù)據(jù)緩沖區(qū)和長(zhǎng)度以及一個(gè)密鑰指針輸出加密后的數(shù)據(jù)。我們將其命名為EncryptPacket。這個(gè)函數(shù)就是我們的核心目標(biāo)。5. HOOK實(shí)現(xiàn)攔截與操縱加密過(guò)程找到EncryptPacket函數(shù)后我們就可以通過(guò)HOOK來(lái)攔截它實(shí)現(xiàn)數(shù)據(jù)的動(dòng)態(tài)加解密、日志記錄或修改。這里我們以使用MinHook為例展示如何實(shí)現(xiàn)一個(gè)HOOK DLL。5.1 創(chuàng)建MinHook HOOK項(xiàng)目在Visual Studio中創(chuàng)建一個(gè)新的“動(dòng)態(tài)鏈接庫(kù)(DLL)”項(xiàng)目。引入MinHook庫(kù)從GitHub下載MinHook源碼將其添加到項(xiàng)目中或者通過(guò)vcpkg等包管理器安裝。編寫(xiě)HOOK代碼// dllmain.cpp #include Windows.h #include cstdio #include MinHook.h // 定義原始函數(shù)類(lèi)型。這需要根據(jù)逆向分析得到的函數(shù)簽名來(lái)精確匹配。 // 假設(shè) EncryptPacket 簽名為void EncryptPacket(void* pInput, size_t inputLen, void* pOutput, size_t* pOutputLen, const void* pKey); typedef void (__cdecl* tEncryptPacket)(void*, size_t, void*, size_t*, const void*); tEncryptPacket fpOriginalEncryptPacket nullptr; // 原始函數(shù)指針 // 我們的HOOK函數(shù) void __cdecl DetourEncryptPacket(void* pInput, size_t inputLen, void* pOutput, size_t* pOutputLen, const void* pKey) { // 1. 打印或保存原始輸入數(shù)據(jù)加密前的明文 FILE* fLog fopen(packet_log.txt, ab); if (fLog) { fprintf(fLog, [Pre-Encrypt] Len: %zu\n, inputLen); fwrite(pInput, 1, inputLen, fLog); fprintf(fLog, \n---\n); fclose(fLog); } // 2. 可選在這里修改pInput數(shù)據(jù)實(shí)現(xiàn)包修改 // 例如如果知道某個(gè)字段是坐標(biāo)可以在這里篡改它。 // 3. 調(diào)用原始函數(shù)執(zhí)行真正的加密 fpOriginalEncryptPacket(pInput, inputLen, pOutput, pOutputLen, pKey); // 4. 打印或保存加密后的輸出數(shù)據(jù) fLog fopen(packet_log.txt, ab); if (fLog pOutputLen) { fprintf(fLog, [Post-Encrypt] Len: %zu\n, *pOutputLen); fwrite(pOutput, 1, *pOutputLen, fLog); fprintf(fLog, \n\n); fclose(fLog); } } // DLL入口點(diǎn) BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { if (ul_reason_for_call DLL_PROCESS_ATTACH) { // 初始化MinHook if (MH_Initialize() ! MH_OK) { OutputDebugStringA(MinHook初始化失敗); return FALSE; } // 計(jì)算目標(biāo)函數(shù)地址。假設(shè)我們通過(guò)逆向分析得到 EncryptPacket 的偏移是 Game.exe 0x78ABCD // 這里需要獲取Game.exe的模塊基址 HMODULE hGame GetModuleHandleA(NULL); // 主模塊通常是游戲exe uintptr_t baseAddr (uintptr_t)hGame; uintptr_t targetAddr baseAddr 0x78ABCD; // 替換為你的實(shí)際偏移 // 創(chuàng)建HOOK if (MH_CreateHook((LPVOID)targetAddr, DetourEncryptPacket, (LPVOID*)fpOriginalEncryptPacket) ! MH_OK) { OutputDebugStringA(創(chuàng)建HOOK失敗); MH_Uninitialize(); return FALSE; } // 啟用HOOK if (MH_EnableHook((LPVOID)targetAddr) ! MH_OK) { OutputDebugStringA(啟用HOOK失敗); MH_Uninitialize(); return FALSE; } // 可以在這里打開(kāi)一個(gè)控制臺(tái)窗口方便調(diào)試僅Debug用 // AllocConsole(); freopen(CONOUT$, w, stdout); printf([HOOK DLL] 已成功注入并啟用HOOK。\n); } else if (ul_reason_for_call DLL_PROCESS_DETACH) { // 清理HOOK MH_DisableHook(MH_ALL_HOOKS); MH_Uninitialize(); } return TRUE; }關(guān)鍵點(diǎn)函數(shù)簽名tEncryptPacket的定義必須絕對(duì)準(zhǔn)確包括調(diào)用約定__cdecl,__stdcall,__fastcall、參數(shù)類(lèi)型和順序。一個(gè)字節(jié)的偏差都會(huì)導(dǎo)致棧損壞和崩潰。這需要從逆向分析中仔細(xì)確認(rèn)。5.2 注入DLL到游戲進(jìn)程編譯生成DLL后需要將其注入到游戲進(jìn)程。有多種方法使用注入工具如Extreme Injector、Process Hacker等圖形化工具。遠(yuǎn)程線(xiàn)程注入編寫(xiě)一個(gè)小的注入器程序使用CreateRemoteThread和LoadLibraryA將DLL路徑寫(xiě)入目標(biāo)進(jìn)程并加載。通過(guò)調(diào)試器加載在x64dbg中可以在游戲暫停時(shí)使用命令loaddll YourHook.dll來(lái)加載。注入成功后如果HOOK安裝正確游戲調(diào)用EncryptPacket時(shí)就會(huì)先執(zhí)行我們的DetourEncryptPacket函數(shù)。我們?cè)诤瘮?shù)中寫(xiě)入文件packet_log.txt就能捕獲到加密前后的數(shù)據(jù)包內(nèi)容。5.3 使用Frida進(jìn)行快速腳本化HOOK對(duì)于快速驗(yàn)證和動(dòng)態(tài)探索Frida非常高效。假設(shè)我們已經(jīng)通過(guò)逆向找到了EncryptPacket函數(shù)的地址偏移0x78ABCD。編寫(xiě)Frida腳本(hook_encrypt.js)// hook_encrypt.js const baseAddr Module.getBaseAddress(Game.exe); const encryptFuncAddr baseAddr.add(0x78ABCD); Interceptor.attach(encryptFuncAddr, { onEnter: function(args) { // args[0], args[1]... 對(duì)應(yīng)函數(shù)的第一個(gè)第二個(gè)...參數(shù)根據(jù)調(diào)用約定調(diào)整 // 假設(shè)是__cdecl參數(shù)都在棧上這里需要根據(jù)實(shí)際分析調(diào)整。 // 更通用的方法是使用Frida的API讀取棧上的參數(shù)或者先通過(guò)調(diào)試確定參數(shù)位置。 console.log([] EncryptPacket called!); console.log( Input Buffer ${args[0]}); console.log( Input Length: ${args[1]}); // 將輸入緩沖區(qū)內(nèi)容打印為hex const inputBuf args[0]; const inputLen args[1].toInt32(); console.log(hexdump(inputBuf, { offset: 0, length: inputLen, header: true, ansi: true })); }, onLeave: function(retval) { // 函數(shù)返回后的處理 console.log([-] EncryptPacket returned.); } });執(zhí)行腳本在命令行中確保游戲進(jìn)程正在運(yùn)行然后執(zhí)行frida -p 游戲PID -l hook_encrypt.js或者使用-n參數(shù)按進(jìn)程名附加。Frida會(huì)注入腳本并在函數(shù)被調(diào)用時(shí)在控制臺(tái)打印信息。你可以快速修改腳本嘗試讀取或修改參數(shù)而無(wú)需重新編譯DLL。6. 逆向分析中的密碼學(xué)識(shí)別技巧在靜態(tài)分析中快速識(shí)別加密算法能節(jié)省大量時(shí)間。以下是一些常見(jiàn)算法的特征算法關(guān)鍵常量/特征常見(jiàn)場(chǎng)景AES查找S盒Substitution Box常量。在IDA中FindCrypt插件可以識(shí)別。常量數(shù)組通常以63 7C 77 7B F2 6B 6F C5...開(kāi)頭。還有輪常數(shù)Rcon。網(wǎng)絡(luò)協(xié)議加密、文件加密??赡苁茿ES-128/192/256。TEA/XTEA/XXTEA魔數(shù)0x9E3779B9黃金比例的倒數(shù)。代碼結(jié)構(gòu)簡(jiǎn)單通常有多次循環(huán)如32輪每輪進(jìn)行移位、加、異或操作。游戲數(shù)據(jù)包加密、資源文件加密。因其實(shí)現(xiàn)簡(jiǎn)單、速度較快。RC4密鑰調(diào)度算法KSA和偽隨機(jī)生成算法PRGA。特征是兩個(gè)循環(huán)變量i和j以及對(duì)一個(gè)256字節(jié)的S盒數(shù)組進(jìn)行交換操作。流加密曾用于TLS、WEP等。Base64編碼表通常是ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/。解碼時(shí)會(huì)有判斷字符是否為填充符的邏輯。不是加密是編碼。常用于在協(xié)議中傳輸二進(jìn)制數(shù)據(jù)。自定義XOR可能使用一個(gè)固定密鑰字節(jié)或密鑰流與明文逐字節(jié)異或。在IDA中可能表現(xiàn)為一個(gè)簡(jiǎn)單的循環(huán)循環(huán)體內(nèi)是xor [rdirax], cl之類(lèi)的指令。最簡(jiǎn)單的混淆常見(jiàn)于對(duì)強(qiáng)度要求不高的通信或資源保護(hù)。實(shí)操心得在UE4游戲中除了標(biāo)準(zhǔn)的加密庫(kù)開(kāi)發(fā)者也可能使用引擎自帶的一些加密工具類(lèi)例如FAES、FEncryption等。在IDA中可以搜索字符串引用查找“Encrypt”、“Decrypt”、“AES”、“Key”等關(guān)鍵詞有助于快速定位相關(guān)函數(shù)。另外注意觀察游戲初始化階段密鑰很可能從某個(gè)配置文件、注冊(cè)表或通過(guò)網(wǎng)絡(luò)交換獲得并存儲(chǔ)在全局變量中。通過(guò)HOOK加密函數(shù)打印出傳入的密鑰指針內(nèi)容有時(shí)可以直接拿到密鑰。7. 常見(jiàn)問(wèn)題、反調(diào)試對(duì)抗與排查技巧在實(shí)際操作中你幾乎一定會(huì)遇到游戲的反調(diào)試和反作弊機(jī)制。下面是一些常見(jiàn)問(wèn)題及應(yīng)對(duì)策略。7.1 游戲崩潰或無(wú)法啟動(dòng)可能原因1HOOK函數(shù)簽名錯(cuò)誤。這是最常見(jiàn)的原因。調(diào)用約定、參數(shù)個(gè)數(shù)或類(lèi)型不匹配導(dǎo)致棧不平衡。排查在調(diào)試器中在原始函數(shù)入口和你的Detour函數(shù)入口都設(shè)斷點(diǎn)單步跟進(jìn)對(duì)比棧指針RSP的變化。確保onEnter和onLeave時(shí)棧是平衡的。仔細(xì)核對(duì)IDA反編譯出的函數(shù)原型??赡茉?DLL注入被檢測(cè)。游戲會(huì)檢查進(jìn)程內(nèi)異常的DLL模塊。對(duì)策使用更隱蔽的注入技術(shù)如線(xiàn)程劫持、APC注入?;蛘邍L試將HOOK代碼直接寫(xiě)入游戲進(jìn)程內(nèi)存通過(guò)WriteProcessMemory并創(chuàng)建遠(yuǎn)程線(xiàn)程執(zhí)行而不使用獨(dú)立的DLL模塊??赡茉?完整性檢查。游戲會(huì)對(duì)關(guān)鍵代碼段如加密函數(shù)進(jìn)行CRC或哈希校驗(yàn)發(fā)現(xiàn)被修改后崩潰。對(duì)策尋找校驗(yàn)函數(shù)并繞過(guò)它或者使用更底層的硬件斷點(diǎn)Hardware Breakpoint來(lái)代替修改代碼的HOOK。硬件斷點(diǎn)不會(huì)改變?cè)噶睢?.2 斷點(diǎn)不觸發(fā)或游戲異常退出可能原因反調(diào)試檢測(cè)。游戲可能使用了IsDebuggerPresent、CheckRemoteDebuggerPresent、NtQueryInformationProcess等API或通過(guò)Trap Flag、INT 3掃描等方式檢測(cè)調(diào)試器。對(duì)策使用插件在x64dbg中啟用ScyllaHide插件并針對(duì)游戲進(jìn)程選擇合適的隱藏配置文件。手動(dòng)繞過(guò)在調(diào)試器中找到調(diào)用這些檢測(cè)API的地方修改其返回值通常讓返回0表示沒(méi)有調(diào)試器。時(shí)間差檢測(cè)有些游戲會(huì)測(cè)量?jī)蓚€(gè)操作之間的時(shí)間如果因?yàn)閱尾秸{(diào)試導(dǎo)致時(shí)間過(guò)長(zhǎng)就判定為調(diào)試。遇到這種情況需要找到檢測(cè)點(diǎn)并繞過(guò)或者盡量避免在檢測(cè)代碼處單步。7.3 HOOK后數(shù)據(jù)包異常服務(wù)器斷開(kāi)連接可能原因1加密/解密過(guò)程被破壞。你的Detour函數(shù)修改了數(shù)據(jù)或密鑰但沒(méi)有按照游戲原有的邏輯處理導(dǎo)致生成的密文服務(wù)器無(wú)法解密。排查確保你的Detour函數(shù)在調(diào)用原函數(shù)前后緩沖區(qū)內(nèi)容的變化符合預(yù)期??梢韵葘?shí)現(xiàn)一個(gè)“只記錄、不修改”的HOOK確認(rèn)能正常工作后再?lài)L試修改數(shù)據(jù)??赡茉?數(shù)據(jù)包校驗(yàn)。除了加密數(shù)據(jù)包可能還有CRC32、MD5或自定義的校驗(yàn)和。你修改了明文數(shù)據(jù)后如果沒(méi)有同步更新校驗(yàn)和服務(wù)器校驗(yàn)會(huì)失敗。對(duì)策在逆向分析時(shí)注意尋找在加密函數(shù)之后是否還有對(duì)數(shù)據(jù)包進(jìn)行哈?;蛐r?yàn)計(jì)算的代碼。需要一并HOOK并修正校驗(yàn)值。7.4 找不到加密函數(shù)或函數(shù)地址偏移不穩(wěn)定可能原因ASLR地址空間布局隨機(jī)化。每次游戲啟動(dòng)模塊的加載基址都會(huì)變化導(dǎo)致硬編碼的偏移失效。對(duì)策使用特征碼搜索Pattern Scan來(lái)定位函數(shù)。在IDA中分析函數(shù)找到一段獨(dú)一無(wú)二的字節(jié)序列特征碼并避開(kāi)直接指針因?yàn)橹羔樦禃?huì)變。在你的HOOK DLL中啟動(dòng)時(shí)動(dòng)態(tài)搜索這段特征碼計(jì)算出函數(shù)的實(shí)際地址。這是制作穩(wěn)定HOOK的必備技能。示例概念uintptr_t FindPattern(const char* module, const char* pattern, const char* mask) { // 實(shí)現(xiàn)一個(gè)特征碼掃描函數(shù)在模塊內(nèi)存中搜索pattern // ... } // 在DllMain中 uintptr_t encryptedFuncAddr FindPattern(Game.exe, \x48\x89\x5C\x24\x10\x48\x89\x74\x24\x18\x55\x57\x41\x56, xxxxxxxxxxxxxx); if (encryptedFuncAddr) { // 創(chuàng)建HOOK }7.5 Frida腳本無(wú)法附加或瞬間被游戲檢測(cè)到可能原因Frida的默認(rèn)注入方式如frida-server比較明顯容易被游戲的反作弊系統(tǒng)如EAC、BattlEye檢測(cè)。對(duì)策嘗試使用frida的--no-pause等選項(xiàng)或使用更低調(diào)的注入技術(shù)。在游戲完全啟動(dòng)并進(jìn)入主菜單后再?lài)L試附加。對(duì)于防護(hù)極強(qiáng)的游戲Frida可能不適用需要回歸到傳統(tǒng)的調(diào)試器和手動(dòng)匯編分析。逆向分析是一場(chǎng)與游戲保護(hù)機(jī)制的持續(xù)博弈。保持耐心從簡(jiǎn)單的、保護(hù)較弱的游戲開(kāi)始練習(xí)逐步積累經(jīng)驗(yàn)和工具鏈。每一次成功的分析和HOOK都會(huì)讓你對(duì)Windows系統(tǒng)機(jī)制、x64匯編、編譯器和游戲引擎的理解更深一層。記住核心目標(biāo)始終是學(xué)習(xí)和理解技術(shù)原理將這些知識(shí)用于構(gòu)建更堅(jiān)固的防御而非破壞。