一次 IL2CPP 包的代码保护排查:global-metadata.dat 是怎么被 dump 出来的

现象

一个 Unity 手游包,用了 IL2CPP,团队认为代码保护到位了:C# 编成 native,Assembly-CSharp.dll 那条 dnSpy 反编译路已经没了,拖进去一片空白。但实际拿到包做静态分析时,业务侧的类名、方法名、接口地址几乎没费什么劲就出来了。下面按排查顺序记录整个过程和结论。

排查

1. 解包,定位关键文件

先把 APK 解开,关注两个位置:

lib/arm64-v8a/libil2cpp.so                              # C# 编译后的 native 代码
assets/bin/Data/Managed/Metadata/global-metadata.dat    # 元数据,运行时反射要用

libil2cpp.so 里是真正的逻辑,但直接 strings 扫,业务字符串基本扫不到——native 化把符号信息剥掉了。问题出在旁边那个 global-metadata.dat

2. 看 metadata 文件头

global-metadata.dat 拖进十六进制编辑器,看开头四字节:

AF 1B B1 FA

这是 IL2CPP 元数据的 sanity 值(0xFAB11BAF,小端)。看到它是明文,说明 metadata 没做任何加密,是裸的。头结构往后是成对的 offset/size,把类型定义、方法签名、字段偏移、字符串表一段段划开,类名方法名就明文存在字符串表里。

3. 跑 Il2CppDumper

有了这两个明文文件,直接喂给 Il2CppDumper:

Il2CppDumper.exe libil2cpp.so global-metadata.dat <output_dir>

它把 libil2cpp.so 里的地址和 metadata 里的名字对起来,产出 dump.csil2cpp.h、以及可以直接跑的 IDA / Ghidra 脚本。原本 IDA 里满屏的 sub_XXXXXX,脚本一跑就还原成带类名的可读方法:

// dump.cs 里长这样
public void AddGold(int amount) { }   // 对应 IDA 里的 PlayerController$$AddGold

到这一步,业务类、方法签名、接口字段基本都能对着看了。

分析

排查结论很明确:只上默认 IL2CPP,等于没锁。

原因在于 IL2CPP 的机制本身。native 这层丢掉了"关于代码的信息"——类叫什么、方法什么签名、字段偏移是多少,可 .NET 靠反射运行,运行时必须拿得到这些。IL2CPP 的办法是把它们从 native 里剥出来单独存成 global-metadata.dat,默认明文进包。也就是说,native 抹掉的符号,metadata 又原样交了回来。逆向根本不用碰 native 那层的算法,读文件就够。

方案

按成本从低到高,分两层加固,另外别把 IL2CPP 当成防护的全部。

第一层:加密 metadata。 在构建流水线里对 global-metadata.dat 加密,运行时在加载入口(MetadataCache::Initialize() 里调 LoadMetadataFile 那处)解回来。密钥别跟明文 metadata 进同一个仓库。

要清楚这一层的天花板:游戏要跑,metadata 就一定会在内存里被解成明文。攻击者不碰你的密钥,等进程解开直接内存 dump——Zygisk-Il2CppDumper 这类工具走的就是这条路。所以加密买到的是把攻击从"解包、跑 Il2CppDumper、导出"抬到"上 Frida、找 dump 点、绕反调试",能挡一批,但挡不死。

第二层:编译前混淆名字。 要让 dump 出来也读不懂,得在 IL2CPP 编译之前就把 C# 类名方法名混淆成无意义符号,写进字符串表的本来就是 a1b2 这种。注意名字不能全抹:反射按字符串查找的类型、序列化用到的字段、GetComponent 按名字取的组件,运行时要靠原名对上号,一并混淆会崩,得在混淆规则里显式排除。

再往上还能自编译 libil2cpp、改元数据布局让现成工具解析失效,但维护成本和跟 Unity 版本对齐的成本要一起算,按项目价值定。

最后一点:IL2CPP 只管"读代码"这一段。包被改重签名分发、进程跑起来被 hook 改数值、协议被原样重放,它一概不接——那分别是签名完整性校验、反调试反 hook、防重放各自的活,还有服务端判定。

检查项

排查完可以对照过一遍:

  • global-metadata.dat 头四字节是否还是明文 AF 1B B1 FA(是则未加密)
  • Il2CppDumper 能否直接吐出带名字的 dump.cs / IDA 脚本(能则符号未保护)
  • 类名方法名有没有在 IL2CPP 编译前混淆(否则 dump 到内存仍是可读名)
  • 混淆是否排除了反射 / 序列化 / GetComponent 依赖的类型(否则运行时崩)
  • metadata 加密是否放进构建流水线、密钥是否与明文 metadata 分仓
  • 签名完整性、防 hook、防重放、服务端判定是否各自落地(别指望 IL2CPP 兜)

(完整的两层加固思路和边界分析,在主站版本里拆得更细,可以搜「字节暗面 IL2CPP 代码保护」。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值