最近排查几个 Unity 项目时,我碰到过一种很典型的安全断层:APK 里的代码保护做得很重,符号、字符串、反调试一个不少;版本更新后,新角色、皮肤和活动配置却还是标准 Bundle。
首包守得很严,真正有时效价值的新内容又从热更链路漏了出来。
AssetBundle 本身是 Unity 的资源打包和加载格式,版本检查、下载、缓存和回滚通常由自研框架或 Addressables 完成。开发中常把这套流程统称为"AB 热更新",排查时最好拆成分发、缓存和加载三个环节。
一次更新请求能暴露多少信息
我一般先看版本检查。自研框架可能拉取 AssetBundleManifest、JSON 或二进制版本表,Addressables 会检查远程 content catalog。格式不同,里面通常都有 Bundle 名称、版本、依赖或下载位置。
一些项目的 CDN 路径也很规整:
text
https://cdn.example.com/hotfix/{channel}/{version}/{bundleName}.ab
channel 和 version 在请求里,bundleName 在清单里。URL 长期有效、缺少鉴权和限速时,遍历清单就能批量下载。甚至不必先拆 APK,在代理里走完一次更新流程就够了。
暴露 CDN 地址不是问题,客户端本来就要知道资源在哪。问题在于文件拿回来仍是标准 UnityFS,拖进 AssetStudio 一类工具即可继续分析。文件名换成 hash 只会让资源没那么好认;签名 URL 可以限制链接滥用,却拦不住合法客户端在有效期内拿到文件。
加密以后,历史目录和缓存仍可能漏
当前版本的 Bundle 加了密,我会继续检查历史目录。灰度发布和紧急回滚通常要保留旧资源,而早期 Bundle 未必同步补过加密。目录规则没变,往前试几个版本号,可能就能找到同一资源的明文版本。
另一个常见问题是本地缓存。CDN 下发的是密文,客户端解密后又把标准 Bundle 写进 Application.persistentDataPath,前面的处理等于只保护了传输。应用目录的访问限制只能抬高门槛,解决不了文件本身的可读性。
缓存最好保留密文,加载时再解。密钥也不建议所有 Bundle 共用一个固定值,可以按 Bundle 和版本派生工作密钥:
csharp
byte[] keyContext = UTF8($"{bundleName}:{version}");
byte[] salt = SecureRandomBytes(16);
byte[] resourceKey = HKDF(masterKey, salt, info: keyContext);
byte[] nonce = SecureRandomBytes(12);
byte[] cipherText = AESGCMEncrypt(
resourceKey,
nonce,
rawBundleBytes,
associatedData: keyContext
);
salt 和 nonce 可以和密文一起保存,但 nonce 不能在同一密钥下重复。这样能缩小单个工作密钥泄露后的影响范围。主密钥如果以明显常量留在客户端里,派生再多层也只是增加几步分析工作。
只校验文件 hash 还不够
Bundle 加密后,我还会尝试替换缓存文件并回退到旧版本。如果清单没有签名,攻击者可以同时替换 Bundle 和清单里的 hash,单独做 SHA-256 对比并不能证明这份版本信息来自发布端。
更完整的顺序是:
- 验证清单签名;
- 检查加密文件的 hash;
- 校验 AES-GCM 认证标签;
- 对比最低支持版本,拒绝危险回退。
Unity CRC 适合发现内容损坏或变化,AssetBundle hash 可以区分缓存版本,它们都不能直接替代发布端签名。战斗结算、掉落和购买结果等直接影响收益的数据,也不能因为放进了加密 Bundle 就交给客户端决定。
最难算的是加载性能
资源加密接进热更新后,真正容易踩坑的是加载路径。LZ4 Bundle 原本可以按块读取,外面套整文件加密,可能要先解完整包再加载。大型 Bundle 全部进入 byte[] 后调用 AssetBundle.LoadFromMemory,峰值内存也会明显上升。
我更倾向按资源价值分级:新角色、付费皮肤和活动配置优先保护,低价值通用资源采用轻量方案;大 Bundle 评估分块解密、流式读取或自定义 Provider。开发机上的数据不够,至少还要在中低端设备上测解密耗时、峰值内存和首帧时间。
接入这套保护,要改构建脚本、加载层、缓存和密钥管理,前期一定会增加成本。省掉这部分,以后每次更新都要重新排查明文 Bundle,出了问题还要清目录、换资源、重新发版。首包安全只是一个快照,热更以后还能不能保持,才是 AssetBundle 资源保护真正要算的工程账。