網路遊戲逆向分析-2-搜索基礎數據(血量)
網路遊戲逆向分析-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工具來篩選,篩選到一個臨時的變數存儲空間,然後依據該內容給調試器打斷點,最好是硬體寫入斷點,然後再根據該斷點的內容,來通過各自暫存器往上找,直到找到一個固定值,一個立即數,一個不會變的值,然後再把偏移值+上作為一個公式來訪問。