植物大战僵尸修改器制作--从入门到入土
一 基础准备
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; }
