发表于 2024-3-13 01:22

申请会员ID:moriv4

申 请 ID :moriv4
个人邮箱:985811440@qq.com

熟悉技术: X86汇编、C/C++软件开发
原创技术文章:

单机游戏的逆向分析:气球塔防6   (Bloons TD 6)

前言
气球塔防系列游戏最早是在4399等Flash游戏网站上推出,从第一代到第四代人气越来越高,被称为塔防游戏三巨头之一(加上植物大战僵尸和王国保卫战)。
后来NinjaKiwi公司使用Unity引擎重构了这款游戏,推出了第五六代在多个平台上发售。游戏中玩家需要放置防御塔,击破气球,夺回猴子们的城镇。


本文将分析此游戏中的金币、经验、猴币等数值在汇编层面的表现方式和修改过程。使用的工具是x64dbg和CE,CE用于查找数值地址,x64dbg用于调试汇编代码。
提示:阅读本文需要了解X64汇编(包括SSE2浮点数指令集)和上述两个工具的使用方法。

https://s21.ax1x.com/2024/03/12/pFcmbb6.png

文件目录
进入游戏安装文件夹,看到有 UnityPlayer.dll, GameAssembly.dll等动态链接库文件,进入Data目录有il2cpp_data文件夹。说明此游戏使用的是
Unity框架的 il2cpp 技术进行生成打包。游戏的业务逻辑在GameAssembly.dll中提前编译好了,如果不出意外,我们要调试的目标代码片段就在其中。
关于mono和il2cpp的差异对比,这篇帖子写得非常详细:通杀爆改 Unity FPS 游戏系列-第三章:il2cpp mono 差异(感谢原作者 lyl610abc )
本文将使用动态调试的方法分析,大家如果有兴趣可自行使用 dump工具还原符号静态分析。
https://s21.ax1x.com/2024/03/12/pFcmf5F.png




数值搜索
选择地图进入到关卡内,初始金额850,放置几个防御塔改变金额并用CE搜索此数值。先尝试搜索4字节整数类型,没有找到。再试单精度浮点,又没有找到。
最后使用双精度浮点终于找到了目标数值。但不要高兴的太早!CE修改此处数值为9999后,再在游戏里放置一个新塔,这里的数值居然变回原来正确的了。
我们的修改没有产生任何作用!这也是经常会遇到的一个情况,此处是纯显示的结果量,表达式计算完成后写入此地址,程序并不会读取这个值参与实际运算。
所以我在文章开头说要了解SSE2浮点数指令集,如果不看汇编代码就没法改。
https://s21.ax1x.com/2024/03/12/pFcnxoT.png



反汇编调试
运行xdbg64,附加到BloonTD6进程上,在内存页面转到之前用CE搜索得到的地址,右键菜单中选择浮点数->Double,可以看到显示数值为15。
在此内存地址处下硬件断点->写入->8字节。在游戏中把塔卖掉后,触发中断,代码地址为GameAssembly.dll+0x3B3F62,如下图所示:
https://s21.ax1x.com/2024/03/12/pFcuf1J.png
分析如下:
函数申请30h个字节的栈空间用于局部变量,然后
(1)开始时把xmm6寄存器保存在栈上。
(2)最后从栈上恢复xmm6。xmm6相当于临时寄存器变量,借来用一下。
(3)把xmm1的值赋值给xmm6。xmm1相当于参数,从外部传入,值为计算后的金额。
(4)把xmm6写入内存,此处地址是之前用CE找到的那个。
(5)调用函数,作用不明。
函数退出前平衡堆栈。可以看到目标写入地址的后一个数据是改变前的金额,即

xmm1的金额数值是从哪里来的?
在函数调用堆栈页面转到上一个调用,如下图所示:
https://s21.ax1x.com/2024/03/12/pFcuhc9.png
(1)xmm0为原金额,xmm6为售价,此处进行浮点数加法运算。新金额=原金额+售价。
(2)将xmm0的值传递给xmm1,作为参数调用接下来的函数。
(3)调用之前分析过的的函数

经过一些尝试发现,在addsd xmm0, xmm6指令之后,且在call 指令之前 下断点,此时用x64dbg修改xmm寄存器的低64位双精度浮点值,
修改后的值可在游戏中生效。开局即可放置5级防御塔。接着看看其他片段,使用二分法下断点,修改xmm寄存器的值,可找出影响内存值的
关键代码片段!
https://s21.ax1x.com/2024/03/12/pFcu4XR.png


那么,xmm0的金额数值是从哪里来的?
多次下断点,单步步过调试发现:addsd xmm0, xmm6指令前面一条call指令,它产生了xmm0的值!
跟踪进去发现有两次跳转。
https://s21.ax1x.com/2024/03/13/pFcKBCD.png

然后来到了一个超级复杂的函数,我用红框标出的代码区域能产生金额,但此处又被多个其他地方调用。初步判断有数值加密算法,
每次取金额时要用两个浮点数相减才得到,这里地址也是来回变动。计算完成后保存金额时要用两个浮点数相加,分别保存到对应内存地址。
存在一个对称结构,怀疑是用C#getter和setter方法修饰了一些属性。
https://s21.ax1x.com/2024/03/13/pFcKw4O.png
分析到此处不准备继续深入了。购买塔、道具、获得经验、猴币,代码相似,平级关系,这里不再重复。
在调试时找到了一个全局常量,单精度浮点数,值为一千万,地址 ds: 。可以把它拿来做修改时的常数使用。

制作修改补丁
之前分析的 addsd xmm0, xmm6 代码处,如果把 xmm0的值设置为一个很大的数,就可以实现无限钱。每当卖出一个塔时就重置金额。
但我选择在购买塔的 subsd xmm0, xmm6 代码处修改(购买塔时触发的硬件写入断点),因为购买功能常用,在此处注入代码更合适。
我们要写的代码有点长,原地放不下,就在GameAssembly.dll代码结束处的有效文件偏移处写,加上两个jmp语句搞定。

金钱修改
; GameAssembly.dll+0x3F38F5前7个字节 改为
jmp GameAssembly.dll+0x291EB70
nop
nop

; GameAssembly.dll+0x291EB70
movss xmm0,dword ptr ds:; 一千万浮点常数
cvtss2sd xmm0,xmm0                                    
xor r8d,r8d                                          
jmpGameAssembly.dll+0x3F38F5

代码处右键->补丁->修补文件,保存为GameAssembly.dll替换原来的即可。

总结
本文讲解了一个塔防游戏的逆向思路,在关键数值加密的情况下修改了游戏内金额,使用代码注入的方式制作了修改补丁。
友情提示:游戏检测到异常数值后,Ninja账号会被标记,但不影响单机游玩体验。


Hmily 发表于 2024-3-13 11:17

今天开放注册,自己来注册吧。
页: [1]
查看完整版本: 申请会员ID:moriv4