【逆向工程】逆向植物大战僵尸 以及关于基址和多级指针的追根究底

本文已参与「新人创作礼」活动,一起开启掘金创作之路。 看了几个视频都发现就只会让你一级一级的找偏移而不是告诉你为什么CE不能直接搜索到一个数据的基址,为什么会有多级偏移,为什么通过多级偏移就能找到一个数据 所以这里就主要探究下原理

因为X64DBG内存断点是真的拉跨,这里还里还是OD了 @TOC

为什么不能直接扫描到阳光数量的基地址?

静态地址(基址) 和 动态地址

话不多说CE开扫,改变阳光数量,得到本次进程阳光的动态地址0x12A473A0

CE中静态地址(或者说基址)以绿色字体显示,静态地址可以理解为全局变量静态变量在内存空间中的地址,在一个程序编译时,就已经确定其在进程的内存中空间地址了除非更改代码静态地址在每次加载程序中都不会改变,学过PE结构的应该都知道,其值大多数都放在data段中。 12A473A0这个字体是黑色的,在CE中代表动态地址,可以理解成局部变量,是在里面的,每次加载程序地址是会改变,是不确定的,关于栈以及局部变量为什么地址是动态的可以看这篇文章C/C++的反汇编表示详解(1)函数调用,栈平衡,变量与参数的内存布局中的局部变量

所以在游戏逆向中的基址,不是指PE结构中程序载入基地址ImageBase,而是指一个结构或变量的静态地址,其可能是一个全局指针的地址,也有可能是一个全局变量的地址,还有可能是一个全局对象的首地址,只要其结构或变量是的地址是静态的,即可称为一个基址

因为阳光数是一个局部变量,其地址值是动态的,所以无法用CE直接找到其静态地址(基址)

但很明显,如果阳光数为一个局部变量是不可取的,因为其重要性必定会在程序的许多代码段中都会被调用

既然如此那肯定有个全局的数据类型可以找到阳光这个局部变量的值,那这就肯定是一个全局指针啦

多级指针 和 多级偏移

这里我们写个Demo来模拟一下怎么通过一个全局指针来找到局部变量阳光的值

#include <stdio.h>
/*僵尸类 里面有僵尸的各类属性*/
class Zombies{
public:
	int type;
	int hP;
};
/*植物类里面包含有阳光值这个属性*/
class Plant{
public:
	int type;
	int hp;
	int sun;//模拟的阳光值
};
/*一个集合类 里面包含了指向各类的指针*/
class Game{
public:
	Zombies* pZombies;
	Plant* pPlant;
};
/*一个全局执政game 指向一个全局对象Game */
Game* game=new Game;

int main() {
/*
对象Plant是动态new出来的 但game其地址是确定的
*/
	game->pPlant=new Plant;
	game->pPlant->sun=11223344;
	return 0;
} 

我们查看一下反汇编


26: game->pPlant=new Plant;
004010B8 push0Ch
/*
调用类的构造函数 在堆中创建一个局部对象
*/
004010BA calloperator new (00401150) 
004010BF add esp,4
004010C2 mov dword ptr [ebp-4],eax
004010C5 mov eax,[game (00427d4c)]
004010CA mov ecx,dword ptr [ebp-4]
/*
将结构首地址传给全局指针game->pPlant
*/
004010CD mov dword ptr [eax+4],ecx
27: game->pPlant->sun=11223344;
004010D0 mov edx,dword ptr [game (00427d4c)] //EDX为game对象地址
004010D6 mov eax,dword ptr [edx+4] //EAX为指针pPlant
004010D9 mov dword ptr [eax+8],0AB4130h //sun值 

我们用CE搜索一下,看看我们的Demo是否满足不能被直接搜索到基地址的情况,发现上述Demo确实满足模拟的阳光值无法被搜索到

因为植物对象plant是动态new出来的,是一个局部变量,其值是不确定的,就导致我们无法直接搜索到阳光的基地址。但因为全局指针game的地址在代码编译时就已经确定了,所以我们就可以通过game指针的地址来找到阳光的动态地址

所以我们要找的就是 一个全局的指向结构 的首地址 的指针的地址

上面的有点绕口,可以多读几遍,就如同上面的Demo一样,阳光这个结构可能被多层结构嵌套指向,所以地址值可能有多次偏移,这就有了一级指针二级指针多级指针这个说法,在上述Demo中,game这个全局指针可以理解为一级指针,pPlant可以理解为二级指针,如果再有多级嵌套的话,就会有三级指针等等,而一级指针的地址就是我们要找的阳光的基地址 再次注意这里要找的是 该全局指针的地址 而不是其指向的地址

