IL2CPP 逆向
前言
在许久之前通常unity常用Mono虚拟机来进行打包,但是因为其打包方式极其容易被逆向因此市面上邮寄基本上使用il2cpp来进行打包操作。
对于il2cpp是将游戏C#代码转换为C++代码然后在编译为各平台Native代码
虽然游戏逻辑以Native代码进行运行,但是其仍然需要实现C#的一些语言特性,于是il2cpp将所有的C#中的类名、方法名、属性名、字符串等地址信息进行记录在global-metadata.dat
文件中。在il2cpp启动时便会从此文件来进行读取需要的类名、方法名、属性名、字符串等信息。
如今互联网上对于il2cpp逆向的教程也是比较多的,但基本上都指向了一个il2cppDumper
Perfare/Il2CppDumper: Unity il2cpp reverse engineer (github.com)
通过其将游戏中的libil2cpp.so
(或是UnityPlayer.dll
)以及global-metadata.dat
进行提取后使用上述项目进行dump处理。事实上因为这个项目过于的出名,许多游戏厂商也对此采取了对应的对抗措施(如对上面两个文件进行加密等)。
前置知识
通常来说global-metadata.dat
的魔术字应为AF 1B B1 FA
,我们通过010
可以发现其明显的被修改过,则我们需要找到对应解密global-metadata.dat
的方法来进行dump处理
运行 && Dump 解密
根据大A师傅的文章,我们一般有两种思路,一种是运行起来后获取dump解密结果,另一种是分析对应的加密算法,以下提供大A师傅的Frida脚本
1 |
|
大概流程就是通过魔术来定位到文件在内存中的起始地址,然后通过解析文件头来计算出文件的大小,最后进行dump。该脚本的适用条件是global-metadata.dat
在解密后必须要有正常的魔术即AF 1B B1 FA
否则定位,同时文件头信息要正确否则无法计算文件大小。
global-metadata.dat 加载流程
其加载的调用链如下:
1 |
|
而在我们逆向中,这些都是不带符号的,然而我们可以对着源码来找到相对应的函数(不同版本的源码有一些差别)
il2cpp_init (located in il2cpp-api.cpp, comments elided):
1 |
|
il2cpp::vm::Runtime::Init (located in vm/Runtime.cpp):
1 |
|
il2cpp::vm::MetadataCache::Initialize (located in vm/MetadataCache.cpp, comments elided):
1 |
|
il2cpp::vm::MetadataLoader::LoadMetadataFile (located in vm/MetadataLoader.cpp):
1 |
|
上述的完整代码我们可以在Unity的安装目录的Editor中找到il2cpp的源码:vm目录
在里面的GlobalMetadata.cpp
中,就可以看到加载global-metadata.dat
文件的逻辑。
如果开发者对
global-metadata.dat
文件做了加密,那么在GlobalMetadata.cpp
中加载global-metadata.dat
前需要实现对应的解密逻辑。
相应的我们有了这些源码,我们只需要在 IDA 中对照他们识别出相对应的函数,而关键是后两个函数,分别是
il2cpp::vm::MetadataLoader::LoadMetadataFile 该函数将 metadata 的文件名文件映射入内存
il2cpp::vm::MetadataCache::Initialize 该函数将映射文件的指针存储在全局变量中,然后开始从这变量读取数据结构
而我们解混淆或是解密代码一般都在这两个函数里,只需要样本代码与源码对比差别即可。
以一个IDA中的例子进行说明:
1 |
|
我们通过对比源码可以发现sub_180261550
就是 il2cpp::vm::MetadataLoader::LoadMetadataFromFile
与此同时我们需要注意的是这个 v0 指针,因为解混淆或是解密都会调用到这个指针(一般是在加载前解密,而不是在加载后解密,这样会导致未解密的数据残余在内存中影响性能),通常来说应用程序在访问 metadata
前执行解密,或是在 il2cpp::vm::MetadataLoader::LoadMetadataFromFile
之前或之中。
il2cpp_init
那么以上套路是针对我们能在il2cpp
里找到il2cpp_init
的函数,但是如果没有,我们就要继续往上找UnityPlayer.dll
或者libunity.so
,而这个我们没有办法用源码对照,那么我们需要创建创建一个 Unity 项目生成 PDB,来进行获取函数符号以及相关信息。
通常我们找不到对应il2cpp_init
时存在以下几种可能性
- 导出名称经过模糊处理/加密
- Unity 调用其他导出来执行初始化
- init 函数的 RVA(相对虚拟地址)在Unity中硬编码
- Unity 调用从应用程序中检索得到的导出函数地址
- 当操作系统加载文件来提供函数地址时,应用程序在其加载挂钩中调用Unity上的导出函数
相对于此我们更加关系其对应的调用链,在为混淆下有下图:
UnityMainImpl 有很多函数调用,不过我们可以通过字符串来寻找
1 |
|
LoadIl2Cpp 也同样可以字符串快速寻找
1 |
|
InitializeIl2CppFromMain
1 |
|
然而有相当不同的变化在不同的版本中,他们的共同特点是对 il2cpp_init 的调用和 IL2CPP Root Domain 与 unused_application_configuration,所以通过这些我们也同样快速找到
例子
我们以2018年的N1CTF-BabyUnity3d为例来简单的了解一下其逆向流程,我们还是一样的提取出来libil2cpp.so
以及global-metadata.dat
使用上述项目进行处理,但是其提醒我们出错了,可能是文件没找到或者是加密了
此题比较基础只对
global-metadata.dat
进行了加密处理,方便我们的学习理解
我们打开010
来观察对应文件魔术字
可以很明显的发现其被加密处置了,我们采用上述提到的Frida
脚本,然而对于这题起不到作用,脚本执行后没有找到起始地址,即使解密后,内存中也没有AF 1B B1 FA
存在。所以这种通用的dump方式应该是不行了,只能找到global-metadata.dat
的加载函数,待其解密完成后再进行dump,所以我们需要对global-metadata.dat
的加载流程进行分析。
我们先尝试进行寻找il2cpp_init
则sub_4C4770
为il2cpp::vm::Runtime::Init
,我们进入该函数后一个个查找对比源码可以发现sub_4B5564
为il2cpp::vm::MetadataCache::Initialize
那么看该函数可以很明显发现,global-metadata.dat
字符串变成了其他字符串,但程序运行sub_4B5518
会自动解密回去。同样的对应着源码sub_513060
就是il2cpp::vm::MetadataLoader::LoadMetadataFile
,其返回值为一个指针
随后还是比对源码,我们需要找到对应修改过的地方,可以发现对应的else
块被修改过
我们进入该函数便可以获取到对应global-metadata.dat
加密的逻辑
我们写出对应的解密脚本
1 |
|
修改完后我们可以发现其魔术字仍然有问题,但是可以明显发现是人为修改后的,我们将其修复为AF 1B B1 FA
,修复后我们再运行il2cppDump
之后便可以拿到对应的函数信息
dump.cs
这个文件会把 C# 的 dll 代码的类、方法、字段列出来
IL2cpp.h
生成的 cpp 头文件,从头文件里可以看到相关的数据结构
script.json
以 json 格式显示类的方法信息
stringliteral.json
以 json 的格式显示所有字符串信息
DummyDll
进入该目录,可以看到很多dll,其中就有 Assembly-CSharp.dll 和我们刚刚的 dump.cs 内容是一致的
我们使用.Net反编译工具对DummyDll
中的Assembly-CSharp.dll
进行反编译,我们可以清晰的看到其导入的函数
其实直接看dump.cs也可以,但是个人感觉有点混乱(虽然两个差不多)
不难看出其使用了一个AES来进行Check
我们的Flag
,我们使用项目提供的python脚本来将libil2cpp.so
中的符号进行还原,我们使用Alt+F7
或者点击Script
执行脚本
此处根据自己IDA使用的Python版本进行选择
之后会要求我们选择一个json
以及h
文件,我们选择Dump
出来的script.json
以及il2cpp.h
即可
之后等待一会便可以恢复出对应的符号信息,我们搜索对应CheckFlag
函数
我们可以看到是Check_TypeInfo
对其进行赋值,我们通过交叉引用可以找到对应的赋值点
对应加密后的Base64
则通过StringLiteral_2814
进行寻找
之后便可以写出对应的解密脚本,成功拿到 Flag
1 |
|
参考
IL2CPP - P.Z’s Blog (ppppz.net)
【游戏开发进阶】教你使用IL2CppDumper从Unity il2cpp的二进制文件中获取类型、方法、字段等(反编译)_林新发的博客-CSDN博客