本次實驗內容:通過逆向分析植物陽光數量的動態地址找到陽光的基址與偏移,從而實現每次啟動游戲都能夠使用基址加偏移的方式定位陽光數據,最后我們將通過使用C語言編寫通用輔助實現簡單的無限陽光外掛,在教程開始之前我們先來說一下為什么會有動態地址與基址的概念!
大部分編程語言都會有局部變量和全局變量,相對於局部變量來說是在游戲運行后動態分配的默認由堆棧存儲,而全局變量則是我們所說的基址其默認存儲在全局數據區,全局數據區里面的數據則是在編譯的時候就寫入到程序里了,所以不會變化,而游戲的開發都會使用面向對象技術,我們可以推測游戲中的陽光很可能就是類中的一個數據成員,而數據成員的地址就是通過new動態分配的,如下代碼:
#include <stdio.h>
class SunClass{
public:
int SunTime;
int SunValue;
int SunAttr;
};
int main()
{
SunClass *Sun=new SunClass;
Sun->SunValue=100;
printf("SunValue: %d
",Sun->SunValue);
return 0;
}
如上代碼定義了SunClass
類,在主函數中我們為Sun實例指針動態分配了內存,分配的內存存儲在棧中,而棧地址每次都會發生變化,所以分配的內存地址是不固定的,從而導致陽光的地址是動態的
好!現在我們就進入正題,開始挖掘游戲數據,先從最簡單的陽光地址找起來吧,首先你需要運行游戲並附加植物大戰僵屍進程,然后我們開啟新的游戲,首次掃描我們先來遍歷4字節的50,也就是搜索當前陽光的數量,當然你也可以嘗試搜索金錢數量等,道理都是一樣的,這里就拿陽光的搜索方法作為演示目標。
接着我們需要讓陽光發生變化,這樣才可以讓我們繼續更加精確的確定這個局部變量在內存中的地址是多少,此處我手動種植了一顆向日葵則陽光變為了0,我們就輸入0然后再次掃描,由於這款游戲比較簡單,基本上經過兩次篩選就能定位到陽光的內存地址了,在遍歷一些大型游戲的時候,讀者應該有耐心,經過多次篩查直到最終找到正確的(動態)內存地址為止。
觀察上圖13C66448
地址,會發現CE顯示該地址是一個灰色地址,在CE中灰色就表示是動態地址而綠色則表示基址,此處的動態地址則相當於我們上方代碼中給一個類動態new開辟的內存空間的首地址,由於該地址是系統為我們動態開辟的,所以每次重啟游戲該地址都會發生變化,為了能夠制作外掛我們必須要找到陽光的基址。
我們繼續將地址欄中的地址雙擊加入到最底部的地址欄,然后在地址上右鍵,選擇查找改寫地址
當我們選擇查找改寫地址的時候,其實CE就為我們在這個地址上下了硬件寫入斷點,這個下斷點的功能我們同樣可以使用X64dbg來完成,此時回到游戲等待陽光出現並點擊陽光,則此時會出現以下匯編指令。
上圖中我們可以得知add [eax+5560],ecx
這條指令是加法運算,最右側ECX里面就是我們當前需要增加的陽光數,將ECX中的陽光數賦值給[eax+5560]
這個內存地址,那么我們的陽光就會增加,此時我們需要知道EAX寄存器指向的地址是多少,CE中已經為我們分析出了EAX寄存器當前值是13C60EE8
我們此時需要記下它的一級偏移5560
,然后去搜索13C60EE8
這個內存地址。
上圖搜索結果可以看到有非常多的數據,那我們該如何判斷應該選擇那一個呢?這里就是一個技巧的問題了,我們需要盡量選擇地址不同的,比如標紅處的位置是我們重點關注的對象,其中13C60EE8這個內存地址就相當於我們SunClass
類實例化的基地址,而5560則是陽光在類中的偏移地址,此處我們需要分析誰給EAX賦值了,直接在00FE82E8
右鍵,查找訪問地址,然后會看到以下截圖內容:
此處會出現一大堆指令,這里也需要一個遍歷技巧,我們可以排除CMP之類的對比指令,因為我們是增加陽光所以不可能出現對比的代碼,此外我們需要關注操作數左側是EAX的,因為我們要找的是誰給EAX賦值的,我們選擇mov eax,[ecx+00000768]
這條匯編指令,然后發現二級偏移是768
,我們繼續查找誰給ECX賦值的,這里直接記下ECX寄存器中的地址00FE7B80
繼續搜索十六進制數00FE7B80
如下搜索結果可以看到有綠色的地址,這些綠色的地址都屬於全局變量,到此說明我們已經找到了這個陽光的基地址了,這里我們可以隨意選擇綠色的地址作為基址使用,此處我選擇的是006A9EC0
來當作基址使用,前面找到的地址每次啟動游戲都會發生變化,而這個基址是永遠不會變化的。
最后我們通過查找到的基址與偏移相加的形式,就可以定位到動態地址了,具體公式應該是陽光= [[[006a9ec0]+768]+5560]
,我們可以直接在CE中添加這個指針,用於進行測試,如下圖所示:
最后我們再來總結一下查找思路,其基址查找過程可以描述為以下流程,如果用正向的思路來理解的話應該從后向前來看,會發現正向思路來看會非常的清晰,而我們找基址則是從逆向的角度來分析,也就是從前向后來理解這個過程。
已知陽光的動態地址ECX的值就是增加的陽光 將增加值ECX賦值給 [eax+5560] 我們就得到了陽光
00430A11 - 01 88 60550000 - add [eax+00005560],ecx <<
我們需要繼續找出EAX是多少? 由第二條匯編指令可知EAX的值來自於[ecx+768]這個地址
0045B6FD - 8B 81 68070000 - mov eax,[ecx+00000768] <<
最后我們繼續跟隨查找ECX里面存儲的數據得到 [006A9EC0] 該數據明顯屬於全局數據區
00467B00 - 8B 0D C09E6A00 - mov ecx,[006A9EC0] <<
最后總結出定位靜態基址公式 【陽光= [[[006a9ec0]+768]+5560]】
通過編程的方式讀取並修改我們的陽光數量,如下這樣一段代碼,它可以實現讀取動態地址並修改陽光數量。
#include <iostream>
#include <Windows.h>
int GetDyAddr(int Pid,int Base, int Offset[], int len)
{
int temp;
HANDLE Process;
Process = OpenProcess(PROCESS_ALL_ACCESS, false, Pid);
ReadProcessMemory(Process, (LPVOID)Base, &temp, 4, NULL);
for (int i = 0; i < len; i++)
{
if (i == len - 1)
temp += Offset[i];
else
ReadProcessMemory(Process, (LPVOID)(temp + Offset[i]), &temp, 4, NULL);
}
return temp;
}
int main()
{
int base;
int offset[3];
int PID = 5772;
base = 0x006a9ec0;
offset[0] = 0x768;
offset[1] = 0x5560;
int addr = GetDyAddr(PID, base, offset, 2);
printf("進程地址:%x
", addr);
HANDLE Process = OpenProcess(PROCESS_ALL_ACCESS, false, PID);
WriteProcessMemory(Process, (LPVOID)addr,&PID,4,0);
}