植物大战僵尸修改器开发逆向与应用

admin 2025年2月12日23:08:12评论9 views字数 5554阅读18分30秒阅读模式


基础准备

1.1 工具

①植物大战僵尸原版 (英文名PlantsVsZombies, 简称PVZ)

②CheatEngine (CE)

③IDA

④VS

1.2 基础

①C丶C++丶Asm丶逆向基础

②Win32开发基础

③ImGui基础


Board 类

2.1 阳光查找

对于作弊小子而言, CE一定不陌生, 网上的CE入门教学基本都是拿PlantsVsZombies开刀。

而入门的第一课通常是找阳光, CE的基操此处不过多赘述, 因为目标是逆向。先找到阳光地址178D1028:

植物大战僵尸修改器开发逆向与应用

2.2 CE+IDA分析数据来源

右键阳光地址查找访问, 可以看到有两处不断访问阳光地址, 分别是00489825和0041BAB5

植物大战僵尸修改器开发逆向与应用

选择00489825显示反汇编程序来到PlantsVsZombies.exe+89825 , mov eax,[esi+5560]这行代码在不断访问阳光。

esi很明显就是对象的首地址(this), 5560 为阳光的偏移, 下断点后得知esi == 178CBAC8。

植物大战僵尸修改器开发逆向与应用

使用CE和IDA动静态结合分析:

①CE在函数头下断点, 取出栈顶(esp)的返回地址(CE右下角的调用堆栈不可靠, 需要手动取)

②IDA跳到返回地址处, 往上找函数头, 将函数头复制到CE再下断点。重复操作, 查找this来源

从访问阳光处开始:

复制代码地址PlantsVsZombies.exe+89825(00489825)到IDA中, 往上找到函数头489630

植物大战僵尸修改器开发逆向与应用

复制函数头到CE跳过去下断点, 从ESP取出返回地址, 重复操作。

植物大战僵尸修改器开发逆向与应用

回溯调用堆栈:

0041A21A

004172EC

0041AE38

反复操作的过程中, 在函数sub_41ACF0中看到一行有意思的代码。

push offset aBoardDraw, 压入的字符串"Board::Draw"值得注意。

如果玩过C++, 不难看出Board是一个类名/命名空间, 而Draw则是函数名。

植物大战僵尸修改器开发逆向与应用

在VS中, 使用__FUNCTION__即可得到验证, 因此在sub_41ACF0中使用的字符串, 极大概率是调试输出的函数信息。此处可以给sub_41ACF0命名为Board::Draw。

植物大战僵尸修改器开发逆向与应用

搜索字符串Board, 可以看到还有个Board::Update

植物大战僵尸修改器开发逆向与应用

交叉引用过去, 该字符串引用自sub_415D40, 与Board::Draw一样, 大概率也是调试输出信息, 同样给它命名。

植物大战僵尸修改器开发逆向与应用

在Board::Update处下断点, 得知ecx == 178CBAC8, 即上边找阳光时的this。

而this在寄存器ecx, 这符合thiscall调用约定, 即第一个参数在ecx。

植物大战僵尸修改器开发逆向与应用

综上所述, 基本确定Board是一个类, 在Board的翻译中有"棋盘"的意思, 游戏的关卡形容成棋盘很贴切。

2.3 Board虚表

对着Board::Update下断点, 在调用处可以看到多态的操作。

ecx是this, 而this来源自于esi, 上边的mov edx,[esi]是非常经典的取虚表操作。

植物大战僵尸修改器开发逆向与应用

虚表为656CA8, 在IDA中跳过去, 可以看到Board::Update和Board::Draw都记录在其中, 这里将其命名为BoardVTable。

植物大战僵尸修改器开发逆向与应用

在C++中,构造填充虚表, 析构还原虚表。即构造和析构都涉及到虚表的引用。

找到虚表, 定位构造和析构函数只是一个交叉引用的事情。

对着BoardVTable交叉引用, 有两个函数引用了虚表, 分别是sub_407B50和sub_408690

植物大战僵尸修改器开发逆向与应用

