植物大战僵尸:代码实现无限阳光
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
植物⼤战僵⼫:代码实现⽆限阳光
本次实验内容:通过逆向分析植物阳光数量的动态地址找到阳光的基址与偏移,从⽽实现每次启动游戏都能够使⽤基址加偏移的⽅式定位阳光数据,最后我们将通过使⽤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);
}。