網絡游戲逆向分析-2-搜索基礎數據(血量)
人物屬性是一個游戲的基礎數據,所有的游戲架構都得在基礎數據的基礎上,所以搜索到基礎數據是重中之重。
這里首先來分析人物屬性的氣血值這個東西。
和單機游戲搜索數據是一樣的,同樣是采用CE,OD等工具來進行搜索。
搜索血量
首先采用CE,通過數據變化來定位該數據存放的內存的位置。
首先搜索初始血量,1635:
值有很多,再在被怪物攻擊血量減少后再次搜索:
現在是1433,再次搜索:
結果就只剩下2個了。
采用內置的游戲技能,按Z加血再來分析:
然后通過仔細觀察分析,第一個地址的值是再人物血量減少后直接扣除,而第二個值確實緩慢扣除,但是最后的結果都是一樣的,所以肯定第一個地址更符合我們的需求。
但是,這個地址肯定不是一個全局變量的地址,關於全局變量前面的PE有提過,就是一個固定的地址,這個地址可能是一個堆地址,在程序重新使用后就會變的一個地址,對我們來說意義不大,但是可以通過它來找到全局變量的地址。
如何找到全局變量的地址
可以根據找到的這個臨時地址來通過打一個訪問或者寫入的斷點來尋找對應的值。
這里采用打一個訪問斷點來尋找。
采用xdbg來調試該進程,訪問該內存地址來觀察:
首先給前面找到的地址下一個斷點,注意盡量不要采用內存斷點,因為內存是按頁來處理的,有可能一個斷點會導致多個斷點出現,盡量下硬件斷點:
下一個硬件訪問的四字節斷點。
然后再讓程序跑起來:
這里很明顯地停止到了剛剛硬件斷點的位置,硬件斷點和內存斷點的區別就是,硬件斷點是在執行完內容后才停下來,而內存斷點就是在斷點的位置停下來不執行,所以這里停下來的位置應該是當前EIP的上一條指令:
也就是這一條指令:
mov edi,dword ptr ds:[esi+1C]
這里的esi+1C的值=5F499D74 //先將其記錄下來
然后把原來的硬件斷點給它取消掉,再給該mov edi,dword ptr ds:[esi+1c]按F2下一個int3斷點。
但是可以發現運行后是一直停在斷點處,於是就可以下一個條件斷點,條件是該指令的esi+1C的值等於前面我們找到的地址值5F499D74:
然后選擇編寫:
由於中途我的游戲崩了,所以數據不對要重新來,但是重新一個一個截圖又太麻煩了,我就不把前面的截圖修改了,大家自行修改吧
為了防止游戲不崩潰,大家再使用完斷點后最后把游戲繼續運行下去,不然可能會崩潰,比如:判斷你掉線了什么之內的。
針對這條指令往上找,可以很明顯的看到前面有一條指令:
mov esi,ecx //改變了esi的值
於是這里再給該指令下一條條件斷點,為的是esi+1C的值還是前面我們找到的血量的地址的值:
然后這里也可以停止到該斷點處,說明沒有任何問題,然后繼續往上面看:
這里很明顯地可以看出來,是一個標准的函數的結構,先提升棧空間然后再處理,而且前面還有int3,一個正常的函數段里面是不會出現int3這種東西的,int3意味着是一個斷點了。還可以通過給這個int3下面的第一條指令下斷點,查看到該指令的時候堆棧是什么樣子的是不是堆棧的下一個內容是一個返回地址。(因為call一個函數時,會把下一條執行的地址壓入到堆棧)。
通過堆棧的返回地址得到函數的地址,然后來觀察這個ecx是怎么來的:
很明顯就是這一段內容:
這段代碼,在調用函數之前修改了ecx的值:
lea ecx,dword ptr ds:[esi+2A0]
這里的意思就是ecx = esi+2A0
添加一個條件斷點來判斷是否這條指令是否是我們要找的:
發現是可以斷下來的,那就沒有任何問題。
然后繼續往上找esi的值:(找了很久才有的結果)
同樣的給這個函數體的最上面打一個條件斷點,因為這里的Mov esi,ecx之前沒有修改過ecx的值,所以肯定是在前面。是可以斷下來的說明這里依然沒有問題。
訪問返回地址的值:
這里肯定就是這條指令的問題了,同樣的加一個條件斷點來判斷找的地址是否正確,經過我的演算是正確的。
然后繼續往上探索esi的值:
這里表明了esi來源與ecx,繼續用前面的辦法給該函數體的第一條指令下條件斷點:
斷點成功,說明是找對了地方的。然后根據返回值又跳出來到上一個函數:
一看就是這里:這里又是ecx和esi相互搗騰。然后繼續往上找esi的值:
又往上找了很久才找到esi相關的:mov esi,ecx。
同樣的給這個函數體打條件斷點判斷是不是,如果是就根據棧空間返回到調用函數體的代碼里面繼續找。
這里我直接跳轉到調用函數體的代碼里面:
這里需要注意的是,由於前面有一個ret的返回指令,所以這個整個函數代碼體肯定不能直接往上找,只能在ret和call之間找,因為這里就是該函數體了。
所以這里直接給ret的下一條指令打條件斷點看是否符合,然后再通過棧回到上一個。
這里得到的mov ecx,ebx再進行條件斷點測試,這里我測試是正確的。然后往上找ebx的內容。
mov ebx,dword ptr ds:[ebx+eax]
對這里的[ebx+eax]添加條件斷點來判斷是否找對位置,這里我驗證出來是找對了的。
這里稍微有一點區別,因為是涉及了兩個寄存器ebx和eax,通過多次運行發現每次運行到這里的時候ebx和eax的值都是固定不變的。
00ED3604 | 8B1C03 | mov ebx,dword ptr ds:[ebx+eax]
//| eax == 709A45C0 ebx == 10
這個可以猜得到,eax是一個地址,然后ebx是一個偏移地址。
然后eax又來自於mov eax,dword ptr ds:[esi+c8],通過觀察發現esi是不變的是一個定值。
然后繼續找這個esi:
直到這個函數快結束的時候才找到:
00ED349E | 8BF1 | mov esi,ecx |
同樣添加條件斷點來驗證。驗證OK。
通過棧返回調用函數來繼續找ecx的值:
一來就找到ecx的變化了,通過條件斷點驗證是正確的,繼續往上找esi:
直到最上面才看到esi和ecx,又得到了是ecx給esi
mov esi,ecx
然后通過函數返回地址再找到上一層:

mov ecx,esi //ecx==esi
經過驗證后是正確的,然后繼續往上找esi對應的值:
添加驗證后,又是ecx,然后得往上再返回函數再繼續找:
ecx,又來源與esi。
反正就是這樣一直找一直找:
直到最后的值是一個立即數:
總結
通過CE工具來篩選,篩選到一個臨時的變量存儲空間,然后依據該內容給調試器打斷點,最好是硬件寫入斷點,然后再根據該斷點的內容,來通過各自寄存器往上找,直到找到一個固定值,一個立即數,一個不會變的值,然后再把偏移值+上作為一個公式來訪問。