ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Cpp2IL实战指南:Unity IL2CPP逆向从安装到输出DummyDll

Cpp2IL实战指南:Unity IL2CPP逆向从安装到输出DummyDll 1. 从IL2CPP说起为什么Cpp2IL会成为Unity逆向的刚需工具做Unity逆向的人迟早会撞上IL2CPP这堵墙。Unity早期用的是Mono后端编译出来的程序集就是标准的.NET DLL直接拖进dnSpy或者ILSpy就能看到完整的C#代码变量名、方法名、类结构一清二楚。但从Unity 2018开始官方主推IL2CPP作为脚本后端情况就完全变了——C#代码不再以IL中间语言的形式存在而是先被转成C再由C编译器编译成原生机器码。这意味着你拿到手的GameAssembly.dll或者libil2cpp.so里面全是汇编指令想直接读逻辑基本等于看天书。Cpp2IL就是专门为解决这个问题而生的。它的核心思路是既然IL2CPP在编译过程中会生成一份global-metadata.dat文件里面保存了所有的类型信息、方法签名、字符串字面量、字段偏移等元数据那么只要把这份元数据和原生的二进制文件对应起来就能重建出接近原始的.NET程序集。重建出来的DLL虽然不一定能100%还原原始代码比如方法体可能只有桩代码但类型结构、方法名、字段名、字符串引用这些关键信息基本都能恢复对于分析游戏逻辑、定位关键函数、做Mod开发来说已经足够了。这个工具在Unity逆向圈子里的地位类似于Android逆向里的jadx或者apktool——不是万能的但没有它很多活根本没法干。尤其是现在越来越多的Unity游戏和VR应用比如Pico平台上的内容都默认用IL2CPP打包Cpp2IL几乎是绕不过去的第一站。它适合几类人做游戏安全分析的、想研究Unity应用内部逻辑的、需要给Unity应用做汉化或功能扩展的以及单纯对IL2CPP机制好奇想一探究竟的技术爱好者。需要提前说清楚的是这个工具的使用场景应该限定在合法的范围内比如分析自己开发的应用、学习IL2CPP的编译原理、做安全研究等。下面所有内容都基于这个前提展开。2. 安装Cpp2IL从下载到跑通第一条命令2.1 获取正确的版本比什么都重要Cpp2IL的发布渠道主要在GitHub上作者是SamboyCoding。这里第一个坑就来了很多人搜cpp2il 插件下载失败或者failed to download cpp2il大概率是因为网络环境或者找错了发布页。正确的做法是直接去项目的Releases页面根据你的目标平台选择对应的压缩包。版本选择上有个经验不是越新越好。Cpp2IL的版本和Unity版本之间存在兼容性关系。比如你要分析的是Unity 2021.3打包的应用用最新的Cpp2IL可能反而会因为元数据格式变化而报错。我一般会先确认目标应用的Unity版本后面会讲怎么确认然后去Releases里找发布时间相近的版本。下载下来的压缩包解压后目录结构大致是这样的Cpp2IL/ ├── Cpp2IL.exe # Windows主程序 ├── Cpp2IL # Linux/macOS可执行文件 ├── LibCpp2IL.dll # 核心解析库 ├── Cpp2IL.Core.dll # 核心逻辑 └── ...其他依赖Windows下直接双击exe会弹出一个命令行窗口但更推荐用终端手动调用因为需要传参数。Linux和macOS下需要先给可执行文件加权限chmod x Cpp2IL注意Linux下运行需要确保系统有对应版本的.NET运行时。Cpp2IL从某个版本开始依赖.NET 6如果运行时报You must install .NET to run this application去微软官方文档装一下对应版本的运行时即可。2.2 依赖环境里最容易忽略的两个细节第一个细节是.NET运行时版本。Cpp2IL的不同版本对.NET的要求不一样早期版本用.NET Framework 4.7.2后来迁移到了.NET 6和.NET 8。如果你在Windows上双击没反应大概率是缺运行时。可以在终端里运行dotnet --list-runtimes看看装了哪些版本。第二个细节是目标文件的完整性。IL2CPP应用的核心文件有两个GameAssembly.dllWindows或libil2cpp.soAndroid/Linux以及global-metadata.dat。这两个文件必须来自同一个构建版本不能混用。我见过有人拿A版本的metadata去配B版本的so文件结果Cpp2IL直接崩溃或者输出一堆乱码。判断是否匹配的简单方法看文件的修改时间是否接近以及metadata文件头部的版本号是否和二进制中的字符串对得上。2.3 跑通第一条命令最小化验证安装完成后先用一个最简单的命令验证工具能正常工作Cpp2IL --help如果能看到参数列表说明基本环境没问题。接下来用一个真实的IL2CPP应用做测试。假设你的目标文件结构是TargetApp/ ├── GameAssembly.dll ├── global-metadata.dat └── ...其他文件最基本的调用方式是Cpp2IL --game-path /path/to/TargetApp --output /path/to/outputCpp2IL会自动在game-path目录下寻找GameAssembly.dll和global-metadata.dat。如果文件在非标准位置可以用--metadata-path和--game-assembly-path手动指定。跑完之后output目录下会出现几个关键文件输出文件内容说明DummyDll/重建的.NET程序集可用dnSpy打开il2cpp.h生成的C头文件包含类型定义script.json方法地址映射逆向时定位函数用stringliteral.json字符串字面量及其地址其中DummyDll目录是最常用的里面每个原始程序集都会生成一个对应的DLL。虽然方法体是空的只有throw new NotSupportedException()之类的桩但类型、方法签名、字段、属性、事件这些元数据都是完整的。3. 参数配置与输出解读让Cpp2IL按你的需求工作3.1 常用参数组合与适用场景Cpp2IL的参数不算多但组合起来能应对不同需求。下面这张表是我在实际使用中总结的常用组合参数作用推荐场景--game-path指定游戏根目录文件在标准位置时用--output指定输出目录必填建议单独建目录--use-processor启用特定处理器需要额外分析时用--analyze-all分析所有方法需要完整方法体分析时用--skip-metadata-txts跳过文本元数据生成只想要DLL时加速--disable-registration-prompts禁用注册提示自动化脚本中必加一个比较完整的调用示例Cpp2IL \ --game-path ./TargetApp \ --output ./cpp2il_output \ --analyze-all \ --use-processor AttributeAnalysis \ --disable-registration-prompts--analyze-all会尝试分析方法体虽然IL2CPP的方法体已经被编译成原生代码Cpp2IL无法完全还原但它可以通过分析调用约定和寄存器使用来推断一些信息。这个选项会显著增加处理时间如果只是想要类型结构不加也行。3.2 DummyDll里到底有什么没有什么打开DummyDll目录下的DLL用dnSpy加载后你会看到完整的命名空间树、类结构、方法列表。但有几个关键点需要理解有的东西所有类型定义包括类、结构体、枚举、接口方法签名包括参数类型和返回类型字段定义和偏移量在il2cpp.h里更详细字符串字面量的引用关系自定义Attribute部分没有的东西方法体的实际实现全是桩局部变量名编译后丢失部分泛型实例化的具体类型这意味着你不能指望在dnSpy里直接看到某个方法的逻辑但你可以知道这个方法存在它接收什么参数返回什么被谁调用。对于逆向来说这已经能帮你画出大致的调用图了。3.3 用script.json定位关键函数script.json是Cpp2IL输出里被低估的一个文件。它记录了每个方法的RVA相对虚拟地址和名称的映射关系。格式大致如下{ ScriptMethod: [ { Address: 12345678, Name: PlayerController::Update, Signature: System.Void PlayerController::Update() } ] }有了这个映射你就可以在IDA或者Ghidra里直接跳转到对应地址看反汇编代码。具体做法是在IDA里加载GameAssembly.dll然后按G跳转到12345678 ImageBase的位置。ImageBase通常是0x18000000064位或0x1000000032位具体看PE头。这个流程我走过很多次效率比盲搜字符串高得多。比如你想找玩家血量相关的逻辑先在DummyDll里搜Health或HP相关的类和方法拿到方法名再去script.json里查地址最后在IDA里看反汇编。三步定位比直接翻汇编快一个数量级。4. 实战中绕不开的坑从报错到成功输出4.1 元数据版本不匹配最常见的失败原因Cpp2IL报错里出现频率最高的就是元数据版本问题。典型报错长这样Failed to load metadata: Unsupported metadata version 29或者Metadata version mismatch: expected 24.5, got 27.1这个问题的根源是Unity在不同版本里修改了global-metadata.dat的格式。Cpp2IL内部维护了一个版本支持列表如果你的目标文件版本超出了当前Cpp2IL支持的范围就会直接拒绝加载。解决办法有两个一是换用更新版本的Cpp2IL如果已经支持该版本二是换用旧版本的Cpp2IL如果新版本反而移除了对旧格式的支持。我一般会保留两三个不同版本的Cpp2IL遇到不兼容就切换。怎么确认metadata的版本用十六进制编辑器打开global-metadata.dat前4个字节是魔数0xFAB11BAF紧接着的4个字节就是版本号。小端序读取比如1D 00 00 00就是版本29。4.2 文件路径与权限问题Windows下最常见的问题是路径里有中文或空格。Cpp2IL对路径的处理不够健壮遇到中文路径可能直接崩溃。解决办法很简单把目标文件和输出目录都放在纯英文、无空格的路径下比如D:\work\cpp2il_test\。Linux下则是权限问题。如果你从Windows拷贝文件到Linux可执行权限可能丢失。另外如果目标应用的libil2cpp.so没有读权限Cpp2IL也会报错。用ls -la确认一下权限必要时chmod r。还有一个隐蔽的坑某些应用会把global-metadata.dat加密或混淆。这种情况下Cpp2IL会报invalid metadata magic之类的错误。遇到这种情况需要先脱壳或解密这就超出Cpp2IL本身的能力范围了。4.3 输出DLL打不开或加载报错有时候Cpp2IL跑完了输出目录里也有DLL但用dnSpy打开时报Invalid assembly或者直接崩溃。原因通常是输出不完整Cpp2IL在处理大型应用时可能中途出错但没报致命错误导致DLL写了一半。检查输出日志里有没有Exception或Error关键字。依赖缺失某些DLL依赖其他程序集而Cpp2IL没有生成对应的依赖。把整个DummyDll目录一起加载通常能解决。dnSpy版本问题老版本dnSpy对某些.NET版本支持不好换最新版试试。我个人的习惯是每次跑完Cpp2IL后先看输出目录的文件数量和大小是否合理。一个中等规模的Unity游戏DummyDll目录下通常有几十到上百个DLL总大小在几MB到几十MB之间。如果只有几个文件或者大小异常小说明处理过程出了问题。4.4 处理时间过长与内存不足大型Unity应用比如几个GB的游戏的global-metadata.dat可能有几十MBCpp2IL加载和解析这些数据会消耗大量内存。在配置较低的机器上可能会遇到OutOfMemoryException。应对策略给Cpp2IL分配更多内存如果是.NET程序可以通过环境变量DOTNET_GCHeapHardLimit调整使用--skip-metadata-txts跳过不必要的文本输出在Linux服务器上跑内存通常比个人电脑充裕处理时间方面一个中等规模的应用大概需要几分钟到十几分钟。如果超过半小时还没结束大概率是卡住了检查一下是不是某个方法分析陷入了死循环。5. 从DummyDll到可读代码后续分析链路怎么搭5.1 用dnSpy做静态浏览与搜索拿到DummyDll后dnSpy是最顺手的浏览工具。加载所有DLL后你可以按命名空间浏览类型结构用CtrlShiftK全局搜索类型、方法、字段、字符串查看方法的引用关系右键 - Analyze导出特定类型为C#代码虽然方法体是空的但通过搜索字符串和类型名你能快速定位到关键逻辑所在的类。比如搜Damage能找到战斗相关的类搜Save能找到存档相关的类。5.2 结合IDA/Ghidra做动态与静态分析DummyDll给你的是地图IDA/Ghidra给你的是地形。两者结合的方式是在DummyDll里找到目标方法记下方法名在script.json里查该方法的RVA在IDA里跳转到对应地址看反汇编如果应用能运行用调试器在对应地址下断点动态观察参数和返回值这个流程对于分析关键算法比如加密、校验、网络协议特别有效。我一般会先用DummyDll缩小范围到几个候选方法再用IDA逐个确认。5.3 字符串搜索的妙用stringliteral.json里保存了所有字符串字面量及其地址。这个文件在分析时非常有用因为字符串往往是定位功能的捷径。比如你想找登录逻辑搜login、password、token这些关键词拿到地址后在IDA里交叉引用很快就能找到处理函数。一个实战技巧把stringliteral.json转成CSV格式用Excel或脚本按字符串内容排序然后批量搜索敏感关键词。这比在IDA里一个个搜快得多。5.4 处理泛型与接口的注意事项IL2CPP对泛型的处理比较特殊每个泛型实例化都会生成独立的原生代码。Cpp2IL在重建时对于泛型方法的处理可能不完整DummyDll里看到的泛型方法签名可能缺少具体的类型参数。遇到这种情况不要纠结于DummyDll里的签名直接去il2cpp.h里找对应的C模板实例化。头文件里的信息通常比DLL更完整。接口方面IL2CPP会生成接口方法的跳转表Cpp2IL能恢复出接口定义但具体的实现类可能需要手动对应。在DummyDll里接口和实现类的关系是保留的通过Find Usages能找到所有实现。6. 几个容易被忽视的实操细节6.1 保留原始文件副本Cpp2IL在处理过程中不会修改原始文件但为了保险起见我习惯在操作前把GameAssembly.dll和global-metadata.dat复制一份到工作目录。这样即使后续操作出了问题原始文件也不会受影响。6.2 输出目录的命名规范建议输出目录名包含目标应用名和Cpp2IL版本号比如MyGame_cpp2il_v2022.1.0。这样当你同时分析多个应用或使用多个版本时不会搞混。我见过有人把所有输出都放在一个目录里结果DLL互相覆盖排查了半天才发现是文件混了。6.3 日志的重要性Cpp2IL运行时会在终端输出大量日志。不要忽略这些日志尤其是Warning级别的信息。比如Failed to resolve type XXX这类警告说明某个类型没能正确重建后续分析时如果发现某个类缺失就要回头查日志。可以把日志重定向到文件Cpp2IL --game-path ./TargetApp --output ./out 21 | tee cpp2il.log这样方便后续搜索关键字。6.4 版本管理Cpp2IL更新比较频繁不同版本的行为可能有差异。建议用版本管理工具比如git或者简单的目录分类来管理不同版本的Cpp2IL。我自己是建了一个tools/cpp2il/目录下面按版本号建子目录需要哪个版本就切到对应目录。6.5 关于cpp2il 插件下载失败的排查思路这个搜索词出现频率很高说明很多人卡在下载环节。排查思路是确认访问的是GitHub Releases页面而不是第三方转载站检查网络是否能正常访问GitHub这是技术问题不展开确认下载的是完整的压缩包而不是网页HTML解压后确认文件完整性对比文件大小和Release页面的说明如果GitHub访问确实有困难可以找找有没有国内镜像或者别人转存的版本但要注意文件安全性最好校验一下哈希值。7. 关于合法使用与边界的一些个人看法Cpp2IL本身是一个技术工具它的价值在于帮助开发者理解IL2CPP的编译产物、做安全研究、学习Unity的底层机制。我在实际工作中用它最多的场景是分析自己开发的Unity应用为什么在某个平台上崩溃、研究第三方SDK的内部实现、以及做安全审计时定位潜在风险点。需要明确的是用这个工具去分析他人的商业应用并修改、破解、传播是另一回事。技术能力是一方面使用边界是另一方面。我个人的原则是只分析自己有权分析的目标只做学习和技术研究用途不碰商业破解和盗版分发。另外Unity官方其实也提供了一些合法的分析手段比如Development Build加上Profiler能看到很多运行时信息。Cpp2IL更多是在没有源码、没有调试符号的情况下的一种补充手段。两者结合使用效率更高。最后分享一个小心得Cpp2IL的输出质量很大程度上取决于目标应用的构建方式。如果开发者开启了代码混淆或者元数据加密Cpp2IL的效果会大打折扣。这种情况下与其硬啃Cpp2IL的输出不如先花时间研究混淆方案本身找到解混淆的切入点再回头用Cpp2IL做结构恢复。顺序对了事半功倍。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表