植物大戰僵屍這款游戲可以說是很多90后的回憶了,基本上只要是90后或多或少的都接觸過,而玩游戲與制作輔助是兩個概念,今天我將給大家分享一些游戲輔助方面的制作技巧,之所以使用植物大戰僵屍這款游戲是因為游戲簡單容易分析,且不需要考慮驅動保護版權等相應的問題,這里我會把我的分析思路分享出來,來供大家參考。
游戲下載地址:鏈接:https://pan.baidu.com/s/1ajoUNpq8DmYspWWkhAt-Eg 提取碼:4i3y
教程大綱
1.簡單實現無限陽光
2.實現自動收集陽光
3.尋找葵花生產速度
4.尋找陽光掉落Call
5.直接一鍵秒通關
提示:分析思路學會了,可以應用到任何一款游戲中,網游過驅動保護后,分析思路也是如此,此處只用於技術研究學習交流,請勿將技術用於商業游戲上,出現問題與本人無關。
簡單實現無限陽光
本次實驗內容:通過逆向分析植物陽光數量的動態地址找到陽光的基址與偏移,從而實現每次啟動游戲都能夠使用基址加偏移的方式定位陽光數據,最后我們將通過使用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\n",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\n", addr);
HANDLE Process = OpenProcess(PROCESS_ALL_ACCESS, false, PID);
WriteProcessMemory(Process, (LPVOID)addr,&PID,4,0);
}
實現自動收集陽光
本次實驗內容:通過陽光增加的值為切入點,找到自動收集陽光的關鍵判斷並實現自動收集陽光,首先我們猜測當陽光出現后,我們是否會去點擊,這個過程必然是由一個判斷和一個時鍾周期事件來控制的,那么當我們點擊下落的陽光以后,則該判斷條件實現,會執行收集陽光的CALL,否則的話繼續執行陽光下落的過場動畫,這正是正向開發的一種開發手段,此時我們也僅僅是猜測,接下來我們將去驗證這個大膽的想法。
為了找到陽光自動收集的關鍵跳轉,我們需要以陽光增加作為切入點,為啥以它作為切入點呢?我們可以這樣思考,當我們點擊陽光后陽光增加了,說明已經完成了判斷,下一步就是寫入變量從而增加陽光,那么我們先來找到陽光的動態地址,並在該動態地址上按下F6鍵查找寫入,然后回到游戲等待陽光出現並點擊陽光,此時CE會出現以下代碼,我們只需要記下00430A11
這個內存地址,然后直接關閉CE。
接着打開X64dbg附加到游戲進程,附加完成后,游戲會被X64dbg暫停運行,此時我們直接按下F12讓游戲運行起來,然后按下Ctrl + G
輸入00430A11
跳轉到剛才找到的代碼位置,過去以后直接F2下一個斷點。
此時我們需要逆向思考一個問題add dword ptr ds:[eax+0x5560],ecx
這條指令是在我們陽光被點擊后執行的,也就是說我們已經點擊了陽光現在開始賦值了,那判斷陽光是否被回收肯定是在這條指令之前出現,所以我們向上找,觀察代碼我們不難看出執行add dword ptr ds:[eax+0x5560],ecx
指令之前有一個無條件跳轉jmp 0x00430A0E
跳過來的。
繼續向上查找跳轉來源,可知在jmp跳轉之前有一個je 0x004309EF
跳轉,經過測試這個地方具體控制陽光是否增加,在向上找就到段首了,此處代碼中並沒有出現自動收集陽光的關鍵跳轉,因此推斷這里應該是一個控制陽光是否增加的子過程(子過程:過程中調用的過程,稱為子過程),所以我們繼續回朔到上一層。
為了能夠回朔到上一層,我們需要取消陽光遞增處的斷點,並在段尾00430AB3
處下一個F2斷點防止程序跑飛,回到游戲等待陽光的出現,然后X64dbg就會斷下,斷下后直接取消00430AB3
處的斷點,執行到Ret處即可返回到上一層。
返回到上一層以后,可以看到我們正是在call <plantsvszombies.sub_4309D0>
這里出來的,而上方就有一個jne plantsvszombies.4313FD
關鍵跳,此處的關鍵跳轉也並不是控制是否回收陽光的關鍵跳轉,而此處的代碼量比較少,因此判斷此處還是一個子過程,我們繼續回溯到上一層。
我們直接單步F8運行到返回,並出這個CALL,出CALL以后會看到call <plantsvszombies.sub_430E40>
沒錯!我們正是從這個子過程里出來的,接着向上找跳轉會看到有一個jne plantsvszombies.431599
此處如果將其改為jmp
的話即可實現自動收集陽光,也就是說如果jne跳轉實現則執行收集陽光,否則繼續執行陽光下落的過場動畫。
注意:如果我們在關鍵跳jne plantsvszombies.4313FD
處下斷點時,會發現當陽光出現后程序會被無限的斷下,這說明是有一個定時器線程在不斷的執行判斷代碼,每次都會判斷你是否點擊了陽光,所以X64dbg才會被一直斷下。
知道了修改流程,那我們就通過編程的方式來實現修改程序的硬編碼,首先我們可以通過以下代碼完成字節集的讀取。
#include <stdio.h>
#include <Windows.h>
byte *ReadByteSet(DWORD Pid, DWORD Base, DWORD Size)
{
HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, 0, Pid);
byte *buf = new byte[Size];
ReadProcessMemory(handle, (LPVOID)Base, buf, Size, NULL);
return buf;
}
int main()
{
byte *Buff = new byte[10];
Buff = ReadByteSet(2232, 0x00401000, 10);
for (int i = 0; i < 10; i++)
printf("%02X ", Buff[i]);
return 0;
}
既然有讀取內存字節集,那么就有寫入字節集,如下代碼就是一種字節集寫入的實現方式。
#include <stdio.h>
#include <Windows.h>
BOOL WriteByteSet(DWORD Pid, DWORD Base, unsigned char *ShellCode, DWORD Size)
{
BYTE *Buff = new BYTE[Size];
memset(Buff, *ShellCode, Size);
HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, 0, Pid);
BOOL Ret = WriteProcessMemory(handle, (LPVOID)Base, Buff, Size, NULL);
if (Ret)
return TRUE;
else
return FALSE;
}
int main()
{
unsigned char shell[] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };
BOOL temp = WriteByteSet(3772, 0x00401010, shell, 8);
return 0;
}
想要實現陽光自動收集,只需要將0x0043158F
機器碼0x75 0x08
修改為0xEB 0x08
即可實現效果。
unsigned char shell[] = { 0xEB };
BOOL temp = WriteByteSet(9744, 0x0043158F, shell, 1);
尋找葵花生產速度
本次實驗內容:通過CE修改器遍歷出控制太陽花吐出陽光的時間變量,太陽花吐出陽光是由一個定時器控制的,首先我們找到第一個太陽花的基址與偏移,然后找出第二個太陽花的動態地址,並通過公式計算得到太陽花結構長度的相對偏移,最后我們通過C語言編程實現,遍歷並修改所有圖中的太陽花吐出陽光的時間,最終實現全圖吐陽光。
從本次實驗開始將接觸到關於分析定時器的相關技巧,一般的定時器分為遞增定時器與遞減定時器,不過大多數游戲都會使用遞減定時器,因為遞減定時器更好編程判斷,本游戲中的太陽花生產速度使用的就是遞減定時器,太陽花生產陽光一定是一個周期性的事件,我們只要找到該定時器並改寫它的時間即可實現無限吐陽光,如下是太陽花定時器的遍歷技巧:
首先種下第一個太陽花 -> 然后CE馬上搜索 -> 未知的初始值
回到游戲短暫等待(時鍾發生變化) -> 然后馬上切回CE -> 搜索減少的數值 -> 掉一點搜一點
如果中途太陽花吐出了陽光 -> 則需要搜索增加的數值(1次) -> 然后再搜索減少的數值
最終找到一個動態地址(范圍:0-5000) -> 鎖定該變量范圍在1至10即可 -> 實現無限出陽光
修改太陽花時鍾有兩種方式,第一種找到基址與偏移然后分別修改每一個定時器的時鍾,第二種方式則是找到匯編跳轉並進行改寫,第一種方式要找植物相對偏移,首先我們先來猜測以下游戲作者會用什么方式存儲不同植物的欄位。
如下圖: 我們可先來猜測,游戲作者會使用二維結構體來存儲植物位置,通過結構體鏈表將不同植物進行連接,當我們鏟除植物的時候,只需要在鏈表中摘除相應節點,而太陽花的的地址一定是連續存儲在內存中的線性空間,此游戲的矩陣可能就是5*9
這么一個范圍,假設在橫坐標X軸如果兩個植物之間的相對偏移是14C(14C就是太陽花結構體的實際長度),那么我們找到第一個植物的基址與偏移,每次相加14C的偏移量,則可遍歷到下一個植物的內存地址,同理如果相減14C則就可遍歷出上一個植物的內存地址,而縱坐標Y可能就是由一個1C偏移來控制的,此時我們也僅僅只是猜測。
如果我們按照上圖中的方式進行推理,其計算每一個陽光時鍾公式就可總結為如下,但真的是這樣嗎?
X坐標下的第1個植物:基地址 + 偏移1 + 偏移2 + 768
X坐標下的第2個植物:基地址 + 偏移1 + 偏移2 + 768 + 14C
X坐標下的第3個植物:基地址 + 偏移1 + 偏移2 + 768 + 14C + 14C
Y坐標下的第1個植物:基地址 + 偏移1 + 偏移2 + 768 + 1C
Y坐標下的第2個植物:基地址 + 偏移1 + 偏移2 + 768 + 1C + 14C
其實並不是!經過我對具體坐標的分析,在本游戲中太陽花與太陽花之間,可能使用了一維結構體來存儲的植物與植物之間的屬性,每次相加偏移都會遍歷到下一個植物的屬性上面,也就是說無論太陽花種植到在什么位置,只要相加偏移就可以遍歷到下一個植物的冷卻數據,而需要遍歷的次數則取決於太陽花的種植數量。
首先我們種植一顆太陽花,並通過上方的遍歷技巧找到當前第一個植物的動態地址,排查到最后可發現剩余11條結果,此時我們可猜測這個定時器應該在0-10000之間,應該不會大於這個參數,如下圖我找到了13D65160
這個地址,將該地址鎖定為10就可以實現第一個太陽無限吐陽光。
接着我們在第一個太陽花的旁邊種植第二個太陽花,然后還是使用前面的遍歷技巧找到第二個太陽花的動態地址13D652AC
,找到以后我們可以猜測第一個與第二個在內存中的布局應該是連續的,所以我們可以使用13D652AC - 13D65160 = 14C
此處得到的14C其實就是太陽花結構的實際長度,也可以說是兩個太陽花之間的偏移值。
既然知道了太陽花之間的相對偏移,那么我們下一步就是來找一個固定的地址,我們在第一個太陽花地址上,右鍵選擇查找改寫地址,然后可看到0045FA48 - 83 47 58 FF - add dword ptr [edi+58],-01 <<
這條匯編指令,此匯編指令乍一看是一條加法指令,但其相加的操作數是-1也就是相減,此處就是太陽花的定時器,其每次減1直到為0則吐出陽光,這里我們就可知該定時器是一個遞減定時器,我們只需要記下偏移為58
下一個地址是13D65108
即可。
回到CE我們繼續搜索十六進制地址13D65108
然后找到偏移為AC
下一個地址為13D08948
繼續搜索13D08948
得到下一個偏移數據為768
,繼續搜索00FE7B80
最終經過我們的搜索得到了一個綠色的基址00599F75 - A1 389F6A00 - mov eax,[006A9F38] <<
最后使用CE添加這個基地址與偏移數據來驗證一下,公式為 [[[006A9F38+768]+AC]+58]]
此時我們就可以定位到第一個太陽花的動態地址了。
根據上面的理論,我們知道太陽花的結構體大小為14C
,那么我們在第一個太陽花動態地址的基礎上加上14C就可以得到第二個太陽花的動態地址。
既然找到了基址與偏移,接下來就是通過C語言編程實現修改全圖太陽花的冷卻時間,此處貼出我實現的代碼.
int main()
{
int base;
int offset[4];
int PID = 1292;
base = 0x006a9f38;
offset[0] = 0x768;
offset[1] = 0xac;
offset[2] = 0x58;
int addr = GetDyAddr(PID, base, offset, 3);
printf("陽光吐出動態地址:%x\n", addr);
HANDLE Process = OpenProcess(PROCESS_ALL_ACCESS, false, PID);
int SunOffset = 0;
int SunNum = 10;
while (TRUE)
{
for (int i = 0; i < 5; i++)
{
WriteProcessMemory(Process, (LPVOID)(addr + SunOffset), &SunNum, 4, NULL);
SunOffset = SunOffset + 0x14c;
printf("搞事 fuck ok\n");
}
SunOffset = 0;
Sleep(1000);
}
return 0;
}
當我們進入游戲種植好太陽花以后,我們開啟這個輔助,即可實現讓前四個太陽花吐出陽光,最后種植的太陽花則不修改,為了防止程序出現假死我通過sleep函數讓太陽花每一秒吐出一個陽光,這樣修改的話會很有節奏感。
上述方法,雖然可以修改達到無線吐陽光的作用,但是這種修改方式,顯然是不太合理,如果圖中有10個太陽花,那么我們則只能循環十次,這種效率還是太低,其實我們可以通過直接修改硬編碼的方式來實現一勞永逸的效果,之所以是一勞永逸,是因為所有太陽花的吐陽光判斷都是共用一個判斷函數執行的,陽光的遞減時鍾都會走一個地方add dword ptr [edi+58],-01
我們只需要定位到這里,然后分析出陽光產生的關鍵鍵跳轉並改掉其硬編碼即可。
上圖是經過測試后備注的一些細節,我們只需要將圖中的0045FA7D
處的指令集,替換為nop即可實現全圖的植物無線吐陽光啦,其C語言修改代碼如下,代碼中使用了上面封裝好的的寫內存字節集函數。
int main()
{
int PID = 3612;
unsigned char Auto[] = { 0xEB };
unsigned char Suns[] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };
BOOL ret = WriteByteSet(PID, 0x0043158F, Auto, 1);
BOOL ret1 = WriteByteSet(PID, 0x0045FA7D, Suns, 6);
if (ret != 0 & ret1 != 0)
{
printf("您的向日葵已打包,請注意查收\n");
}
return 0;
}
最終配合自動收集陽光,即可實現如下圖所示的變態功能,其實這也不算變態,畢竟還有更加變態的,其實這種修改方式並不完美,因為我們的陽光數量可能是一個整數類型,如果不加以控制,當整數變量到達所能承載的最大范圍時,則程序會發生整數溢出,輕則陽光變為負數,重則直接崩潰卡死。
尋找陽光掉落Call
本次實驗內容:本次實驗將接觸到Call調用這個概念,什么是Call調用? Call相當於你在編程時所編寫的函數,而高級語言中的函數最終也是會被編譯器轉換為匯編格式的Call調用,這些關鍵Call普遍都會存在各種參數,關於Call的作用,打個比方有些網游外掛可以實現自動尋路,自動吃葯,自動打怪,甚至是全屏秒殺,這些功能是通過修改數值也無法做到的,Call就可做到。
其實關鍵Call就是作者開發過程中寫的一個個處理不同事件的獨立的處理函數,這些函數包括了各種獨立的游戲功能,而我們可以在遠程進程中開辟線程,並通過匯編形式動態的調用這些關鍵Call,從而實現一些變態功能。
本次實驗我們將通過查找陽光的掉落的定時器,並通過定時器變量順藤摸瓜找到生成陽光的關鍵Call,通過給此Call傳遞不同參數實現掉落陽光,鑽石,零秒通關等,陽光遍歷技巧如下:
進入游戲等待陽光出現 -> 當陽光出現后 -> 馬上搜索未知初始數值
返回游戲等待時鍾發生變化 -> 馬上切回CE -> 搜索減少的數值 -> 陽光下落一點搜一點
經過上方步驟反復排查 -> 最終能找到一個值范圍(0-700) -> 鎖定1即可實現無限掉落
基址與偏移的找法,在文章開頭就已經分享了查找的技巧,此處控制陽光掉落公式 [[[006A9F38 + 768] + 5538]]
00413B7C - 83 86 38550000 FF - add dword ptr [esi+00005538],-01 <<
004524F4 - 8B 86 68070000 - mov eax,[esi+00000768] <<
00599F75 - A1 389F6A00 - mov eax,[PlantsVsZombies.exe+2A9F38] <<
我們要找關鍵Call只需要等待陽光出現,當陽光出現后,CE會檢測到一條數據 add dword ptr [esi+00005538],-01
說明此時定時器開始工作了,我們只需要記下這個內存地址00413B7C
,然后關閉CE,並打開X64DBG附加到游戲。
打開X64DBG附加到游戲進程,然后按下ctrl+G
定位到00413B7C
此時我們需要關注add dword ptr ds:[esi+0x5538],0xFFFFFFFF
這條計時器指令下方,就是一個大跳轉該跳轉跳向了結束,那么我們可以猜測jne 0x413BF1
跳過的內容很有可能就是跳過了陽光的生成過程,也就是可能跳過了陽光生成Call。
玩過此游戲的一定知道,游戲屏幕中不止可以掉落出陽光,還可以掉出其他的東西,比如鑽石,金錢,獎牌等,那么我們有理由相信,該游戲中調用的Call應該有很多參數傳遞,比如掉落屬性,掉落坐標,掉落類型等,而我們已經找到了陽光計時器每次遞減的匯編代碼,故猜測調用Call應該就在附近,向下查找發現有很多Call調用,但有一個比較特別的call 0x40CB10
,之所以說它特別是因為該Call在調用之前,通過push傳遞了多了參數,此處很可疑,我們需要具體分析。
此時我們在00413BE4
處下斷點,然后回到游戲發現每當陽光出現的時候,此處就會斷下,那么我們嘗試給它傳遞不同的參數,例如給eax
傳遞一個2,觀察效果發現原本應該是出現陽光的地方,現在居然出現了金幣。
經過上方的測試,發現我們猜測是合理的,那么這段代碼,轉換為C語言后應該是這樣的:
> push 0 普通掉落 push 2-3 其他掉落方式 push 4 自動收集陽光 push 6 右側滑出掉落
> eax=2 金錢 eax=3 鑽石 eax=4 普通陽光 eax=5 小的陽光 eax=6 大的陽光 eax=8 自動通關神器
sub_00413BE4(push 0= 物品飛出狀態,push eax= 掉落物品,push 0x3c,push edi= 對象狀態,ecx= 游戲基地址)
{
return 0;
}
到此,我們還沒有辦法完成外部注入,因為據我觀察程序中的edi寄存器與ecx寄存器中的數據是動態的,每次游戲重新運行都會發生變化,如果想要在外部調用這個Call函數,我們需要找到這兩個寄存器的基址,或者說找到他們的來源。
我們先來分析第一個地址如何定位,第一個動態地址是EDI寄存器,這個寄存器每次存儲一個整數,此處無法直接找基址,我采取的方式是從后向前逆推代碼,觀察那些指令改寫了該寄存器,然后將這些改寫寄存器的指令拼湊起來。
這些調用分別是上圖中的,賦值1-賦值3外加一個call 0x005AF400
,程序中正是通過這種方式計算出動態數據的,我們直接將其提取出來,提取后代碼如下:
mov eax,226
call 0x005AF400
mov edi,eax
add edi,64
再來分析第二個,第二個相對於第一個來說就好找許多了,因為它是一個動態地址,如下圖我們記下 13DCE000
這個動態地址,然后直接在X64dbg中脫離游戲,這里不能結束游戲,如果結束了下次該地址又會發生變化。
CE直接附加游戲,然后我們來搜索十六進制數據13DCE000
然后,你懂的,就是找基址,前面已經找過很多次了。
通過查找訪問代碼,就能找到一級偏移是768,繼續記下00FE4170
然后回到CE繼續搜索。
到這里基地址出來了,其公式為[6A9EC0+768]
,從這里也可以看出,這應該是一個通用對象地址。
最后,我們整合並寫出以下匯編代碼,然后使用注入器注入到游戲測試。
pushad
mov eax,226
call 0x005AF400
mov edi,eax
add edi,64
mov esi,dword ptr ds:[ 006a9ec0 ]
mov esi,dword ptr ds:[ esi + 768 ]
push 0
push 4
push 3c
push edi
mov ecx,esi
call 0x40cb10
popad
發現每當我們點擊注入以后,程序中就會多出一個太陽,說明我們的匯編代碼沒有問題。
為了能夠寫出自己的外掛,我們需要實現一個簡單的注入器,並能夠通過遠程的方式調用陽光生成Call,使用C語言編寫如下代碼即可實現注入效果,需要注意調用約定,此處我在Win10上測試沒有問題,在Xp系統上會出現異常退出的情況。
#include <stdio.h>
#include <Windows.h>
void AddSun()
{
__asm{
pushad
mov eax, 226
mov ebx, 0x005AF400
call ebx
mov edi, eax
add edi, 64
mov esi, dword ptr ds : [0x006a9ec0]
mov esi, dword ptr ds : [esi + 0x768]
push 0h
push 0x4
push 0x3c
push edi
mov ecx, esi
mov edx, 0x40cb10
call edx
popad
}
}
void InjectCode(DWORD dwProcId, LPVOID mFunc)
{
HANDLE hProcess,hThread;
LPVOID mFuncAddr,ParamAddr;
DWORD NumberOfByte;
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcId);
mFuncAddr = VirtualAllocEx(hProcess, NULL, 128, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProcess, mFuncAddr, mFunc, 128, &NumberOfByte);
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)mFuncAddr, ParamAddr, 0, &NumberOfByte);
WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hProcess, mFuncAddr, 128, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
}
int main()
{
for (int i = 0; i < 10;i++)
InjectCode(3644, AddSun);
}
至此我們的植物大戰僵屍關於陽光的分析就到此結束了,其他的比如植物無敵,植物攻擊加速等,就留一個作業,大家開動腦筋看能不能自己實現,回顧前面所講的外掛制作技巧,你是否已經完全理解了呢?如果你能夠吃透這些基礎知識,那么相信你可以做出植物無敵,攻擊加速等其他變態功能,其實逆向就是一個思路的問題,大家要多去思考,多去嘗試,相信你會成功的!
原創作品:轉載請加出處,您添加出處,是我創作的動力!