sub_408690中调用了一堆free释放资源, 毫无疑问是析构, 另一个就是构造, 分别命名为Board::Ctor和Board::Dtor。

植物大战僵尸修改器开发逆向与应用


PVZ 类

在返回主菜单/关掉游戏后, 地址会变动, 用CE玩家的话讲, 这不是基址, 接下来需要找到基址。

经过前边的折腾, 定位到了与Board类相关的函数, 接下来的逆向分析要简单的多。

这里选择Board::Ctor进行分析, 因为创建对象时肯定要调用Board::Ctor。

交叉引用可以看到熟悉的new, new完后返回的指针给了[edi+768]。

植物大战僵尸修改器开发逆向与应用

在mov [edi+768],eax处下断点, 进入游戏关卡后会断下, 从而拿到this指针(edi == 02C79E60)。

查看this对应的内存, 可以看到首地址处的两张虚表, 即00667BA0和00667D50(怎么看出的? 因为和BoardVTable挨的太近)。

植物大战僵尸修改器开发逆向与应用

掏出00667BA0这张虚表, 到IDA里交叉引用, 轻松定位到构造和析构。

sub_44EAA0为构造, sub_44EDF0为析构。

植物大战僵尸修改器开发逆向与应用

在构造中可以看到一些初始化信息, 这个类的名称大概率和PlantsVsZombies有关, 这里姑且将类命名为PVZ。

sub_44EAA0命名为PVZ::Ctor, sub_44EDF0命名为PVZ::Dtor

植物大战僵尸修改器开发逆向与应用

同样对着PVZ::Ctor交叉引用, 熟悉的名称映入眼帘, 是老朋友WinMain

植物大战僵尸修改器开发逆向与应用

在WinMain中, new PVZ之后, 将返回的指针储存在6A9EC0, 6A9EC0这个地址常量相当于二级指针, 姑且命名为GPVZ。

植物大战僵尸修改器开发逆向与应用

手动添加地址, 将逆向出来的数据添加到其中并保存, 以便后续分析。

GPVZ = [6A9EC0]

Board = [GPVZ+768]

阳光 = [Board+5560]

植物大战僵尸修改器开发逆向与应用

扯了这么多, 总不能一直纸上谈兵, 该上代码意思一下了。

此处仅仅是测试, 简单写个Dll注入到PVZ中改一下阳光。

植物大战僵尸修改器开发逆向与应用

植物大战僵尸修改器开发逆向与应用

使用CE将Dll注入后, 进入关卡中阳光不断自增, 溢出7FFFFFFF后, 游戏应该做了处理, 负数都显示为0

植物大战僵尸修改器开发逆向与应用

这里的代码仅仅是测试, 在后边会使用ImGui结合逆向的内容进行修改器开发, 也会开源该项目。


游戏对象

棋盘中有植物丶僵尸丶小推车等, 找到这些数据储存的位置, 我们便可以随意拿捏这些对象。

对象在哪? 对象相关的数据哪个更好找? 毫无疑问, 对象的个数无疑是最合适的切入点。

使用CE搜索植物个数, 植物增加+1, 铲除植物-1, 找到了两个结果, 分别为20FB88E8和20FB88EC。

植物大战僵尸修改器开发逆向与应用 

选中20FB88EC右键查找访问, 种植植物时41DEE7处的代码访问了该地址, esi是this。

植物大战僵尸修改器开发逆向与应用

同样在函数头下断点, 查找this来源。

在调用处可以看到, esi源于ebp+AC, 而ebp正是我们熟悉的Board。

通过lea esi,[ebp+AC]这行指令, 得知Board+AC位置储存着植物对象相关信息。

植物大战僵尸修改器开发逆向与应用

此时前边逆向工作带来的优势已逐步体现, 与植物对象相关的信息在Board中。

对象的初始化工作, 肯定在Board::Ctor中, IDA跳到Board::Ctor。

找到给偏移AC赋值的地方, 附近有着丰富的信息提示。

植物大战僵尸修改器开发逆向与应用

根据字符串plants的提示, 猜测Board+AC指向的是植物对象的数组。使用CE的结构分析, 将Board的地址填入, 在偏移AC处展开。

