IL2CppDumper笔记
最近研究了一段时间的IL2Cpp编译出来的dll,整理下笔记记录下中间遇到的一些问题和解决方法。
加载UnityPlayer.dll对应PDB
这个其实是我无意间搜到了Unity官方提供了对应Symbol Server,Windows Debugging里也给出了常见软件的使用方法:
.sympath+ SRVc:\symbols-cachehttp://symbolserver.unity3d.com/
参考为IDA加载调试符号我修改了.\cfg\pdb.cfg
中的_NT_SYMBOL_PATH
一栏,发现识别不出来orz
用浏览器访问了下对应地址,发现下载下来的是一个.pd_
文件——直接用解压软件打开就能获得对应的.pdb
文件对上。也许IDA也能支持压缩版本? 目前反正暂时手动档了…
ps. 后来还遇到一个情况是一开始IDA无法运行ida_with_struct_py3.py
这个文件,运行下idapyswitch.exe
就好。
绕过外部加壳
现在蛮多游戏都会做初步的加壳,可以防住一些Script Boy。这里推荐Katy大佬的很多文章,他本人也是Il2CppInspector作者:
- Practical IL2CPP Reverse Engineering: Extracting Protobuf definitions from applications using protobuf-net (Case Study: Fall Guys)
- Reverse Engineering Adventures: League of Legends Wild Rift (IL2CPP)
- Reverse Engineering Adventures: Honkai Impact 3rd (Houkai 3) (IL2CPP) (Part 1)
- Reverse Engineering Adventures: VMProtect Control Flow Obfuscation (Case study: string algorithm cryptanalysis in Honkai Impact 3rd)
- IL2CPP Tutorial: Finding loaders for obfuscated global-metadata.dat files
- Reverse Engineering Adventures: Brute-force function search, or how to crack Genshin Impact with PowerShell
需要强调的是Il2Cpp本身是能看到部分源代码的(对应Unity安装目录的Editor\Data\il2cpp\libil2cpp
下),必须对这块有一定了解才能往下推进。核心我们其实需要获取两个文件:
GameAssembly.dll
存储了逻辑本身global-metadata.dat
存储了类型、方法等信息
GameAssembly.dll
现在蛮多游戏都会对GameAssembly.dll加密,所以很多时候偷懒不高兴分析加壳手段,直接去内存里抓取。
安卓上之前一般是用Game Guardian来抓取,现在比较推荐直接使用Zygisk-Il2CppDumper。
PC上的话最简单的情况是直接任务管理器里生成转储,就可以dump下来然后分析dll的magic header; 如果常规手段被禁用的话,可以请出KsDumper,直接从kernel角度入手解决问题。这里给自己挖了个坑,后文详述
global-metadata.dat
这里无法用dump内存的手段是因为这个文件是直接MemoryMap的,只有用到的地方才会加载,所以内存里内容很有可能是不完整的。
下图是我拿来练手的dll,一边参考一边各种rename下来,发现其实没有任何骚套路:
bool il2cpp::vm::GlobalMetadata::Initialize(int32_t* imagesCount, int32_t* assembliesCount) |
void* il2cpp::vm::MetadataLoader::LoadMetadataFile(const char* fileName) |
Katy也在博客中整理了一些case,譬如混淆文件内容、修改文件名和路径等手段,这里就不再赘述等遇到到再说,一般来说先找到正常加载的口子(譬如搜索到global-metadata.dat
这个常量然后xref看使用的地方),接着对比和正常逻辑有没有区别。
Il2CppDumper
获取脱壳结果后,就可以进行分析。对于GameAssembly.dll
来说,其实最首先的是找到codeRegistration
和metadataRegistration
这两个指针指向的内容,然后就可以依葫芦画瓢把里面的数据全部Dump出来。感谢Perfare大佬的工作特别是各种版本处理我看的都头大
void il2cpp_codegen_register(const Il2CppCodeRegistration* const codeRegistration, const Il2CppMetadataRegistration* const metadataRegistration, const Il2CppCodeGenOptions* const codeGenOptions) |
具体的流程其实对照il2cpp的源代码还是比较好理解的,这里就提一个我遇到的问题: 使用dump出来的GameAssembly.dll
会遇到GetTypeDefinitionFromIl2CppType
里数组越界。
我一开始以为是抓出来的dll有问题,分析了下雀实各种struct layout和指针都能完美对上。ps. 我看Katy在分析League of Legends Wild Rift时遇到类成员变量顺序改变的情况,这个方法有点意思的。
索性一路怼下去发现代码逻辑和公版都对的上,而且il2CppType
的数据内容看上去似乎没问题(datapoint近乎连续,bits都是0x120000)——突然发现盲点,datapoint应该是一个内存地址阿! 换算了下这个image的baseAddr雀实对的上。
这样其实就解释的通了: GetTypeDefinitionFromIl2CppType
其实应该走297行的那个分支,而不应该走303行的else里面。
正当我准备开始好好看下Il2CppDumper的实现的时候,发现作者刚好修复掉了马娘新版本DMMdump的DLL 无法使用…
所以总结下,本质还是因为我是使用KsDumper抓取的内存中dll,而一开始工具只考虑了安卓的ELF格式可能会出现抓取的情况(PC上的PE默认是没有检查Dump的)。
小结
虽然很久没正经弄过Unity了,但是找点乐子来练练手还挺有意思的,而且作为开发者的思路和逆向似乎交叠验证的很好玩。后面如果有空准备研究研究新出的huatuo热更新方案,看看它对Il2Cpp有什么套路可以学习学习。