網路遊戲逆向分析-7-人物背包遍歷
前面我們找到了使用人物背包的函數調用,但是並不完美,因為我們需要處理一些參數,比如說用背包第二個物品,就需要push 1像數組一樣從0 1 2 4 5 6這樣來數,這對於程式設計師來說沒問題,但是對於使用者肯定是不好的,所以我們需要得到背包的內容,來遍歷背包,再根據需求,比如說喝紅藥水來封裝一下。所以我們就需要人物背包里的內容。
實操:
搜索地址
先用CE來搜索數據來找到使用物品的時候的一個調用:
通過篩選,比如說先搜索18,然後吃掉變17後又搜索17,這樣直到唯一確定。
這裡比較簡單嗷,一下子就找到了。
地址:4C13D4B0 值:17
探索基址
由於pe文件的特性,這個程式肯定是會有一個寫死的地址來使用的,通過這個地址我們可以利用偏移等等來得到程式運行後背包所存放的地址。
從正常開發的角度來思考,這個肯定是把人物背包的東西存放在了某一個地址,然後查看人物背包的時候又給顯示出來,所以這裡直接給這個地址下一個硬體訪問的地址就可以知道是什麼訪問了這個物品的地址空間了。
一看是停在了這裡:
4C13D4B0 17
00A4AFAB | 837E 70 01 | cmp dword ptr ds:[esi+70],1 | 最初訪問背包第一個物品的彙編指令
然後問題就出現了,這裡會一直停下來,如果你打上斷點後繼續運行,會繼續停在這條指令,而且你想返回上一層函數,採用Ctrl+F9和F8的指令也返回不了,還是會跳轉到這裡,那麼這裡就很蹊蹺了,通過我不停的查看這個 esi+70的地址,可以看到它對於的內容就是你物品欄裡面的物品數量,比如說:
我這裡的物品是這樣的
然後斷下來查看esi+70後:
翻譯成10進位就是先4後20然後17,就和物品欄裡面的物品數量是一一對應的。
所以這裡我猜測,它是打開物品欄之後,一個循環一直讀取物品裡面的所有數量。
然後這裡有一個跳轉,就是大於1之後會跳轉,我們把它修改一下看看會發生什麼:
修改成直接跳轉不管1了的時候可以看到是只顯示了物品並沒有顯示物品的數量。
那麼這裡就很明顯了,這條cmp指令就是和1作判斷,如果大於1就顯示有多少,等於1就不顯示了。
這條指令前面有3個函數call,一個很明顯的mov esi,eax,就很有可能是他們修改了esi的值,那麼就從下往上觀察有沒有修改:
對於函數的話可以在進入函數前後通過觀察esi暫存器來區別。
然後4這個mov esi,eax根本斷不下來,就肯定不是這個地方了。
再往上:
可以看到有兩個跳轉,很有可能是跳轉的原因,直接跳過了mov esi,eax。分別給這兩個je打斷點觀察:
這兩個不管那個跳轉只有小跳轉A4A56會跳轉,而大的je A4B251不會,很可能就是拿來判斷程式碼邏輯把
而且通過第一個je A4B254大跳轉最後還是會回到小跳轉je A4AF56
所有這咯從 00A4AF47到00A4AF56中間的就不用考慮了,就繼續往上看:
這條指令是在訪問背包時完全可以斷下來的,而且是直接對esi這個暫存器進行了修改的。
mov esi,dword ptr ss:[esp+84]
前面我們提到過,跟esp有關的基本上都是函數參數,或者臨時變數。這裡方法就不細講了,可以參考前面的部落格。
這裡的esp+84是這個函數的函數參數,call A4AE00
通過值的比對,這裡應該是最後push進來的一個參數,因為正好和棧是對著的。但是奇怪的是這個函數如果就這看對應的最後一個傳參是一個push 0,再往上看看就可以看到其實前面有一個jmp跳轉:
而且這個跳轉是生效的,那麼參數就應該是前面的push edx,ecx,ebx,eax了。
eax的值也是我們想要的,那麼可以肯定是這裡了。就往上找eax:
通過我的分析;
這個函數會修改eax的值。
這是這段函數的邏輯:
通過這三個跳轉,大致上可以分析清楚了,就是比較eax是啥,然後如果不是要的就清零後返回了。
通過觀察,這個函數的參數就是從0開始然後到某一個值結尾,猜測這個值應該是數組的範圍把,應該就是遍曆數組然後輸出的函數
那麼這個函數的功能大概明白了,重新分析下這個函數:
test eax,eax 然後有個jl jl是小於跳轉,就肯定不會跳轉了唄,下面是jge相等跳轉,應該意思是等於某個值之後就跳轉了應該是數組的最大範圍就直接跳出去,然後cmp [esp+8],0這個肯定是一樣的,因為這個函數前面是push 0,
mov ecx,dword ptr ds:[ecx+24]
lea ecx,dword ptr ds:[ecx+eax*4]
mov eax,dword ptr ds:[ecx]
剩下的應該好理解,把ecx+24的給ecx然後ecx通過eax偏移,後賦值給ecx。所以這裡我們需要往上找ecx的值。
首先可以先偷懶看看每次斷在這裡的ecx是不是一個固定值,或者重啟遊戲看這個地址會不會改變,這裡我自己測試是並不是一個固定的值,所以還得往上找。那麼現在的關鍵就變成了找ecx的值了。
因為這個函數內部沒有涉及到ecx的值,所以得跳出這個函數往上找:
這裡有三個,但是我們還得考慮會不會跳到這裡,這裡我們可以利用條件斷點,或者說看程式碼邏輯執行下來會不會執行到背包的程式碼邏輯。由於前面我沒有計數這個值,我就用後面一種看程式碼邏輯來處理了,大家可以通過前面暫存器的積累來通過條件斷點判斷。
通過我的測試,第一個
mov ecx,dword ptr ss:[esp+2c]
這個彙編指令是會執行的。而且這裡又扯到了esp了,辦法就不多說了,參考前面:
esp+2c離函數返回地址太遠了,很有可能就是一個臨時變數。就直接給這個esp+2C打一個寫入斷點,看看是怎麼賦值的。
但是問題是很多地方都會修改這個值,所以為了我們的目的,我直接給函數的開頭打個斷點,斷到函數的時候再給這個地址打一個硬體斷點就可以看到在函數裡面是誰修改了這個esp+2C的值:
然後就破案了:
是這裡的mov dword ptr ss:[esp+34],ecx修改了esp+2C的值。可以再用一個條件斷點來判斷一下。
mov dword ptr ss:[esp+34],ecx
然後往上找ecx:
這裡
lea ecx,dword ptr ds:[edi+8] 又修改了ecx,又往上找edi
但是往上的一些又修改edi的指令,是完全斷不下來的,這個通過條件斷點或者程式碼邏輯判斷斷不下來就說明在背包的情況下沒有使用這個,就繼續一直往上找,直到有一個:
這裡是完全可以斷下來:
00A4B35F | E8 2CAAA0FF | call xajh.455D90 |
00A4B364 | 8B40 08 | mov eax,dword ptr ds:[eax+8] |
00A4B367 | 8B78 14 | mov edi,dword ptr ds:[eax+14] |
而且eax這個關鍵暫存器的值,在這個函數 call 455D90裡面有賦值為一個定值也就是所謂的基址,那麼我們就是找到內容了,由於一步一步找的時候沒有記錄值,這樣的話我們只能從這個基址往下找一直到最後到背包的地址,然後來確定通過基址如何偏移才能達到最後的背包的地址。
這個函數的所有je都不會執行。
最後我把所有關鍵的彙編指令整理出來:
mov eax,dword ptr ds:[1597F20]
mov eax,dword ptr ds:[eax+30]
mov eax,dword ptr ds:[eax+90]
mov eax,dword ptr ds:[eax+8]
mov edi,dword ptr ds:[eax+14]
lea ecx,dword ptr ds:[edi+8]
mov dword ptr ss:[esp+34],ecx
//[esp+34] == esp+2C 0.0
mov ecx,dword ptr ss:[esp+2C]
mov ecx,dword ptr ds:[ecx+24]
lea ecx,dword ptr ds:[ecx+eax*4]
mov eax,dword ptr ds:[ecx]
//eax == esp+84
mov esi,dword ptr ss:[esp+84]
cmp dword ptr ds:[esi+70],1
[[[[[[[1597F20]+30]+90]+8]+14]+8+24]+eax*4]+70
最後這個:[[[[[[[1597F20]+30]+90]+8]+14]+8+24]+eax*4]+70 就是通過偏移得到的背包了,這裡eax可以改,比如從0-數組最大值:
這裡就是背包的第一個數據
這裡就是第二個。
總結
這個就比較像是找基址了,用到了很多經驗相關的,比如說硬體訪問寫入斷點,比如說esp暫存器拿來分析函數參數或者臨時變數。