Cheat Engine 学习笔记之mono篇

folder_open
comment没有评论

起因

这几天放假,闲着无聊就想着找点单机游戏打发下时间,偶然间发现个叫部落幸存者的类模拟城市的游戏,看着还行于是玩了一会。玩着玩着发现不对劲,感觉游戏节奏有点不太好把控,于是拿出 Cheat Engine(下面简称CE) 打算加快一下进度。这个游戏没有对数据进行保护,所以基本搜索几下就能找到想要的数据,不过每次启动游戏这些数值的地址就会变掉,谷歌了一下发现需要去找基址,找了一圈也没找到。鉴于之前没用过CE的其他功能。干脆就学习一下怎么用CE好了。

分析一下

首先得明确自己想做什么,这个游戏总体来说难度还是不算高,但是对我这种急性子来说节奏有点偏慢了,游戏里面有一个银币系统,这个银币前期很难收集,对我这种急性子来说想快点体验到游戏后期内容的话,通过修改银币可以很大程度加快游戏进程;其次就是这个游戏的物资种类比较多,关联性也比较强,仓库很容易就放满了,玩到后面一大片地全部造仓库了,因此也可以考虑将仓库容量改大一点。

明确了目的以后,接下来就要琢磨怎么解决了,首先打开游戏目录,一眼就看到一个UnityCrashHandler64.exe摆在那里,Unity的游戏大多都是用C#编写的,部分Unity游戏甚至可以直接反编译成C#代码,这样的话甚至都不用CE,直接改C#代码就行了。不过之后版本的Unity提供了IL2CPP,可以将IL进一步编译成原生,这样的话想再看到C#代码就比较难了。打开Data文件夹里面有个il2cpp_data文件夹,顿时眉头一紧,虽然有Il2CppDumper这样的工具来拆包,不过还是需要用IDA来进一步分析,我玩不明白IDA,因此暂时先放弃这条路…

继续在游戏文件夹里面摸索,发现了一个没用IL2CPP编译的“URP”版本,不知为何这个版本并没有进行IL2CPP编译。有了这个版本,那就意味着可以直接看到游戏的代码。可以在这个版本的Data文件夹下找到Managed文件夹,里面全是托管dll,默认情况下游戏逻辑都在Assembly-CSharp.dll这个类库中,不过也有很多开发者会将游戏逻辑拆分出来,比如这个游戏就放在了GameLogic.dll中,用dnSpy之类的工具打开就能直接看到C#代码了。

开始操作

首先打开游戏,启动CE,用CE附加游戏进程,然后启用CE的mono功能

通过多次变动银币数值的方法搜索到银币的地址,按Ctrl+F6或者右键点击“找出是什么改写了这个地址”,接下来再次对银币进行操作,就能在这里看到了

接着按Ctrl+D或点击“显示反汇编程序”,如果上一步开启了mono功能,这时候就能直接在这里看到类名和函数名了

这里能很明显看出来调用了CoinMgr类下面的AddCoin函数,通过查看C#源代码可以知道,CoinMgr是一个静态类,AddCoin也是静态函数,并且只需要一个入参

知道这些以后,我们可以打开CE的Lua脚本功能尝试直接调用这个函数:

执行成功后,进入游戏发现银币也增加了1000个,不过还不算完,因为总不能每次都打开lua控制台来执行代码吧,如果想在CE中直接双击修改的话,也可以通过注入的方式将银币的地址注册成别名来实现修改。回到刚才的反汇编界面,可以看到在+49的前面,将银币的地址放进了rax

在这里按下Ctrl+A或者点击菜单的“工具”->“自动汇编”,然后按Shift+Ctrl+F或者点击“模板”->“完全注入”,生成注入模板,再将其分配到当前的作弊表。

这时候双击编辑这个脚本,在ENABLE处检查是否已经打开mono功能,然后将里面alloc的地址替换为C#里面的函数名(记得带上偏移量),然后注册一个叫coinPointer的符号,在newmem下面将rax写入coinPointer:

此时将脚本启用,只要银币发生变动,我们就可以通过这个coinPointer直接找到银币地址

之后只需要双击修改它的值就能随意修改银币数量了。

OK,银币问题解决了,接下来是仓库扩容,这个稍微麻烦一点,因为游戏里没有将仓库容量的数值展示出来,因此这里就得从代码入手了。经验上来说,一般这种存储的东西,名字都带点什么Inventory,Storage,Container之类的,全局搜一搜,在搜Storage的时候,一个叫BagData的类进入了我的视线:

从经验上来说,这个weightLimit就是容量上限,再继续看看是谁在读写这个变量

然后就发现了这么几个函数,从名字不难猜出,IsFull是判断是否已经装满,RemainWeight是返回剩余容量,而他们都调用了一个共同的函数:WeightUpper,这个就是容量上限了,再进一步查看一下引用,BuildingData类和CitizenData类都有这个类作为属性,那么可以大概知道这个函数做了些什么,首先if判断是否是建筑,如果建筑的类型为2(这个2应该就对应了仓库类型的建筑)的话,则使用建筑的GetBrokenNum函数来加权计算容量(例如建筑耐久破损之类的);然后再判断是否为村民,是的话返回这个村民的加权可携数(例如装备了背包或者手推车之类的),如果不是就直接把weightLimit返回出去。

当然,分析归分析,我们还是得以事实为准,那要怎么验证呢?既然都有C#类库了,直接找到这个函数,在它return之前给他*20来个超级加倍,例如建筑,在第一个ret之前插入ldc.i4.s, 20,然后再插入mul,就实现了*20

然后保存dll,进入游戏,发现都已经存了100%的东西了,村民还在往里面搬东西,直到库存达到2000%的时候才提示已装满

既然确定了是这个函数,那么接下来就在CE中如法炮制,先打开反汇编界面,然后Ctrl+G跳转到地址,输入BagData:WeightUpper跳转到这个函数,然后往下翻,找到调用GetBrokenNum的地方,以下面的call为注入点注入

步骤大概跟上面的差不多,在生成好代码之后插入启用mono的代码,然后将地址替换为函数名,接着在下面的call r11的后面插入imul eax,eax,14,注意这个14是16进制,换算回10进制的话就是20。

这样的话只要勾选激活了这段代码,游戏里的建筑容量就会变成20倍,当然,村民的话也可以如法炮制。

总结

虽然困扰我的2个问题解决了,但是对我来说现在还只是学到点皮毛,因为能做到这些事的根本原因是因为开发商留了未经IL2CPP编译的mono版本,如果没有的话,那么整个过程会更加的复杂,得先使用Il2CppDumper将函数列表dump出来,然后把游戏文件拖进IDA去分析,并且有些游戏还对global-metadata.dat进行了加密(例如某神),这时候的分析会变得更加复杂,接下来如果有时间的话再继续深入学习吧,希望不久的将来我也可以像耍耍一样IDA轻松玩。

Tags:

看看其他

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Fill out this field
Fill out this field
请输入有效的电子邮箱地址。