王者荣耀采用流行的Unity3d框架开发,主要游戏逻辑使用C#编写, 但目前尚未确认是否有使用到Lua。

一、关于Unity3D

Unity3D是一款专用于3D游戏开发的跨平台框架,支持Lua、C#、JS脚本语言开发游戏逻辑。但目前使用较为广泛的还是C#,而王者荣耀也不例外。

二、Unity3D with C#手游的通用修改方案

先来看下通用的脚本提取方法。Unity3D只是提供3D框架,其对C#脚本的支持能力完全依赖开源项:

mono (https://github.com/mono/mono, 在apk的lib下面有一个名为libmono.so就是使用了mono项目的特征)。

王者荣耀脚本提取

Unity3D编译好后,最终的C#文件保存在×.apk/assets/bin/Data/Managed/中的Assembly-CSharp.dllAssembly-Csharp-firstpass.dll 两个文件,其都是编译好的C#文件。

王者荣耀脚本提取

对Unity3D游戏逻辑的修改最后就是针对以上这两个dll文件,这两个dll文件是windows上的PE格式

王者荣耀脚本提取

正常无保护的情况下可以很明显的看出以上两个文件的特征,使用一些C#的反编译工具(如:.Net Reflector、ILSpy)可以很容易的得到源码逻辑:

王者荣耀脚本提取

最后利用如.NetReflector工具对 dll文件中的游戏逻辑做修改,并替换dll文件后打包。便完成了游戏的修改。

三、对王者荣耀进行分析

1. 在王者荣耀中,这两个文件(Assembly-CSharp.dllAssembly-Csharp-firstpass.dll)均被加密保护,已经无法看到标准的PE头。

王者荣耀脚本提取

2. C#解析引擎libmono.so也被加壳混淆。

王者荣耀脚本提取

3. 游戏存在反调试保护,无法用断点断到关键函数,会自动退出。

根据阅读mono开源项目得知其的加载assembly文件的方法名为:

mono_image_open_from_data_with_name(char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, const char *name)

并且该函数在libmono.so中对外导出

  • 第1个参数data, 指向的是Assembly-*.dll的完整文件数据。

  • 第2个参数是data_len,是数据长度。

首先查看libmono.so中的mono_image_open_from_data_with_name导出函数,发现已被加密混淆。怀疑TX可能对mono做了特殊的定制(如果是的话会比较棘手)

4. 通过进程注入对libmono.so脱壳还原代码后发现并无特殊定制:

说明调用到mono_image_open_from_data_with_name后参数中的一定是明文dll数据。

王者荣耀脚本提取

既然没有定制libmono.so,所以直接采用hook方式进行(可使用开源项目adbi、或自己开发)hook这个函数(mono_image_open_from_data_with_name) 但由于Assembly-*.dll文件在游戏启动时就被加载,所以需要考虑hook时机:

1.使用debug模式启动王者荣耀,使其处于等待调试状态…

2.先hook住libc.so中的__openat函数,当发现有Assembly-*.dll文件被打开时,再对libmono.so中的mono_image_open_from_data_with_name挂钩,这样可以保证hook不错过时机,并且外壳代码已经还原完毕。

3.最后将mono_image_open_from_data_with_name 的数据写出文件。

5. DUMP成功后的脚本,使用反编译工具可以看到游戏逻辑,并可以修改:

王者荣耀脚本提取

本次分析验证到此。

四、基于此开发辅助的思路

  • 定位到游戏关键逻辑后,使用C#反编译修改工具(如:.Net Reflector、ILSpy)对脚本进行修改。

  • 在加载脚本时用hook将修改后的脚本替换进去,游戏的逻辑也随之被修改。