《植物大战僵尸》是一款非常经典的塔防类游戏,由PopCap Games
公司开发并先后在多个平台上推出。主要玩法为种植各种攻击性植物,抵御僵尸攻击,该游戏可以说绝大多数九零后都接触或者玩过,本章将通过逆向分析技术对该游戏进行分析,并实现一些游戏之外的功能,以此让用户理解二进制安全技术的应用范围。
本次实验将介绍如何寻找任意种植CALL调用,所谓的任意种植指的是我们可以通过外部CALL调用的方式实现对植物的任意放置,通常情况下游戏中的格子是无法叠加种植的,但当我们找到了任意种植CALL调用时也就实现了叠加种植的效果,本次实验将通过两种方式分析并实现植物的任意种植效果,并通过注入代码实现通用辅助。
首先我们需要找到植物种植CALL调用,种植CALL调用的遍历技巧如下所示;
- 打开修改器 > 回到游戏 > 然后回到CE > 扫描未知初始数
- 回到游戏 > 拿起向日葵(不要种) > 搜索变动的数值 > 回到游戏(不要动) > 搜索未变动的数值
- 回到游戏 > 放下向日葵 > 拿起豌豆射手 > 搜索变动的数值 > 以此重复进行直到找到为止
当我们使用鼠标点击时会在一个地址写入值,当换个植物则会换一个数值,这样如此反复就会找到CALL的地址,经过上方的排查读者应该能看到两个相近的内存地址,我们此处就选择134E7658
这个地址,当我们放下植物时这个值会保持在4294967295
范围内,如下图所示;
接着我们在该地址上面点击找出是什么访问了这个地址
,回到游戏中我们再次点击植物时会看到如下图所示的输出结果,其中的地址0041239A
则是触发植物时所出现的一条汇编指令;
- 0041239A - 89 41 28 - mov [ecx+28],eax
此时我们关闭CE修改器,打开x64dbg
调试器并附加到游戏进程上,运行程序,并通过Ctrl+G
跳转到0041239A
内存区域内,并在此处下断点,回到游戏拿起植物此处则会断下,当读者通过不断栈回溯最后读者应该能找到0x00410A8C
处的跳转地址,当读者在此处下段点时会发现,当我们拿起植物并种下时此处就会被断下,而经过分析如果此处没有跳转则植物会被种下,而一旦跳转实现则植物将不会被种下,也就可以说明此处的jne 0x00410AD9
就是植物种植的关键位置,而紧随其后的call 0x0040D120
则是关键的种植CALL;
为了验证种植CALL的参数传递,此处我们在第一个格子中种植一个豌豆射手,并观察堆栈传递情况,当种下豌豆射手时参数传递如下图所示;
我们让程序运行起来,并在一个新的格子里种植一个向日葵,此时观察并对比参数传递情况,读者应该能看出两个植物种植所需参数之间的变化;
如上两张图的对比已经非常明显了,我们直接使用代码注入器注入分别将push 5 改成1,2,3
,然后用mov eax,2
控制在第几列种植,即可实现叠加效果。
push 0xFFFFFFFF push 5 mov eax,2 push 0 push ebp call 0x0040D120
|
为了实现自定义注入代码,此处的push 0x1359D108
还需要定位来源,此处读者可自行寻找push ebp
中EBP寄存器内的值,这里为了节约篇幅不在演示重复的内容,当读者找到了这段调用代码,则可以编写一段代码实现全屏种植效果,当前格子为4*8
的矩阵,那么首先这段代码则可写成如下样子;
#include <iostream> #include <windows.h> #include <string> #include <tlhelp32.h>
DWORD GetPidByName(const char* name) { HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) }; DWORD pid = 0;
if (Process32First(snapshot, &pe32)) { do { if (_stricmp(pe32.szExeFile, name) == 0) { pid = pe32.th32ProcessID; break; } } while (Process32Next(snapshot, &pe32)); } CloseHandle(snapshot); return pid; }
void MyASM() {
for (int x = 0; x < 5; x++) { for (int y = 0; y < 9; y++) { __asm { pushad push 0xFFFFFFFF push 5 mov eax, x push y push 0x14ED3C68 mov ebx, 0x40D120 call ebx popad } } } }
BOOL InjectCode(DWORD dwProcId, LPVOID mFunc) { HANDLE hProcess, hThread; LPVOID mFuncAddr, ParamAddr; DWORD NumberOfByte;
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcId); if (hProcess == NULL) { return FALSE; }
printf("[*] 打开目标进程 \n");
mFuncAddr = VirtualAllocEx(hProcess, NULL, 128, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (mFuncAddr == NULL || mFuncAddr == 0) { return FALSE; }
printf("[*] 分配内存空间 \n");
if (!WriteProcessMemory(hProcess, mFuncAddr, mFunc, 128, &NumberOfByte)) { return FALSE; }
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)mFuncAddr, ParamAddr, 0, &NumberOfByte); if (hThread == NULL) { return FALSE; }
printf("[*] 创建远程线程 \n");
WaitForSingleObject(hThread, INFINITE);
BOOL virtual_flag = VirtualFreeEx(hProcess, mFuncAddr, 128, MEM_RELEASE); if (virtual_flag == FALSE) { return FALSE; }
CloseHandle(hThread); CloseHandle(hProcess);
return TRUE; }
int main(int argc, char *argv[]) { DWORD Pid = GetPidByName("PlantsVsZombies.exe");
BOOL ref = InjectCode(Pid, MyASM); if (ref == TRUE) { printf("[+] 代码注入完成 \n"); }
system("pause"); return 0; }
|
通过手动替换EBP
中的值,此处为push 0x14ED3C68
并运行这段代码,则可实现全屏种植的效果,如下图所示;
既然找到了种植CALL的地址00410A94
,那我们可以猜测,植物在种下之前是否会判断放入方格中是否有植物呢? 答案是肯定的,当我们在一个空地上种植的时候,我们能够种上说明条件成立,那如果方格中有植物则无法完成种植,条件也就不会成立,由此可猜到这里应该是使用一个条件判断来控制的,下面我们就去寻找这个条件判断的位置。
我们打开x64dbg
并通过Ctrl+G
跳转到0x410A94
并在程序的段首0x40FD30
处下断点,依次将过程中的关键判断条件进行断点测试,当读者运行到0x40FE2F
当此处的JE指令跳转实现则我们即可实现叠加种植的效果,而如果没有跳则无法实现叠加,此处只需要将跳转改为JMP
即可实现叠加;
通过编程的方式实现跳转,只需要将0040FE2F
处替换为E9 20 09 00 00 90
即可,实现代码如下所示;
#include <iostream> #include <windows.h> #include <tlhelp32.h>
using namespace std;
DWORD GetPidByName(const char* name) { HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) }; DWORD pid = 0;
if (Process32First(snapshot, &pe32)) { do { if (_stricmp(pe32.szExeFile, name) == 0) { pid = pe32.th32ProcessID; break; } } while (Process32Next(snapshot, &pe32)); } CloseHandle(snapshot); return pid; }
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);
return WriteProcessMemory(handle, (LPVOID)Base, Buff, Size, NULL); }
int main(int argc, char *argv[]) { DWORD pid = GetPidByName("PlantsVsZombies.exe"); printf("[*] 进程PID = %d \n", pid);
unsigned char AutoSun[6] = { 0xE9, 0x20, 0x09, 0x00, 0x00, 0x90 };
BOOL ref = WriteByteSet(pid, 0x40FE2F, AutoSun, 6); if (ref == TRUE) { printf("[*] 替换已开启 \n");
for (size_t i = 0; i < sizeof(AutoSun); i++) { printf("%x \n", AutoSun[i]); } } else { printf("[*] 替换数据失败 \n"); }
system("pause"); return 0; }
|
叠加种植效果如下图所示;