通过动态地址 获取 静态地址(基址)

根据上面的推测,我们可以通过静态地址获取到动态地址,那么逆推根据阳光的动态地址获取静态地址(也是可行的)

我们首先获取到包含阳光这个结构的首地址,也就是一个指针类似于Demo中的指针pPlant,但我们没有源代码所以暂时无法确定这是个几级指针

查看是什么改写了其值


 EDI=0B63FEE0
0041BA76 - mov [edi+00005560],esi 

很明显edi即是包含阳光这个结构的首地址,阳光值就包含在这个结构中,edi也就是一个pPlant指针的的地址,我们CE搜索一下其地址,看看其地址是否是个静态地址,若是静态地址,则他就不是pPlant指针,而是一个game一级指针,也就是我们最终要找的那个指针。

这时候千万要注意,这次是查看 是查看谁访问了这个地址 不是改写了地址 因为其地址在本次运行的时候都已经确定指向哪一个结构了,要不然也无法改写阳光的数值,但要改写阳光的数值就必定需要来访问各级指针来寻找阳光的地址

找到了全局指针的地址(基地址)后,这里就不多赘述了,主要目的是认识多级指针和多级偏移的概念。修改阳光数值代码如下。至于为什么会有多个基址,则是有多个全局指针都指向了包含阳光值这个结构的地址

 INT sunNumber=你要改写的阳光数;DWORD writeNum;DWORD addr01;DWORD addr02;/*获取全局指针(基址)指向的地址 即二级指针的地址*/ReadProcessMemory(this->hProcess,(LPCVOID)0x006A9EC0,&addr01,sizeof (addr01),&this->pID);//进程句柄 基地址/*根据二级指针指向的地址 获取本次进程阳光的地址*/ReadProcessMemory(this->hProcess,(LPCVOID)(addr01+0x768),&addr02,sizeof (addr02),&this->pID);/*获取改写阳光数*/WriteProcessMemory(this->hProcess,(LPVOID)(addr02+0x5560),&sunNumber,sizeof (sunNumber),&writeNum); 

使食人花吃僵尸无CD

要想定位到食人花吃僵尸的CD计数器,就要通过CE的模糊搜索来找到其计数器的值,通过其值来找到计数器的代码

CE模糊搜索到三个数值

很明显可以看出,[edi+54]就是食人花攻击后的冷却时间计时器


004632458B47 54 mov eax, dword ptr [edi+54]
0046324885C0testeax, eax ;判断计时器是否为 0
0046324A7E 06 jle short 00463252 ;不为 0 则减 1
0046324C83C0 FF add eax, -1
0046324F8947 54 mov dword ptr [edi+54], eax;计时器
00463252|>8B0Fmov ecx, dword ptr [edi]
00463254|.E8 E705FFFF call00453840 

那么可以推断,当每次食人花攻击前,都会判断一下该计时器是否为0,所以我们还要找到食人花攻击的代码段。 最显眼的当然是紧跟计时器代码段下面的一个CALL,在此处下断点发现食人花依旧还是会有攻击冷却。

我们接着往下看,发现了一条Switch语句,在此处下断点调试,发现其功能是绘制各种植物的攻击动作。

既然如此,那其中大概率会有食人花的动作,而其每次攻击前判断计时器是否为零的语句也大概率在其中 其Switch是根据[EDI+24]来判断对应的case语句,我们下断点调试,查看食人花对应的case语句是哪一条,最后会发现其对应的是case 6 这条语句,其中会执行一个CALL 我们跟进去看看

跟进去会发现该代码段很长,但我们已经知道了[EDI+54]是计时器的值,所以只需要着重找含有[EDI+54]的指令就可以了,其他指令可以先忽略掉


 /* 两条nop指令 */
	WORD shellCoded=0x9090;
	/* 原jle指令 */WORD shellCode=0x5F75;DWORD writeNum;if(arg1== Qt::Checked){/* 用来修改 */WriteProcessMemory(this->hProcess,(LPVOID)(0x461565),&shellCoded,sizeof (shellCoded),&writeNum);}else{/* 用来恢复 */WriteProcessMemory(this->hProcess,(LPVOID)(0x461565),&shellCode,sizeof (shellCode),&writeNum);} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的Kiko君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值