从简到难写一下Unity手游作弊思路,当作一个学习记录。
前置知识:Unity C#开发、il2cpp封装层。
为了开发效率和程序员的身心健康,Unity引擎游戏是使用C#脚本语言来写的,而要运行编译封装出来的C#程序,一般都要求.NET SDK的环境。这个对于用户来说就不太方便,想要开箱即用、点击即玩,Unity就提供了两种办法来解决:Mono、il2cp。
从简单的Mono开始:
这是一个开源的.NET框架实现,使得C#应用能够跨平台运行,相当于用户下载游戏的时候就提供了一个运行环境,如下所示:

其中Assembly-CSharp包含了开发者在Unity引擎中写的C#逻辑代码,通过 dnspy 就可以直接解析大部分源代码:

为什么能那么顺利?因为C#编译出来的字节码、很像Python的逻辑,没有什么混淆,直接反编译就能恢复原来的信息,至于Mono那部分的也就是为C#提供了一个运行环境,给程序跑起来,能看到这种结构的分析起来就非常轻松了。(经验之谈,毕竟这部分也不用关注太多),Unity手游现代开发倾向于放弃Mono而采用IL2CPP(Intermediate Language to C++),主要因为IL2CPP在性能、兼容性(特别是64位支持)、安全性以及跨平台维护上具有明显优势。Mono在不同平台移植时VM(虚拟机)维护成本极高,且在iOS等禁止代码动态加载的平台无法使用。
重点是IL2CPP:
笔者学识浅薄,加上il2cpp的机制比较复杂,就简单概括下il2cpp:类似于一种翻译机、将C#源码翻译成C++代码再来构建生成二进制文件,所以兼容性、性能都比Mono脚本好很多,加上并非字节码编译、而是原生的CPP编译,反编译安全性这块就比Mono好很多。其中游戏代码逻辑都是封装在 il2cpp.so 中。
正式开始:
就拿一个比较简单的传奇游戏来解释一下Unity手游的分析,之后逐渐提升难度到 碧蓝档案 国服。
Part1:
我们的目标是完全分析出传奇手游的所有代码逻辑,来逆向实现私服。
通过IDA打开libil2cpp.so,这部分是il2cpp翻译C#为cpp后编译出来的二进制文件,里面缺失了各种符号表信息。更神奇的是观察 字符串列表 里面,是没有任何游戏内有关的字符串

想要简单地找到登录入口,篡改ip来登录在静态分析是非常难的事情,所以我通过抓包的办法来寻找、游戏的主逻辑是从哪里得到了服务器的ip地址然后进行通讯的(这部分截图丢了)。
简单说就是游戏通过TCP 连接与 服务器进行指令通讯,客户端 发送 命令ID(例如登录8 + payload json、签到13 + payload json)这种形式发送给服务器,服务器也回传命令ID和payload给客户端, 这部分都可以通过抓包获取,没什么问题,既然服务器ip就在客户端内,那么通过特征查找的办法,很容易地就能在 global-meta.data中翻到。(如下)

这个是什么?查了一番之后发现是一种字符串资源,或者是类似于il2cpp符号表的东西,里面包含了很多有用的信息。
unity游戏启动 -> il2cpp_init -> metaLoader(加载global-meta.dat) -> il2cpp主逻辑接管程序运行,大致这样一个逻辑。
通过直接修改global meta里面的字符串信息 为 自己的域名 之后,发现竟然成功劫持到了客户端的连接。
当然这一步通过il2cpp-dump实现起来就没什么难度了,详细原理以后有空再写。
接下来就是frida hook libc通讯的函数查看调用堆栈、确定recv、send逻辑函数的位置,根据对应的命令ID和触发的UI位置来比对抓包payload确定逻辑就行了。

逆向的代码:顺着逻辑一路往下追还能看到物品id什么的,结合assert studio解包出来的资源,很容易就能恢复。
感觉上有些新奇:这种游戏的通信逻辑是这样写的,客户端和服务端并不期待特定的数据,而是客户端有另外的线程来处理socket连接,通过TCP随自己心意去发送命令ID接受服务端的响应来刷新、推进游戏主逻辑。
接下来就是体力活,照着逆向代码来恢复服务端:连接管理、命令ID响应、物品表、还有和其他客户端的通讯什么的。
part2 难度上升:
有空再写。