植物大战僵尸修改器开发逆向与应用

发现58处一直在减少, 当改成0时, 后边的向日葵立刻产生阳光。右键添加到地址列表, 改成0锁定, 向日葵发泄般的喷阳光。

植物大战僵尸修改器开发逆向与应用

此时可以确定了对象数组信息都在Board中, 根据Board::Ctor中的信息, 将各种数组信息的位置逆向出来:

Board+90: zombies(僵尸)

Board+AC: plants(植物)

Board+C8: projectiles(子弹)

Board+E4: coins(货币)

Board+100: lawnmowers(小推车)

Board+11C: griditems(网格物品, 钉耙和梯子等对象储存在此)

观察这些偏移, 都是相差1C, 即数组信息大小相同, 但不同对象的大小肯定不同

从这里能闻出类模板的味儿, 很像std::vector

经过分析, 数组信息对应的结构体如下图Vector所示

植物大战僵尸修改器开发逆向与应用

进一步完善Board类

植物大战僵尸修改器开发逆向与应用

知道了植物丶僵尸等数组的位置, 逆向出里边单个元素结构就非常的简单了

无非是拿CE观察内存变动和试着改的操作, 此处不折腾这些。

对着Vector<Zombie>分析了一下, Zombie的类如下图所示(后边的僵尸放置功能会用到)

植物大战僵尸修改器开发逆向与应用


种植函数

找种植函数也从个数入手, 以植物为例, 直接对着Board::Plants::Count查找访问就行。

当种下一个植物时, 植物的个数会添加,  在添加个数的代码附近应该有相关信息。由此找到41DEE7处的访问代码:

植物大战僵尸修改器开发逆向与应用

此时可以肯定的是sub_41DE80不是种植call

根据PVZ的植物种植特点, 种植植物至少要有植物ID丶草坪X丶草坪Y这三个参数。而IDA并未识别到sub_41DE80有参数, 如果说这几个参数全通过寄存器传递不太现实。

同样在函数头下断点, 来到调用处40D131, 调用它的函数sub_40D120就很符合目标, IDA显示有四个参数。

植物大战僵尸修改器开发逆向与应用

在调用sub_40D120处下断点, 种植物断下, 观察栈顶的参数。

参数1: Board (this)

参数2: 8 (结合种下的位置看, 是草坪Y)

参数3: 1 (估计是ID)

参数4: FFFFFFFF (意义不明)

eax: 2 (结合位置看是草坪X)

植物大战僵尸修改器开发逆向与应用

简单测试一下, 将草坪X这个参数清零, 看看能否影响种植位置

将mov eax,[esp+20]改成xor eax,eax即可

植物大战僵尸修改器开发逆向与应用

测试没问题, 说明这确实是植物种植的函数

逆向其他种植函数思路也类似, 就不详细解析

①植物种植函数:

地址: 40D120

类型: Plant* Board::NewPlant(Board* this, int LawnY, eax = LawnX, int ID, int Reserve = -1);

②僵尸放置函数:

地址: 40DDC0:

类型: Zombie* Board::NewZombie(eax = Board*, int ID, int LawnX);

③小推车放置函数:

地址: 458000

类型: LawnMover* Board::NewLawnMover(Vector<LawnMover>* lawnmovers, eax = LawnX);

④货币生成函数:

地址: 40CB10

类型: Coin* Board::NewCoin(ecx = Board*, int PosY,  int delay, int ID, int DropMode);

⑤子弹生成函数:

地址: 40D620

类型: Projectile* Board::NewProjectile(eax = Board*, int PosY, int PosX, int Reserve, int ID);

⑥网格物品生成函数:

这里就不一一例举了, 因为生成梯子丶罐子丶墓碑丶脑子等函数都是独立的,而写这篇文章是边逆向边写的...之前虽然逆向过, 但相关信息都没保存。


ImGui 开发修改器

6.1 项目准备

imgui开源项目地址: GitHub - ocornut/imgui at docking

基本的入门代码可以在examples文件夹中查看, 此次不做入门代码解析

自己将入门代码写在一个函数中, 在DllMain创建一个线程执行即可

