bugku上面的一道unity逆向题,花了一两天学习一下unity 的逆向
背景介绍
- Unity 游戏有两种打包方式 mono 与 il2cpp
- 在 mono 模式下,游戏 C# 代码被编译为 IL(中间代码) 并生成 dll 文件,然后 dll 被打包进最后的游戏包文件。由于 IL 非常容易被 ILSpy / .NET Reflector 等专业反编译软件分析逆向,所以在无保护情况下,游戏的安全性极差。
- 在 il2cpp 模式下,虽然游戏逻辑是以 Native 代码运行, 但依然要实现 C# 某些语言特性(如GC), il2cpp 将所有的 C# 中的类名/属性名/字符串等信息记录在 global-metadata.dat 文件。il2cpp 启动时会从这个文件读取所需要的类名/属性名等信息。
- 使用 IL2cppDumper 可以解析 global-metadata.dat 文件,并将文件里的类名等字符串信息对应到 Native 代码中去
Unity使用Mono方式打出来的apk,我们可以直接从包内拿到Assembly-CSharp.dll,如果开发者没有对Assembly-CSharp.dll进行加密处理,那么我们可以很方便地使用ILSpy.exe对其进行反编译。
题目介绍 (baby unity3d)
在bugku上遇到了一个unity apk需要我们来逆向,但是直接用IDA打开so文件,完全摸不着边际
查阅网络上的资料,了解到这个游戏使用了il2cpp的打包方式,需要分析libil2cpp.so和global-metadata.dat文件来获取native中的类名/函数名/变量名等等。
这一方面之前从来没有了解过,正好通过这道题来学习一下如何逆向unity的apk
工具环境
- 工欲善其事必先利其器,先搞好工具,下个 Il2cppDumper
安装参考:https://blog.csdn.net/linxinfa/article/details/116572369


- 从apk中提取文件
解压apk,从\crackme\assets\bin\Data\Managed\Metadata\中提取global-metadata.dat文件;

从\crackme\lib\armeabi-v7a文件中提取libil2cpp.so文件;

- 回到
Il2CppDumper.exe所在的目录,创建input目录和output目录。

将上述两个文件放到input文件夹中,并编写il2cpp_decompilation.bat文件
1 | ..\Il2CppDumper.exe libil2cpp.so global-metadata.dat ..\output |

运行bat文件,发现出错了,metadata可能被加密了
- 查看global-metadata.dat文件
- 使用二进制文本查看器打开global-metadata.dat文件

- 发现文件开头并不是AF 1B B1 FA,说明该文件应该是修改或加密过
解密
- 想要解密
global-metadata.dat我们有两种思路,一种是dump解密结果,另一种是分析加密算法。对于第一种思路,这里有个frida脚本
1 | function frida_Memory(pattern) |
大概流程就是通过magic来定位到文件在内存中的起始地址,然后通过解析文件头来计算出文件的大小,最后进行dump。
该脚本的适用条件是global-metadata.dat在解密后必须要有正常的magic即AF 1B B1 FA来定位,同时文件头信息要正确否则无法计算文件大小。
这个脚本有一定的参考价值,然而对于这题起不到作用,脚本执行后没有找到起始地址,看来即使解密后,内存中也没有AF 1B B1 FA存在。所以这种通用的dump方式应该是不行了,只能找到global-metadata.dat的加载函数,待其解密完成后再进行dump,所以我们需要对global-metadata.dat的加载流程进行分析。
global-metadata.dat加载流程
考验英文的时候到了,要先学习了解一下如何跟踪分析global-metadata.dat:https://katyscode.wordpress.com/2021/02/23/il2cpp-finding-obfuscated-global-metadata/
简要概括下,在libil2cpp.so里面有个il2cpp_init函数是加载函数调用链中的第一个函数,整个调用链是这样的
1 | il2cpp_init |
对比IDA中的函数
- il2cpp_init

- il2cpp::vm::Runtime::Init
sub_4C4770

- il2cpp::vm::MetadataCache::Initialize (通过查看特殊的字符发现的,因为“global-metadata.dat”被加密了)
sub_4B5564

解密“global-metadata.dat”函数

运行一下

确实是“global-metadata.dat”
可以看到与“global-metadata.dat”(v0)密切相关的函数是sub_513060,而后面v0就被free掉了,那么
dword_6959CC就应当对于global-metadata.dat的数据,故而要看sub_513060的返回值
sub_513060 (return v3)

根据调用关系,解密dat的函数为sub_512FDC
1 | char *__fastcall sub_512FDC(int a1, size_t size) |
解密脚本
1 | import struct |
解密完成后发现还是不能用Il2cppDumper,将解密后的文件放到010editor里发现魔数不对,改成AF 1B B1 FA就行了,原来他把魔数校验的那一步给去掉了,所以可以改魔数,这样就可以防止用前面提到的通用frida脚本来dump了。

感动,迈出了一大步,获得了output内容

dump.cs
:这个文件会把C#的dll代码的类、方法、字段列出来
在其中,搜索checkFlag函数

il2cpp.h
生成的cpp的头文件,从头文件里我们也可以看到相关的数据结构

script.json
显示类的方法信息

1 | { |
寻找check函数
上一步知道了checkflag函数地址为0x518a24,在IDA中跳到该地址

利用ida.py还原script.json中的数据

1 | int __fastcall Check__CheckFlag(int a1, int a2) |
o(╥﹏╥)o,终于可以看了
愉快地取出关键字符

StringLiteral_2814: w0ZyUZAHhn16/MRWie63lK+PuVpZObu/NpQ/E/ucplc=
IV和key暂时获取不到,需要用frida来hook出来
1 | Java.perform(function(){ |
frida hook
复习一下frida知识:https://www.jianshu.com/p/fa422d3b7148
血泪经验:frida windows版本一定要和sever版本相同,麻了,折腾一天了总算成功了
1 | adb forward tcp:27043 tcp:27043 |

frida脚本
1 | Java.perform(function(){ |
hook这里折腾了一天,模拟器是真的不行!!!!
最后用jr的真机实现的,哭了—

pawd: 91c775fa0f6a1cba
iv:58f3a445939aeb79
在线解密一下

不容易,获得flag:N1CTF{h4ppy_W1TH_1l2cpp}
补个解密的脚本
1 | from Crypto.Cipher import AES |
参考资料
https://blog.csdn.net/qq_39268483/article/details/115833605
https://www.ashenone66.cn/2022/04/22/il2cpp-ni-xiang-global-metadata-jie-mi/