植物大戰僵尸修改器制作--從入門到入土
一 基礎準備
1.CheatEngine工具的基本使用
推薦視頻你能學會的Cheat Engine零基礎入門教程(https://www.bilibili.com/video/BV1nR4y1u7PZ/?spm_id_from=333.999.0.0&vd_source=8c182d1e4a80cc9f34dfe996135c2c23),
將ce官方給的闖關游戲通關即可。
2.C/C++和匯編語言基礎
附上匯編代碼轉換網站(https://defuse.ca/online-x86-assembler.htm#disassembly)。
3.WIN32開發基礎
了解WIN32命名規則,會使用GPT和查找微軟官方文檔即可。
推薦通過看微軟官方文檔Win32 和 C++ 入門 能創建第一個windows程序即可。
示例游戲版本: 中文年度加強版1.1.0.1056
主要參考資料
1.【補檔】豪哥植物大戰僵尸修改教程視頻合集(https://www.bilibili.com/video/BV1te4y1U7Jn?p=1&vd_source=8c182d1e4a80cc9f34dfe996135c2c23)
2.C/C++全棧軟件安全課(調試、反調試、游戲反外掛、軟件逆向)持續更新中~~~~(https://www.bilibili.com/video/BV1By4y1r7Cq/?p=156&vd_source=8c182d1e4a80cc9f34dfe996135c2c23)
3.逆向工程實戰 揭秘匯編/反匯編(win32+游戲逆向實戰)(https://www.bilibili.com/video/BV1Jb411p7fU/?p=25&vd_source=8c182d1e4a80cc9f34dfe996135c2c23)
二 基址偏移表
部分參考: 公布我所找到的所有基址及各種功能實現方法
基址 0x00355E0C
陽光 +868 +5578
金錢 +950 +50
花肥 +950 +220
巧克力 +950 +250
樹肥 +950 +258
樹高 +950 +11C
殺蟲劑 +950 +224
卡槽數 +868 +15C +24
卡槽欄 +868 +15C +5C 此后每個植物欄相隔0x50
植物當前冷卻值 +868 +15C +4C 此后每個植物冷卻相隔0x50
植物冷卻值上限 +868 +15C +50 此后每個植物冷卻上限相隔0x50
植物當前數量 +868 +D4
植物種植函數EBP +868
僵尸當前數量 +868 +B8
僵尸種植函數EBP +868 +178
三 常規項目
根據變量的變化使用CE尋找,找到之后再通過指針掃描尋找可用的基址。
陽光 內存實際值=游戲顯示值
智慧樹高度 內存實際值=游戲顯示值
金錢 內存實際值=游戲顯示值/10
花肥,殺蟲劑,巧克力,樹肥 內存實際值=游戲顯示值+1000
關鍵函數和變量
enum Type {
Sunlight, Money, TreeHeight, Chocolate, TreeFood, FlowerFood, Insecticide
};
//定義映射表用于保存各項偏移值
unsigned int offsetTable[10] = { 0x5578,0x50,0x11c,0x250,0x258,0x220,0x224 };
//獲取某些項目的值
unsigned int getSomething(HANDLE handle, DWORD BaseAddr,unsigned int type) {
unsigned int num = 0;
DWORD addr = BaseAddr + 0x00355E0C;
ReadProcessMemory(handle, addr, &addr, sizeof(DWORD), NULL);
if (type == Sunlight)
addr += 0x868;
else
addr += 0x950;
ReadProcessMemory(handle, (LPVOID)addr, &addr, sizeof(DWORD), NULL);
addr += offsetTable[type];
ReadProcessMemory(handle, (LPVOID)addr, &num, sizeof(DWORD), 0);
return num;
}
//設置某些項目的值
void setSomething(HANDLE handle, DWORD BaseAddr,unsigned int type, unsigned int num) {
DWORD addr = BaseAddr + 0x00355E0C;
ReadProcessMemory(handle, addr, &addr, sizeof(DWORD), NULL);
if (type == Sunlight)
addr += 0x868;
else
addr += 0x950;
ReadProcessMemory(handle, (LPVOID)addr, &addr, sizeof(DWORD), NULL);
addr += offsetTable[type];
WriteProcessMemory(handle, (LPVOID)addr, &num, sizeof(DWORD), 0);
}
四 卡槽植物
十個卡槽,每個卡槽對應一個植物,可以在堅果保齡球2中根據卡槽1(最左邊的卡槽)的堅果變化來找到卡槽的地址,之后再尋找基址。
具體方法: 初值未知,如果卡槽1的植物和新的卡槽1(原卡槽2)的植物相同,則掃不變的值,否則掃變化的值。
卡槽之間的偏移可以通過瀏覽卡槽1內存區域看出,為0x50。
堅果植物卡槽編號:
普通堅果 3
爆炸堅果 49
巨型堅果 50
設置卡槽植物函數
//設置卡槽植物
BOOL SetPlantCard(HANDLE hProcess,DWORD BaseAddr,DWORD nCard,DWORD plantType) {
DWORD cardAddr = BaseAddr + 0x355E0C;
ReadProcessMemory(hProcess, cardAddr, &cardAddr, sizeof(DWORD), NULL);
cardAddr += 0x868;
ReadProcessMemory(hProcess, cardAddr, &cardAddr, sizeof(DWORD), NULL);
cardAddr += 0x15C;
ReadProcessMemory(hProcess, cardAddr, &cardAddr, sizeof(DWORD), NULL);
cardAddr += 0x5C+nCard*0x50;//卡槽偏移
return WriteProcessMemory(hProcess, cardAddr, &plantType, sizeof(DWORD), NULL);
}
五 種植無冷卻
具體方法: 僅針對一個卡槽,初始值未知,種植后持續變化,冷卻完畢后不變,反復掃描并查找基址,查看對應內存區域再對照植物編號可以發現卡槽間的偏移為0x50。
冷卻特點: 可種植狀態冷卻值為0,種植后冷卻值持續增長,到達冷卻上限后,冷卻值清零,植物重新可種植。
注意: 直接將冷卻值置0會導致無法種植。
修改方法:
1.修改冷卻結束后恢復的速度,將inc指令修改為mov一個較大值 這個
2.直接跳轉到冷卻值和冷卻上限比較成功的函數
以方法2為例
7E 16 對應匯編指令為 jle 0x18
修改為jmp $+2 即 eb 00 (相對當前指令2字節后的指令)
直接執行冷卻值達到冷卻上限后的函數(冷卻值清零,植物冷卻完畢可種植)。
附上匯編代碼轉換網站(https://defuse.ca/online-x86-assembler.htm#disassembly)

關鍵代碼
//修改進程代碼區代碼 參數: 進程句柄 修改代碼起始地址 硬編碼指針 代碼字節數
BOOL WriteProcessCodeMemory(HANDLE hProcess, LPVOID lpStartAddress, LPCVOID lpBuffer, SIZE_T nSize) {
DWORD dwOldProtect;
//取消頁保護
if (!VirtualProtectEx(hProcess, lpStartAddress, nSize, PAGE_EXECUTE_READWRITE, &dwOldProtect)) {
return FALSE;
}
BOOL bResult = WriteProcessMemory(hProcess, lpStartAddress, lpBuffer, nSize, NULL);//寫入代碼
VirtualProtectEx(hProcess, lpStartAddress, nSize, dwOldProtect, &dwOldProtect);//開啟頁保護
return bResult;
}
//無限冷卻
BOOL Uncooled(HANDLE hProcess, DWORD BaseAddr) {
unsigned char code[2] = { 0xeb,0x00 };
return WriteProcessCodeMemory(hProcess, BaseAddr + 0x9ce02, code, 2);//jle 0x18修改為jmp $+2
}
//恢復冷卻
BOOL RecoveryCooling(HANDLE hProcess, DWORD BaseAddr) {
unsigned char OriginalCode[2] = { 0x7E ,0x16 };//jmp $+2恢復為jle 0x18
return WriteProcessCodeMemory(hProcess, BaseAddr + 0x9ce02, OriginalCode, 2);
}
六 無限陽光
前文已經給出了陽光的地址 基址為0x355E0C 偏移+868 +5578
查找對陽光修改的代碼即可。
陽光減少代碼

陽光增加代碼

基本過程:
1.設置陽光值為9999
2.修改陽光減少代碼使得種植物不消耗陽光
3.修改陽光增加代碼使得陽光不變化(防止陽光過多導致溢出)
//修改進程代碼區代碼 參數: 進程句柄 修改代碼起始地址 硬編碼指針 代碼字節數
BOOL WriteProcessCodeMemory(HANDLE hProcess, LPVOID lpStartAddress, LPCVOID lpBuffer, SIZE_T nSize) {
DWORD dwOldProtect;
//取消頁保護
if (!VirtualProtectEx(hProcess, lpStartAddress, nSize, PAGE_EXECUTE_READWRITE, &dwOldProtect)) {
return FALSE;
}
BOOL bResult = WriteProcessMemory(hProcess, lpStartAddress, lpBuffer, nSize, NULL);//寫入代碼
VirtualProtectEx(hProcess, lpStartAddress, nSize, dwOldProtect, &dwOldProtect);//開啟頁保護
return bResult;
}
//無限陽光,鎖定陽光為9999
BOOL UnlimitedSun(HANDLE hProcess, DWORD BaseAddr) {
unsigned char Code[3] = { 0x29,0xdb,0 };//cmp ebx,eax 修改為sub ebx,ebx and ecx,0x32修改為and ecx,0
BOOL flag;
flag = setSomething(hProcess, BaseAddr, Sunlight, 9999);//修改陽光
flag &= WriteProcessCodeMemory(hProcess, BaseAddr + 0x27690, Code, 2);//修改陽光減少代碼
flag &= WriteProcessCodeMemory(hProcess, BaseAddr + 0x3C0AB, &Code[2], 1);//修改陽光增加代碼
return flag;
}
//恢復陽光消耗
BOOL RecoverySunConsume(HANDLE hProcess, DWORD BaseAddr) {
unsigned char OriginalCode[3] = { 0x3B,0xD8,0x32 };//sub ebx,ebx恢復為cmp ebx,eax and ecx,0恢復為and ecx,0x32
BOOL flag = WriteProcessCodeMemory(hProcess, BaseAddr + 0x27690, OriginalCode, 2);//恢復陽光減少代碼
flag &= WriteProcessCodeMemory(hProcess, BaseAddr + 0x3C0AB, &OriginalCode[2], 1);//恢復陽光增加代碼
return flag;
}
七 濃霧透視
基本原理
具體方法: 在生存模式濃霧進行,初值未知,通過在霧區種植和鏟除路燈花引起的變化來判斷,最終可以發現是4字節數據,數值代表霧的濃度,255代表濃霧,0代表沒霧,再查找修改霧值的代碼。
尋找濃霧地址

濃霧修改代碼
mov [ecx],edx這行代碼修改了霧值,可以改為mov [ecx],0。
注意硬編碼為0xc7,0x01,0x00,0x00,0x00,0x00 由于較長無法直接修改代碼,所以這里選擇使用hook技術。

HOOK
hook的基本過程
1.讀取并保存目的地址原始代碼
2.申請空間(PVZ游戲進程空間)用于存儲原始代碼 hook代碼 jmp返回代碼
3.向申請的空間中寫入原始代碼 hook代碼 jmp返回代碼
4.修改目的地址的代碼為jmp HookCode
5.返回HookCode首地址 用于解除hook
值得一提的是jmp指令后跟的偏移值是以jmp的下一條指令首地址計算
jmp指令偏移值=目的地址-(jmp指令首地址+5) 這里的5是jmp指令本身的長度 +5便是下一條指令
offset=desAddr-(jmpAddr+5)
//修改進程代碼區代碼 參數: 進程句柄 修改代碼起始地址 硬編碼指針 代碼字節數
BOOL WriteProcessCodeMemory(HANDLE hProcess, LPVOID lpStartAddress, LPCVOID lpBuffer, SIZE_T nSize) {
DWORD dwOldProtect;
//取消頁保護
if (!VirtualProtectEx(hProcess, lpStartAddress, nSize, PAGE_EXECUTE_READWRITE, &dwOldProtect)) {
return FALSE;
}
BOOL bResult = WriteProcessMemory(hProcess, lpStartAddress, lpBuffer, nSize, NULL);//寫入代碼
VirtualProtectEx(hProcess, lpStartAddress, nSize, dwOldProtect, &dwOldProtect);//開啟頁保護
return bResult;
}
//hook指定地址,申請新空間保存原始代碼并寫入hookcode,返回申請空間的地址
LPVOID SetHook(HANDLE hProcess, LPVOID desAddr, LPCVOID hookCode, SIZE_T hookCodeSize, SIZE_T origCodeSize) {
BYTE origCode[10] = { 0 }, jmpCode[5] = { 0xE9,0,0,0,0 };
//1. 讀取并保存原始代碼
if (!ReadProcessMemory(hProcess, desAddr, origCode, origCodeSize, NULL))
return NULL;
//2. 申請空間用于存儲原始代碼,hook代碼,jmp返回代碼
LPVOID allocAddr = VirtualAllocEx(hProcess, NULL, hookCodeSize + origCodeSize + 5, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!allocAddr)
return NULL;
//3. 向申請空間寫入原始代碼,hook代碼,jmp返回代碼 jmp xxx 偏移為目的地址-jmp下一條指令地址
*(DWORD*)(jmpCode + 1) = (DWORD)desAddr + 5 - ((DWORD)allocAddr + hookCodeSize + origCodeSize + 5);//hook返回地址的偏移
if (!WriteProcessCodeMemory(hProcess, allocAddr, origCode, origCodeSize) //寫入原始代碼
|| !WriteProcessCodeMemory(hProcess, (DWORD)allocAddr + origCodeSize, hookCode, hookCodeSize)//寫入hook代碼
|| !WriteProcessCodeMemory(hProcess, (DWORD)allocAddr + origCodeSize + hookCodeSize, jmpCode, 5))//寫入jmpcode
{
VirtualFreeEx(hProcess, allocAddr, 0, MEM_RELEASE);//寫入失敗則釋放空間
return NULL;
}
//4. 修改目的地址處的代碼 jmp xxx偏移 原始代碼后才是需要執行的hook代碼
*(DWORD*)(jmpCode + 1) = ((DWORD)allocAddr + origCodeSize) - ((DWORD)desAddr + 5);
WriteProcessCodeMemory(hProcess, desAddr, jmpCode, 5);//在源地址處寫入跳轉代碼
if (origCodeSize > 5)//原始代碼長度大于5時nop多余字節
{
BYTE nopCode[5] = { 0x90,0x90,0x90,0x90,0x90 };
if (!WriteProcessCodeMemory(hProcess, (DWORD)desAddr + 5, nopCode, origCodeSize - 5))
{
VirtualFreeEx(hProcess, allocAddr, 0, MEM_RELEASE);//寫入nopcode失敗則釋放空間并返回
return NULL;
}
}
//5. hook成功則返回hookCode所在地址
return allocAddr;
}
//取消hook指定地址,寫回原始代碼并釋放申請空間
BOOL UnHook(HANDLE hProcess, LPVOID desAddr, SIZE_T origCodeSize, LPVOID allocAddr) {
BYTE origCode[10] = { 0 };
//1. 從申請空間中讀出原始代碼
if (!ReadProcessMemory(hProcess, allocAddr, origCode, origCodeSize, NULL))
return FALSE;
//2. 將原始代碼寫回目的地址
if (!WriteProcessCodeMemory(hProcess, desAddr, origCode, origCodeSize))
return FALSE;
//3. 釋放申請空間
if (!VirtualFreeEx(hProcess, allocAddr, 0, MEM_RELEASE))
return FALSE;
return TRUE;
}
除霧代碼
//除霧 注意保留hook代碼首地址
LPVOID DeFogByHook(HANDLE hProcess, LPVOID BaseAddr) {
unsigned char hookCode[9] = {
0xc7,0x01,0x00,0x00,0x00,0x00, //mov [ecx],0
0x83,0xc1,0x04 //add ecx,0x4
};
//寫入hook代碼進行hook
return SetHook(hProcess, (DWORD)BaseAddr + 0x26173, hookCode, sizeof(hookCode), 5);
}
//恢復霧
BOOL RecoveryFogByUnHook(HANDLE hProcess, LPVOID BaseAddr, LPVOID allocAddr) {
return UnHook(hProcess, (DWORD)BaseAddr + 0x26173, 5, allocAddr);
}
hook前 指令為mov [ecx],edx add ecx,04。

hook后 指令被修改為jmp。

hookcode 新分配空間前5個字節正是原始代碼 之后是hook代碼和jmp返回代碼。

八 種植植物
基本原理
程序是執行種植植物的函數后再執行增加植物數量的功能。
首先查找草坪上的植物數量,初值0,隨著種植個數增加 基址0x355E0C 偏移+868 +D4。
再查找是什么修改了植物數量,下斷點之后再種植一個植物。
斷下后查看調用堆棧中的返回地址,即可找到種植函數。

這個功能最初使用遠程線程注入dll來實現,注入dll雖然比較簡單但是卻并不通用,在此僅做介紹,比較推薦使用遠程代碼注入的方式實現。
遠程線程注入dll函數
遠程線程是當前進程在目標進程中創建一個線程并執行特定代碼(這段代碼必須在目標進程中而不是當前進程中)。
注入dll是因為dll在被進程或線程加載時執行dll的DllMain函數,通過這一特點我們可以實現一些特殊功能。
優點: 便于實現
缺點: dll注入容易被檢測到
基本過程:
1.打開進程獲取進程句柄
2.在目標進程中申請空間用于存儲dll路徑名
3.將dll路徑名寫入申請的空間中
4.創建遠程線程,執行LoadLibrary函數(加載dll)
5.目標進程加載dll后自動執行dll的DllMain函數
//創建遠程線程方式向指定進程注入dll
BOOL InjectDllByRemoteThread(DWORD desProcId,WCHAR* dllPath) {
//打開進程獲取進程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, desProcId);
if (!hProcess)
return FALSE;
//申請空間
DWORD pathSize = (wcslen(dllPath) + 1) * 2;
LPVOID newMemAddr = VirtualAllocEx(hProcess, 0, pathSize, MEM_COMMIT, PAGE_READWRITE);
if (!newMemAddr)
return FALSE;
//寫入dll路徑
if (!WriteProcessMemory(hProcess, newMemAddr, dllPath, pathSize, NULL))
{
VirtualFreeEx(hProcess, newMemAddr, 0, MEM_RELEASE);
return FALSE;
}
//創建遠程線程
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, newMemAddr, 0, NULL);
if (!hThread)
{
VirtualFreeEx(hProcess, newMemAddr, 0, MEM_RELEASE);
return FALSE;
}
WaitForSingleObject(hThread, INFINITE);//等待線程信號,保證成功注入
//回收資源
VirtualFreeEx(hProcess, newMemAddr, 0, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
//返回成功
return TRUE;
}
遠程線程卸載dll函數
很多教程只給出了如何注入dll,沒有演示如何卸載。
如果只注入不卸載會導致下次再注入時不會執行特定函數(由于dll已經被加載過) 不方便實時調試更新dll等問題。
基本過程:
1.在目標進程申請內存,將需要卸載的dll模塊名稱寫入該內存
2.通過枚舉模塊來查找指定模塊
3.成功查找到dll模塊則創建遠程線程執行FreeLibrary函數卸載dll
BOOL UnLoadDllByRemoteThread(DWORD dwProcessId, LPCWSTR lpDllName)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (hProcess == NULL)
return FALSE;
// 在目標進程中申請一塊內存,并將需要卸載的DLL模塊的名稱寫入該內存
LPVOID lpRemoteDllName = VirtualAllocEx(hProcess, NULL, (wcslen(lpDllName) + 1) * sizeof(WCHAR), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (lpRemoteDllName == NULL)
{
CloseHandle(hProcess);
return FALSE;
}
if (!WriteProcessMemory(hProcess, lpRemoteDllName, lpDllName, (wcslen(lpDllName) + 1) * sizeof(WCHAR), NULL))
{
VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
//查找dll模塊
HMODULE hModules[1024],DesModule=NULL;
DWORD dwSize = 0;
if (!EnumProcessModules(hProcess, hModules, sizeof(hModules), &dwSize))
{
VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
// 遍歷模塊列表,查找需要卸載的DLL模塊
for (DWORD i = 0; i < (dwSize / sizeof(HMODULE)); i++)
{
WCHAR szModuleName[MAX_PATH] = { 0 };
if (GetModuleFileNameExW(hProcess, hModules[i], szModuleName, MAX_PATH) > 0)
{
// 獲取模塊句柄
if (wcsicmp(szModuleName, lpDllName) == 0)
{
DesModule = hModules[i];
}
}
}
//沒有查找到模塊
if (!DesModule) {
VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
// 在目標進程中創建遠程線程,執行FreeLibrary函數
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)FreeLibrary, DesModule, 0, NULL);
if (hThread == NULL)
{
VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
// 等待線程執行完成
WaitForSingleObject(hThread, INFINITE);
// 關閉句柄
CloseHandle(hThread);
VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
CloseHandle(hProcess);
return TRUE;
}
關鍵dll函數
這里使用了三種方法。
注意: 不要將代碼寫入switch(reason)之外,否則可能會導致多次執行。
#include<windows.h>
#include<stdio.h>
//調用函數
BOOL GrowPlant(DWORD BaseAddr, DWORD x, DWORD y, DWORD TypePlant) {
LPVOID PlantFunc = BaseAddr + 0x18D70;
__asm {
pushad
push -1 //-1
push TypePlant //植物類型
mov eax, y //y
push x //x
mov ecx, BaseAddr
mov ecx, [ecx+0x355E0C]
mov ecx, [ecx + 0x868]
push ecx //植物種植ebp
call PlantFunc
popad
}
return TRUE;
}
BOOL WINAPI DllMain(HMODULE hInstance, DWORD fdwReason, LPVOID lpReserved) {
DWORD BaseAddr = GetModuleHandle(NULL);
DWORD pid = GetCurrentProcessId();
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
LPVOID PlantFunc = BaseAddr + 0x18D70;
DWORD ebpAddr = BaseAddr+0x355E0C,num=0;
ReadProcessMemory(hProcess, ebpAddr, &ebpAddr, sizeof(DWORD), NULL);
ebpAddr += 0x868;
ReadProcessMemory(hProcess, ebpAddr, &ebpAddr, sizeof(DWORD), NULL);//必須使用帶hProcess參數的才能正確讀取到地址,NULL不可以
DWORD x = 1, y = 1, TypePlant = 16;
//注意不要寫到switch外,否則可能會一次種多株植物,猜測是dll被多個線程加載導致的
switch (fdwReason)
{
case DLL_PROCESS_ATTACH: //當進程加載dll模塊時執行
//MessageBoxW(0, L"ProcessAttach!", L"window2", 0);
//1.直接通過使用ReadProcessMemory函數讀取內存獲取ebp參數
__asm {
pushad
push - 1 //-1
push TypePlant //植物類型
mov eax, y //y
push x //x
push ebpAddr //ebp
call PlantFunc
popad
}
//2.通過利用寄存器獲取ebp(推薦)
x = 3, y = 2, TypePlant = 18;
__asm {
pushad
push - 1 //-1
push TypePlant //植物類型
mov eax, y //y
push x //x
mov ecx, BaseAddr
mov ecx, [ecx+0x355E0C]
mov ecx, [ecx + 0x868]
push ecx
call PlantFunc
popad
}
//3. 通過調用函數(推薦)
GrowPlant(BaseAddr,7,3,23);
break;
//case DLL_THREAD_ATTACH:
// printf("ThreadAttach!\n");
// break;
//case DLL_THREAD_DETACH:
// if (lpReserved == NULL)
// {
// FreeLibrary(hInstance);
// }
break;
case DLL_PROCESS_DETACH: //當進程卸載dll模塊時執行
MessageBoxW(0, L"ProcessDeTachDll!", L"window2", 0);
break;
}
return TRUE;
}
執行結果

失敗代碼
這是寫dll函數時遇到的問題 如果直接用 mov ecx,[BaseAddr+0x355E0C]會導致代碼執行失敗,推測是這條指令訪存過慢所以無效。
建議mov ecx,BaseAddr之后通過對寄存器操作達到目的。
__asm {
pushad
push - 1 //-1
push TypePlant //植物類型
mov eax, y //y
push x //x
mov ecx,[BaseAddr+ 0x355E0C]//這樣不行,推測是訪存過慢
mov ecx,[ecx+0x868]
mov num, ecx
push ecx
call PlantFunc
popad
}
遠程線程代碼注入(推薦)
和遠程線程dll注入類似,CreateRemoteThread函數要求的函數原型是:
DWORD WINAPI ThreadProc( _In_ LPVOID lpParameter//使用CreateThread函數傳遞的參數 該參數是一個指向其他數據的指針,當然也可以強轉為其他類型直接使用 );
基本過程:
1.打開進程
2.定義注入代碼(函數)
3.在目標進程中申請空間并寫入注入代碼
4.創建遠程線程執行注入代碼(函數)
5.執行完畢釋放空間
//以創建遠程線程方式種植植物
BOOL GrowPlantByInjectCode(DWORD dwProcessId,DWORD BaseAddr,DWORD x,DWORD y,DWORD PlantType)
{
BOOL bSuccess = FALSE;
//1. 打開進程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (hProcess != NULL)
{
//2. 定義注入代碼(函數)
BYTE InjectCode[50] = { //匯編指令 //修正點偏移
0x55, //0 push ebp
0x89, 0xE5, //1 mov ebp,esp
0x60, //3 pushad
0x68, 0xFF, 0xFF, 0xFF, 0xFF, //4 push -1
0x68, 0x00, 0x00, 0x00, 0x00, //9 push PlantType //10
0xB8, 0x00, 0x00, 0x00, 0x00, //14 mov eax,y //15
0x68, 0x00, 0x00, 0x00, 0x00, //19 push x //20
0xB9, 0x00, 0x00, 0x00, 0x00, //24 mov ecx,BaseAddr //25
0x8B, 0x89, 0x0C, 0x5E, 0x35, 0x00, //29 mov ecx,[ecx+0x355E0C]
0x8B, 0x89, 0x68, 0x08, 0x00, 0x00, //35 mov ecx,[ecx+0x868]
0x51, //41 push ecx
0xE8, 0x00, 0x00, 0x00, 0x00, //42 call PlantFunc //43 //被調方平棧
0x61, //47 popad
0xC9, //48 leave
0xC3 //49 ret
};
//3. 申請空間用于存儲代碼
DWORD dwCodeSize = 50, desFunc = BaseAddr + 0x18D70;
LPVOID lpRemoteCodeMem = VirtualAllocEx(hProcess, NULL, dwCodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
//4. 修正參數
*(DWORD*)&InjectCode[10] = PlantType;
*(DWORD*)&InjectCode[15] = y;
*(DWORD*)&InjectCode[20] = x;
*(DWORD*)&InjectCode[25] = BaseAddr;
*(DWORD*)&InjectCode[43] = desFunc-((DWORD)lpRemoteCodeMem+42+5) ;
//call指令與jmp類似,相對于當前指令的下一條指令計算偏移,offset=des-(source+5),減去call自身長度5
if (lpRemoteCodeMem != NULL)
{
SIZE_T dwBytesWritten = 0;
//5. 注入代碼
if (WriteProcessMemory(hProcess, lpRemoteCodeMem, InjectCode, dwCodeSize, &dwBytesWritten) &&
dwBytesWritten == dwCodeSize)
{
//6. 創建遠程線程執行代碼
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRemoteCodeMem,NULL, 0, NULL);
if (hThread != NULL)
{
//7. 等待線程信號
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
bSuccess = TRUE;
}
}
//8. 執行完后釋放空間
VirtualFreeEx(hProcess, lpRemoteCodeMem, 0, MEM_RELEASE);
}
CloseHandle(hProcess);
}
return bSuccess;
}
九 種植僵尸
基本原理
與種植植物思路類似。
首先在頭腦風暴中通過種植僵尸來找到僵尸數量地址。
然后找到僵尸數量增加代碼。
再通過查看調用堆棧和參數找到種植僵尸call。
參數應該也是x y type ebp (注意沒有-1)。
僵尸種植函數的x值在一個call上方,這個call是個switch結構,沒有參數,所以x值也沒被修改。

種植僵尸函數--dll注入版
BOOL GrowZombie(DWORD BaseAddr, DWORD x, DWORD y, DWORD ZombieType) {
LPVOID PlantZombieFunc = BaseAddr + 0x35390;
__asm {
pushad
push x
push ZombieType
mov eax,y
mov ecx,BaseAddr
mov ecx,[ecx+0x355E0C]
mov ecx,[ecx+0x868]
mov ecx,[ecx+0x178] //ebp
call PlantZombieFunc
popad
}
return TRUE;
}
遠程代碼注入版
//以創建遠程線程方式種植僵尸
BOOL GrowZombieByRemoteThread(DWORD dwProcessId,DWORD BaseAddr, DWORD x, DWORD y, DWORD ZombieType) {
BOOL bSuccess = FALSE;
//1. 打開進程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (hProcess != NULL)
{
//2. 定義注入代碼(函數)
BYTE InjectCode[50] = {
0x55, //0 push ebp
0x89, 0xE5, //1 mov ebp,esp
0x60, //3 pushad
0x68, 0x00, 0x00, 0x00, 0x00, //4 push x
0x68, 0x00, 0x00, 0x00, 0x00, //9 push ZombieType
0xB8, 0x00, 0x00, 0x00, 0x00, //14 mov eax,y
0xB9, 0x00, 0x00, 0x00, 0x00, //19 mov ecx,BaseAddr
0x8B, 0x89, 0x0C, 0x5E, 0x35, 0x00, //24 mov ecx,[ecx+0x355E0C]
0x8B, 0x89, 0x68, 0x08, 0x00, 0x00, //30 mov ecx,[ecx+0x868]
0x8B, 0x89, 0x78, 0x01, 0x00, 0x00, //36 mov ecx,[ecx+0x178]
0xE8, 0x00, 0x00, 0x00, 0x00, //42 call PlantZombieFunc
0x61, //47 popad
0xC9, //48 leave
0xC3 //49 ret
};
//3. 申請空間用于存儲代碼
DWORD dwCodeSize = 50, desFunc = BaseAddr + 0x35390; //種植僵尸函數
LPVOID lpRemoteCodeMem = VirtualAllocEx(hProcess, NULL, dwCodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
//4. 修正參數
*(DWORD*)&InjectCode[5] = x;
*(DWORD*)&InjectCode[10] = ZombieType;
*(DWORD*)&InjectCode[15] = y;
*(DWORD*)&InjectCode[20] = BaseAddr;
*(DWORD*)&InjectCode[43] = desFunc - ((DWORD)lpRemoteCodeMem + 42 + 5);//call指令與jmp類似,相對于當前指令的下一條指令計算偏移,要減去call長度5
if (lpRemoteCodeMem != NULL)
{
SIZE_T dwBytesWritten = 0;
//5. 注入代碼
if (WriteProcessMemory(hProcess, lpRemoteCodeMem, InjectCode, dwCodeSize, &dwBytesWritten) &&
dwBytesWritten == dwCodeSize)
{
//6. 創建遠程線程執行代碼
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRemoteCodeMem, NULL, 0, NULL);
if (hThread != NULL)
{
//7. 等待線程信號
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
bSuccess = TRUE;
}
}
//8. 執行完后釋放空間
VirtualFreeEx(hProcess, lpRemoteCodeMem, 0, MEM_RELEASE);
}
CloseHandle(hProcess);
}
return bSuccess;
}
十
完整代碼
#include<stdio.h>
#include<windows.h>
#include <tlhelp32.h>
#include <string.h>
#include <shlwapi.h>
#include <psapi.h>
enum Type {
Sunlight, Money, TreeHeight, Chocolate, TreeFood, FlowerFood, Insecticide
};
unsigned int offsetTable[10] = { 0x5578,0x50,0x11c,0x250,0x258,0x220,0x224 };
// 根據進程名獲取進程ID
DWORD GetProcessIdByName(const wchar_t* processName) {
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);// 創建一個進程快照
if (snapshot == INVALID_HANDLE_VALUE) {
return 0;// 如果創建失敗,返回 0
}
// 定義一個 PROCESSENTRY32 結構體,用于存儲進程信息
PROCESSENTRY32 processEntry = { 0 };
processEntry.dwSize = sizeof(PROCESSENTRY32); //必須初始化,否則調用Process32First會失敗
if (!Process32First(snapshot, &processEntry)) {
CloseHandle(snapshot);
return 0;// 如果獲取第一個進程信息失敗,關閉進程快照句柄并返回 0
}
// 遍歷進程列表
do {
wchar_t currentProcessName[MAX_PATH]; // 獲取當前進程的名稱
wcscpy_s(currentProcessName, MAX_PATH, processEntry.szExeFile); //szExeFile存儲了進程對應可執行文件的名稱
if (wcscmp(currentProcessName, processName) == 0) {
CloseHandle(snapshot); // 如果當前進程名稱和指定的進程名稱相同,返回進程 ID
return processEntry.th32ProcessID;
}
} while (Process32Next(snapshot, &processEntry)); //獲取快照中下一個進程的信息
// 如果遍歷完整個進程列表都沒有找到指定進程,關閉進程快照句柄并返回 0
CloseHandle(snapshot);
return 0;
}
//根據進程模塊名獲取基址
LPVOID GetModuleBaseAddress(DWORD processId, LPCWSTR moduleName) {
LPVOID lpBaseAddress = NULL;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId); // 打開進程句柄
if (hProcess != NULL) {
// 枚舉進程中的所有模塊
HMODULE hMods[1024];
DWORD cbNeeded;
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
DWORD dwModuleCount = cbNeeded / sizeof(HMODULE);// 計算模塊數量
// 獲取指定模塊的信息
for (DWORD i = 0; i < dwModuleCount; i++) {
TCHAR szModName[MAX_PATH];
//獲取指定模塊的完整路徑名
if (GetModuleFileNameEx(hProcess, hMods[i], szModName, MAX_PATH)) {//函數成功返回字符串長度,注意第四個參數的單位為字符而非字節
if (wcsstr(szModName, moduleName)) {//查找模塊名,若成功則返回子串第一次出現的指針
MODULEINFO modInfo = { 0 };
if (GetModuleInformation(hProcess, hMods[i], &modInfo, sizeof(MODULEINFO))) {//獲取模塊信息并保存到modInfo中
lpBaseAddress = modInfo.lpBaseOfDll;//模塊基地址
break;
}
}
}
}
}
CloseHandle(hProcess); // 關閉進程句柄
}
return lpBaseAddress;
}
//修改進程代碼區代碼 參數: 進程句柄 修改代碼起始地址 硬編碼指針 代碼字節數
BOOL WriteProcessCodeMemory(HANDLE hProcess, LPVOID lpStartAddress, LPCVOID lpBuffer, SIZE_T nSize) {
DWORD dwOldProtect;
//取消頁保護
if (!VirtualProtectEx(hProcess, lpStartAddress, nSize, PAGE_EXECUTE_READWRITE, &dwOldProtect)) {
return FALSE;
}
BOOL bResult = WriteProcessMemory(hProcess, lpStartAddress, lpBuffer, nSize, NULL);//寫入代碼
VirtualProtectEx(hProcess, lpStartAddress, nSize, dwOldProtect, &dwOldProtect);//開啟頁保護
return bResult;
}
//hook指定地址,申請新空間保存原始代碼并寫入hookcode,返回申請空間的地址
LPVOID SetHook(HANDLE hProcess, LPVOID desAddr, LPCVOID hookCode, SIZE_T hookCodeSize, SIZE_T origCodeSize) {
BYTE origCode[10] = { 0 }, jmpCode[5] = { 0xE9,0,0,0,0 };
//1. 讀取并保存原始代碼
if (!ReadProcessMemory(hProcess, desAddr, origCode, origCodeSize, NULL))
return NULL;
//2. 申請空間用于存儲原始代碼,hook代碼,jmp返回代碼
LPVOID allocAddr = VirtualAllocEx(hProcess, NULL, hookCodeSize + origCodeSize + 5, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!allocAddr)
return NULL;
//3. 向申請空間寫入原始代碼,hook代碼,jmp返回代碼 jmp xxx 偏移為目的地址-jmp下一條指令地址
*(DWORD*)(jmpCode + 1) = (DWORD)desAddr + 5 - ((DWORD)allocAddr + hookCodeSize + origCodeSize + 5);//hook返回地址的偏移
if (!WriteProcessCodeMemory(hProcess, allocAddr, origCode, origCodeSize) //寫入原始代碼
|| !WriteProcessCodeMemory(hProcess, (DWORD)allocAddr + origCodeSize, hookCode, hookCodeSize)//寫入hook代碼
|| !WriteProcessCodeMemory(hProcess, (DWORD)allocAddr + origCodeSize + hookCodeSize, jmpCode, 5))//寫入jmpcode
{
VirtualFreeEx(hProcess, allocAddr, 0, MEM_RELEASE);//寫入失敗則釋放空間
return NULL;
}
//4. 修改目的地址處的代碼 jmp xxx偏移 原始代碼后才是需要執行的hook代碼
*(DWORD*)(jmpCode + 1) = ((DWORD)allocAddr + origCodeSize) - ((DWORD)desAddr + 5);
WriteProcessCodeMemory(hProcess, desAddr, jmpCode, 5);//在源地址處寫入跳轉代碼
if (origCodeSize > 5)//原始代碼長度大于5時nop多余字節
{
BYTE nopCode[5] = { 0x90,0x90,0x90,0x90,0x90 };
if (!WriteProcessCodeMemory(hProcess, (DWORD)desAddr + 5, nopCode, origCodeSize - 5))
{
VirtualFreeEx(hProcess, allocAddr, 0, MEM_RELEASE);//寫入nopcode失敗則釋放空間并返回
return NULL;
}
}
//5. hook成功則返回hookCode所在地址
return allocAddr;
}
//取消hook指定地址,寫回原始代碼并釋放申請空間
BOOL UnHook(HANDLE hProcess, LPVOID desAddr, SIZE_T origCodeSize, LPVOID allocAddr) {
BYTE origCode[10] = { 0 };
//1. 從申請空間中讀出原始代碼
if (!ReadProcessMemory(hProcess, allocAddr, origCode, origCodeSize, NULL))
return FALSE;
//2. 將原始代碼寫回目的地址
if (!WriteProcessCodeMemory(hProcess, desAddr, origCode, origCodeSize))
return FALSE;
//3. 釋放申請空間
if (!VirtualFreeEx(hProcess, allocAddr, 0, MEM_RELEASE))
return FALSE;
return TRUE;
}
//獲取某些項目的值
unsigned int getSomething(HANDLE handle, DWORD BaseAddr, unsigned int type) {
unsigned int num = 0;
DWORD addr = BaseAddr + 0x00355E0C;
ReadProcessMemory(handle, (LPVOID)addr, &addr, sizeof(DWORD), NULL);
if (type == Sunlight)
addr += 0x868;
else
addr += 0x950;
ReadProcessMemory(handle, (LPVOID)addr, &addr, sizeof(DWORD), NULL);
addr += offsetTable[type];
ReadProcessMemory(handle, (LPVOID)addr, &num, sizeof(DWORD), 0);
return num;
}
//設置某些項目的值
BOOL setSomething(HANDLE handle, DWORD BaseAddr, unsigned int type, unsigned int num) {
DWORD addr = BaseAddr + 0x00355E0C;
ReadProcessMemory(handle, addr, &addr, sizeof(DWORD), NULL);
if (type == Sunlight)
addr += 0x868;
else
addr += 0x950;
ReadProcessMemory(handle, (LPVOID)addr, &addr, sizeof(DWORD), NULL);
addr += offsetTable[type];
return WriteProcessMemory(handle, (LPVOID)addr, &num, sizeof(DWORD), 0);
}
//無限冷卻
BOOL Uncooled(HANDLE hProcess, DWORD BaseAddr) {
unsigned char code[2] = { 0xeb,0x00 };
return WriteProcessCodeMemory(hProcess, BaseAddr + 0x9ce02, code, 2);//jle 0x18修改為jmp $+2
}
//恢復冷卻
BOOL RecoveryCooling(HANDLE hProcess, DWORD BaseAddr) {
unsigned char OriginalCode[2] = { 0x7E ,0x16 };//jmp $+2修改為jle 0x18
return WriteProcessCodeMemory(hProcess, BaseAddr + 0x9ce02, OriginalCode, 2);
}
//無限陽光,鎖定陽光為9999
BOOL UnlimitedSun(HANDLE hProcess, DWORD BaseAddr) {
unsigned char Code[3] = { 0x29,0xdb,0 };//cmp ebx,eax 修改為sub ebx,ebx and ecx,0x32修改為and ecx,0
BOOL flag;
flag = setSomething(hProcess, BaseAddr, Sunlight, 9999);//修改陽光
flag &= WriteProcessCodeMemory(hProcess, BaseAddr + 0x27690, Code, 2);//修改陽光減少代碼
flag &= WriteProcessCodeMemory(hProcess, BaseAddr + 0x3C0AB, &Code[2], 1);//修改陽光增加代碼
return flag;
}
//恢復陽光消耗
BOOL RecoverySunConsume(HANDLE hProcess, DWORD BaseAddr) {
unsigned char OriginalCode[3] = { 0x3B,0xD8,0x32 };//sub ebx,ebx恢復為cmp ebx,eax and ecx,0恢復為and ecx,0x32
BOOL flag = WriteProcessCodeMemory(hProcess, BaseAddr + 0x27690, OriginalCode, 2);//恢復陽光減少代碼
flag &= WriteProcessCodeMemory(hProcess, BaseAddr + 0x3C0AB, &OriginalCode[2], 1);//恢復陽光增加代碼
return flag;
}
//除霧
LPVOID DeFogByHook(HANDLE hProcess, LPVOID BaseAddr) {
unsigned char hookCode[9] = {
0xc7,0x01,0x00,0x00,0x00,0x00, //mov [ecx],0
0x83,0xc1,0x04 //add ecx,0x4
};
//寫hook代碼進行hook
return SetHook(hProcess, (DWORD)BaseAddr + 0x26173, hookCode, sizeof(hookCode), 5);
}
//恢復霧
BOOL RecoveryFogByUnHook(HANDLE hProcess, LPVOID BaseAddr, LPVOID allocAddr) {
return UnHook(hProcess, (DWORD)BaseAddr + 0x26173, 5, allocAddr);
}
//創建遠程線程向指定進程注入dll
BOOL InjectDllByRemoteThread(DWORD desProcId,WCHAR* dllPath) {
//打開進程獲取進程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, desProcId);
if (!hProcess)
return FALSE;
//申請空間
DWORD pathSize = (wcslen(dllPath) + 1) * 2;
LPVOID newMemAddr = VirtualAllocEx(hProcess, 0, pathSize, MEM_COMMIT, PAGE_READWRITE);
if (!newMemAddr)
return FALSE;
//寫入dll路徑
if (!WriteProcessMemory(hProcess, newMemAddr, dllPath, pathSize, NULL))
{
VirtualFreeEx(hProcess, newMemAddr, 0, MEM_RELEASE);
return FALSE;
}
//創建遠程線程
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, newMemAddr, 0, NULL);
if (!hThread)
{
VirtualFreeEx(hProcess, newMemAddr, 0, MEM_RELEASE);
return FALSE;
}
WaitForSingleObject(hThread, INFINITE);//等待線程信號,保證成功注入
//回收資源
VirtualFreeEx(hProcess, newMemAddr, 0, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
//返回成功
return TRUE;
}
//創建遠程線程釋放指定進程dll
BOOL UnLoadDllByRemoteThread(DWORD dwProcessId, LPCWSTR lpDllName)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (hProcess == NULL)
return FALSE;
// 在目標進程中申請一塊內存,并將需要卸載的DLL模塊的名稱寫入該內存
LPVOID lpRemoteDllName = VirtualAllocEx(hProcess, NULL, (wcslen(lpDllName) + 1) * sizeof(WCHAR), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (lpRemoteDllName == NULL)
{
CloseHandle(hProcess);
return FALSE;
}
if (!WriteProcessMemory(hProcess, lpRemoteDllName, lpDllName, (wcslen(lpDllName) + 1) * sizeof(WCHAR), NULL))
{
VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
//查找dll模塊
HMODULE hModules[1024],DesModule=NULL;
DWORD dwSize = 0;
if (!EnumProcessModules(hProcess, hModules, sizeof(hModules), &dwSize))
{
VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
// 遍歷模塊列表,查找需要卸載的DLL模塊
for (DWORD i = 0; i < (dwSize / sizeof(HMODULE)); i++)
{
WCHAR szModuleName[MAX_PATH] = { 0 };
if (GetModuleFileNameExW(hProcess, hModules[i], szModuleName, MAX_PATH) > 0)
{
// 獲取模塊句柄
if (wcsicmp(szModuleName, lpDllName) == 0)
{
DesModule = hModules[i];
}
}
}
//沒有查找到模塊
if (!DesModule) {
VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
// 在目標進程中創建遠程線程,執行FreeLibrary函數
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)FreeLibrary, DesModule, 0, NULL);
if (hThread == NULL)
{
VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
// 等待線程執行完成
WaitForSingleObject(hThread, INFINITE);
// 關閉句柄
CloseHandle(hThread);
VirtualFreeEx(hProcess, lpRemoteDllName, 0, MEM_RELEASE);
CloseHandle(hProcess);
return TRUE;
}
//以創建遠程線程方式種植植物
BOOL GrowPlantByInjectCode(DWORD dwProcessId,DWORD BaseAddr,DWORD x,DWORD y,DWORD PlantType)
{
BOOL bSuccess = FALSE;
//1. 打開進程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (hProcess != NULL)
{
//2. 定義注入代碼(函數)
BYTE InjectCode[50] = { //匯編指令 //修正點偏移
0x55, //0 push ebp
0x89, 0xE5, //1 mov ebp,esp
0x60, //3 pushad
0x68, 0xFF, 0xFF, 0xFF, 0xFF, //4 push -1
0x68, 0x00, 0x00, 0x00, 0x00, //9 push PlantType //10
0xB8, 0x00, 0x00, 0x00, 0x00, //14 mov eax,y //15
0x68, 0x00, 0x00, 0x00, 0x00, //19 push x //20
0xB9, 0x00, 0x00, 0x00, 0x00, //24 mov ecx,BaseAddr //25
0x8B, 0x89, 0x0C, 0x5E, 0x35, 0x00, //29 mov ecx,[ecx+0x355E0C]
0x8B, 0x89, 0x68, 0x08, 0x00, 0x00, //35 mov ecx,[ecx+0x868]
0x51, //41 push ecx
0xE8, 0x00, 0x00, 0x00, 0x00, //42 call PlantFunc //43 //被調方平棧
0x61, //47 popad
0xC9, //48 leave
0xC3 //49 ret
};
//3. 申請空間用于存儲代碼
DWORD dwCodeSize = 50, desFunc = BaseAddr + 0x18D70;
LPVOID lpRemoteCodeMem = VirtualAllocEx(hProcess, NULL, dwCodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
//4. 修正參數
*(DWORD*)&InjectCode[10] = PlantType;
*(DWORD*)&InjectCode[15] = y;
*(DWORD*)&InjectCode[20] = x;
*(DWORD*)&InjectCode[25] = BaseAddr;
*(DWORD*)&InjectCode[43] = desFunc-((DWORD)lpRemoteCodeMem+42+5) ;
//call指令與jmp類似,相對于當前指令的下一條指令計算偏移,offset=des-(source+5),減去call自身長度5
if (lpRemoteCodeMem != NULL)
{
SIZE_T dwBytesWritten = 0;
//5. 注入代碼
if (WriteProcessMemory(hProcess, lpRemoteCodeMem, InjectCode, dwCodeSize, &dwBytesWritten) &&
dwBytesWritten == dwCodeSize)
{
//6. 創建遠程線程執行代碼
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRemoteCodeMem,NULL, 0, NULL);
if (hThread != NULL)
{
//7. 等待線程信號
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
bSuccess = TRUE;
}
}
//8. 執行完后釋放空間
VirtualFreeEx(hProcess, lpRemoteCodeMem, 0, MEM_RELEASE);
}
CloseHandle(hProcess);
}
return bSuccess;
}
//以創建遠程線程方式種植僵尸
BOOL GrowZombieByInjectCode(DWORD dwProcessId,DWORD BaseAddr, DWORD x, DWORD y, DWORD ZombieType) {
BOOL bSuccess = FALSE;
//1. 打開進程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (hProcess != NULL)
{
//2. 定義注入代碼(函數)
BYTE InjectCode[50] = {
0x55, //0 push ebp
0x89, 0xE5, //1 mov ebp,esp
0x60, //3 pushad
0x68, 0x00, 0x00, 0x00, 0x00, //4 push x
0x68, 0x00, 0x00, 0x00, 0x00, //9 push ZombieType
0xB8, 0x00, 0x00, 0x00, 0x00, //14 mov eax,y
0xB9, 0x00, 0x00, 0x00, 0x00, //19 mov ecx,BaseAddr
0x8B, 0x89, 0x0C, 0x5E, 0x35, 0x00, //24 mov ecx,[ecx+0x355E0C]
0x8B, 0x89, 0x68, 0x08, 0x00, 0x00, //30 mov ecx,[ecx+0x868]
0x8B, 0x89, 0x78, 0x01, 0x00, 0x00, //36 mov ecx,[ecx+0x178]
0xE8, 0x00, 0x00, 0x00, 0x00, //42 call PlantZombieFunc
0x61, //47 popad
0xC9, //48 leave
0xC3 //49 ret
};
//3. 申請空間用于存儲代碼
DWORD dwCodeSize = 50, desFunc = BaseAddr + 0x35390; //種植僵尸函數
LPVOID lpRemoteCodeMem = VirtualAllocEx(hProcess, NULL, dwCodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
//4. 修正參數
*(DWORD*)&InjectCode[5] = x;
*(DWORD*)&InjectCode[10] = ZombieType;
*(DWORD*)&InjectCode[15] = y;
*(DWORD*)&InjectCode[20] = BaseAddr;
*(DWORD*)&InjectCode[43] = desFunc - ((DWORD)lpRemoteCodeMem + 42 + 5);//call指令與jmp類似,相對于當前指令的下一條指令計算偏移,要減去call長度5
if (lpRemoteCodeMem != NULL)
{
SIZE_T dwBytesWritten = 0;
//5. 注入代碼
if (WriteProcessMemory(hProcess, lpRemoteCodeMem, InjectCode, dwCodeSize, &dwBytesWritten) &&
dwBytesWritten == dwCodeSize)
{
//6. 創建遠程線程執行代碼
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRemoteCodeMem, NULL, 0, NULL);
if (hThread != NULL)
{
//7. 等待線程信號
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
bSuccess = TRUE;
}
}
//8. 執行完后釋放空間
VirtualFreeEx(hProcess, lpRemoteCodeMem, 0, MEM_RELEASE);
}
CloseHandle(hProcess);
}
return bSuccess;
}
//設置卡槽植物
BOOL SetPlantCard(HANDLE hProcess,DWORD BaseAddr,DWORD nCard,DWORD plantType) {
DWORD cardAddr = BaseAddr + 0x355E0C;
ReadProcessMemory(hProcess, cardAddr, &cardAddr, sizeof(DWORD), NULL);
cardAddr += 0x868;
ReadProcessMemory(hProcess, cardAddr, &cardAddr, sizeof(DWORD), NULL);
cardAddr += 0x15C;
ReadProcessMemory(hProcess, cardAddr, &cardAddr, sizeof(DWORD), NULL);
cardAddr += 0x5C+nCard*0x50;//卡槽偏移
return WriteProcessMemory(hProcess, cardAddr, &plantType, sizeof(DWORD), NULL);
}
//選擇菜單
void choiceMenu(HANDLE hProcess,DWORD Pid, LPVOID BaseAddr) {
DWORD choice = 0;
unsigned int num = 0;
DWORD fogAddr = 0;
unsigned int x, y, Type;
while(1) {
system("cls");
printf("\t\t\t\tWelcome to PVZ Modifier!\n");
printf("\t\t\t\t\t0.退出\n");
printf("\t\t\t\t\t1.修改陽光數\n");
printf("\t\t\t\t\t2.修改金錢數\n");
printf("\t\t\t\t\t3.修改智慧樹高\n");
printf("\t\t\t\t\t4.修改巧克力數\n");
printf("\t\t\t\t\t5.修改樹肥\n");
printf("\t\t\t\t\t6.修改花肥\n");
printf("\t\t\t\t\t7.修改殺蟲劑\n");
printf("\t\t\t\t\t8.無限冷卻\n");
printf("\t\t\t\t\t9.恢復冷卻\n");
printf("\t\t\t\t\t10.無限陽光\n");
printf("\t\t\t\t\t11.恢復陽光消耗\n");
printf("\t\t\t\t\t12.除霧\n");
printf("\t\t\t\t\t13.恢復霧\n");
printf("\t\t\t\t\t14.種植植物\n");
printf("\t\t\t\t\t15.生成僵尸\n");
printf("\t\t\t\tPlease choose your option:[ ]\b\b");
scanf("%d", &choice);
switch(choice){
case 0:
return;
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
printf("\t\t\t\tPlease input Num:");
scanf("%d", &num);
setSomething(hProcess, BaseAddr, choice - 1, num);
break;
case 8:
Uncooled(hProcess, BaseAddr);
break;
case 9:
RecoveryCooling(hProcess, BaseAddr);
break;
case 10:
UnlimitedSun(hProcess,BaseAddr);
break;
case 11:
RecoverySunConsume(hProcess, BaseAddr);
break;
case 12:
fogAddr=(DWORD)DeFogByHook(hProcess, BaseAddr);
break;
case 13:
RecoveryFogByUnHook(hProcess, BaseAddr,fogAddr );
break;
case 14:
printf("請輸入X Y PlantType: ");
scanf("%d%d%d", &x, &y, &Type);
GrowPlantByInjectCode(Pid, BaseAddr,x,y,Type );
break;
case 15:
printf("請輸入X Y ZombieType: ");
scanf("%d%d%d", &x, &y, &Type);
GrowZombieByInjectCode(Pid, BaseAddr, x, y, Type);
break;
}
}
}
int main() {
//獲取進程pid
DWORD Pid = GetProcessIdByName(L"PlantsVsZombies.exe");
//打開進程,獲取進程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE,Pid);
//獲取進程基址
DWORD BaseAddr=GetModuleBaseAddress(Pid, L"PlantsVsZombies.exe");
choiceMenu(hProcess, Pid, BaseAddr);
//dll注入
//InjectDllByRemoteThread(Pid, L"E:\\MyProject\\vsProjects\\Project1\\Debug\\DllPlant3.dll");
//int op = 1;
//printf("輸入0卸載dll:");
//scanf("%d", &op);
//if(op==0)
// UnLoadDllByRemoteThread(Pid, L"E:\\MyProject\\vsProjects\\Project1\\Debug\\DllPlant3.dll");//加載完dll之后釋放掉
CloseHandle(hProcess);
return 0;
}
DLL代碼
#include<windows.h>
#include<stdio.h>
//調用函數
BOOL GrowPlant(DWORD BaseAddr, DWORD x, DWORD y, DWORD TypePlant) {
LPVOID PlantFunc = BaseAddr + 0x18D70;
__asm {
pushad
push -1 //-1
push TypePlant //植物類型
mov eax, y //y
push x //x
mov ecx, BaseAddr
mov ecx, [ecx+0x355E0C]
mov ecx, [ecx + 0x868]
push ecx
call PlantFunc
popad
}
return TRUE;
}
BOOL GrowZombie(DWORD BaseAddr, DWORD x, DWORD y, DWORD ZombieType) {
LPVOID PlantZombieFunc = BaseAddr + 0x35390;
__asm {
pushad
push x
push ZombieType
mov eax,y
mov ecx,BaseAddr
mov ecx,[ecx+0x355E0C]
mov ecx,[ecx+0x868]
mov ecx,[ecx+0x178] //ebp
call PlantZombieFunc
popad
}
return TRUE;
}
BOOL WINAPI DllMain(HMODULE hInstance, DWORD fdwReason, LPVOID lpReserved) {
DWORD BaseAddr = GetModuleHandle(NULL);
DWORD pid = GetCurrentProcessId();
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
MessageBoxW(0, L"ProcessAttachDll!", L"window2", 0);
GrowPlant(BaseAddr,5,3,23);
GrowZombie(BaseAddr, 6, 2, 23);
break;
/* case DLL_THREAD_ATTACH:
printf("ThreadAttach!\n");
break;
case DLL_THREAD_DETACH:
if (lpReserved == NULL)
{
FreeLibrary(hInstance);
}
break;*/
case DLL_PROCESS_DETACH:
MessageBoxW(0, L"ProcessDeTachDll!", L"window2", 0);
break;
}
return TRUE;
}