直接从ImGui窗口开始写代码:

植物大战僵尸修改器开发逆向与应用

将需要用到的结构体放到一个头文件中, 且统一放在命名空间PopCap(宝开)

植物大战僵尸修改器开发逆向与应用

6.2 阳光编辑

滑动条编辑阳光的逻辑写在SunlightSlider中, 在ImGui::Begin与ImGui::End之间调用即可。

植物大战僵尸修改器开发逆向与应用

效果:

植物大战僵尸修改器开发逆向与应用

这种编辑内存的效果没什么意思, 就以阳光为例意思一下即可, 下边玩点有意思的。

6.3 安装位置

现在开始为植物和僵尸的摆放位置做铺垫, 我们使用一个二维数组(LawnActive)表示草坪上的位置。

当点击对应的按钮时激活/取消对应的草坪位置, 激活时按钮显示白色

下图中的SetupAction同样在ImGui::Begin和ImGui::End之间调用

植物大战僵尸修改器开发逆向与应用

植物大战僵尸修改器开发逆向与应用

点击"安装位置"弹出弹窗, 点击里边的按钮激活对应的位置, 激活的按钮颜色为白色。

植物大战僵尸修改器开发逆向与应用

6.4 任务处理

现在开始为植物和僵尸的放置做铺垫, 这里选择Board::Update进行操作, 它有以下优点:

①Board::Update由主线程调用, 它和种植植物丶僵尸等功能call的调用是在同一线程, 这能避免各种线程安全问题(如TLS变量的访问等)。

②Board::Update在进入游戏对局后不断调用, 这能及时响应我们在其他线程派发的指令

我们对Board::Update进行虚表Hook, 将其所在的虚表位置替换等我们的任务处理函数, 在任务处理函数返回时再调用Board::Update

这里直接将任务处理的逻辑写在Board中

植物大战僵尸修改器开发逆向与应用

安装和卸载任务处理

植物大战僵尸修改器开发逆向与应用

任务的添加和处理

植物大战僵尸修改器开发逆向与应用

注入Dll时调用InstallTaskHandler即可

植物大战僵尸修改器开发逆向与应用

以std::printf为例, 让它在Board::Update的执行线程调用

植物大战僵尸修改器开发逆向与应用

6.5 植物种植

封装一个植物的种植函数, 以便我们调用

植物大战僵尸修改器开发逆向与应用

SetupPlant与之前一样调用于窗口循环之间

植物大战僵尸修改器开发逆向与应用

效果

植物大战僵尸修改器开发逆向与应用

6.6 僵尸放置

封装一个僵尸的放置函数, 以便我们调用, 而Zombie并没有草坪Y的索引, 我们需要手动调整Zombie对应的X坐标以满足需求。

植物大战僵尸修改器开发逆向与应用

SetupZombie与之前一样调用于窗口循环之间

植物大战僵尸修改器开发逆向与应用

效果

植物大战僵尸修改器开发逆向与应用

总结:

我们从一个最简单的阳光, 把Board类和PVZ类的关键数据逆了出来, 对于PVZ而言, 游戏中的数据绝大部分都放在这两个类当中。

另外补充一点, PVZ是宝开的作品之一, 宝开开源了它的框架SexyAppFramework, 在github上可以找到。

PVZ的开发也用了这个框架, 因为IDA识别出Board::Ctor中引用的虚表符号,这两个类的成员完善就不细讲了, 毕竟是边逆向边写文章...总算到头了。

项目: 

链接: https://pan.baidu.com/s/1F3Bh7zOMc24E3qPddspoqA?pwd=w3r5 提取码: w3r5

注意要使用x86和C++17编译

植物大战僵尸修改器开发逆向与应用

看雪ID:std::memset

https://bbs.kanxue.com/user-home-988811.htm

*本文为看雪论坛优秀文章,由 std::memset 原创,转载请注明来自看雪社区

原文始发于微信公众号(看雪学苑):植物大战僵尸修改器开发--逆向与应用

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月12日23:08:12
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   植物大战僵尸修改器开发逆向与应用https://cn-sec.com/archives/3641141.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息