验收一款 iOS 手游的加固,平台报告写着"都做了"。报告归报告,到底扛到什么程度还得自己实测。用的就是攻击者那套工具,IDA / Hopper、Frida、Il2CppDumper,从静态到运行时逐层过一遍。
静态:符号和明文还剩多少
主二进制先拖进 IDA。App Store 包主二进制带 FairPlay 加密(LC_ENCRYPTION_INFO_64 的 cryptid=1),静态直接看是密文;但这只挡"从商店包静态扒",越狱机上 frida-ios-dump、dumpdecrypted 等系统把页解开后从内存 dump,一样能拿到可分析的 Mach-O。
拿到能分析的二进制,重点看两样:__TEXT,__cstring 里明文字符串还剩多少(接口地址、开关字段、检测标记能不能直接搜到),关键函数顺着字符串引用能不能定位。只做了 Strip 的,符号表删了但字符串和交叉引用还在,顺着 reward_daily 之类的文案就能摸到发奖逻辑。保护做在哪一环也拉开差距:成品包外面套壳的,运行时一解就能从内存 dump 出原始代码;做进编译阶段的(字符串编译期成密文常量、控制流被打乱),静态才读不动。
IL2CPP:metadata 是不是只加密没混淆
Unity 项目重点查 global-metadata.dat,路径在 .app/Data/Managed/Metadata/,头四字节 magic 0xFAB11BAF(内存里 AF 1B B1 FA)。只加密了这个文件的,游戏一跑它就在内存里解成明文,frida-il2cpp-bridge 不碰密钥,等进程解开后按 magic 从内存搜出来 dump 走;再用 Il2CppDumper 配上二进制,sub_XXXX 全还原成 PlayerController$$AddGold 这种可读名。
看 dump 出来的名字就够判断了:还是可读的,就是只加密没混淆。编译前把类名方法名混成 a1、b2、再配运行时反 dump 的,才算做到位,那样从内存 dump 出来也只剩 sub_xxxx。
资源:整包解密的明文窗口
热更缓存目录里 grep 一下 UnityFS(55 6E 69 74 79 46 53),首包加密、热更下发的 bundle 还是明文,这种碰得不少。另一个点是加载方式:整包一次性解密再交给引擎的,内存里会出现完整明文,hook 加载入口一下就整包捞走。
byte[] plain = Decrypt(File.ReadAllBytes(path), key); // 整包明文
AssetBundle ab = AssetBundle.LoadFromMemory(plain); // hook 这里 dump plain
Decrypt 写得再复杂也没参与,攻击者截的是解完的 plain。做到位的是包内和热更统一保护、按需分块解、per-resource key,让运行时内存里不出现整包完整明文。
运行时:越狱检测和单点 bool
越狱检测这块,还在探 /Applications/Cydia.app、/bin/bash 这类固定路径的,对 Dopamine、palera1n 的 rootless 越狱没什么用,环境早挪到了 /var/jb;就算补了路径,Shadow、A-Bypass 会 hook stat、access 回一个正常结果。
检测结构是另一处,最脆的是所有检测收敛成一个返回 bool 的总开关:
bool isSafeEnvironment() {
return checkJailbreak() && checkDebugger() && checkSignature();
}
一条 MOV W0, #1 ; RET 把返回值改成 true,前面查得再全也一起废。扛得住改的做法是把信号铺开:沙箱内不该成功的 fork、_dyld_get_image_name() 里的可疑动态库、Frida 的特征端口和线程,几个一起看,结果掺进后续逻辑,不收敛成一个能被一处 patch 掉的 bool;特征也得跟着新样本更新,不是写死一次就完。
上报:客户端判还是服务端判
看客户端算完是不是直接报一个 safe / unsafe。是的话这层就没用了:这个结论能被 hook 成永远安全,或者干脆不上报,而"干净设备没上报"和"被改了不上报"在后端看着一样。稳妥的做法是把结果当证据,带 HMAC 签名和 nonce 报给服务端,"该报没报"本身也算一个信号;金币、掉落、胜负这类最终判定放在服务端,客户端只负责把伪造成本抬上去。
排查检查项
验收时对着过一遍:
- 静态:解密后拖 IDA,关键明文字符串搜不到、关键函数顺不出来,保护在编译阶段
- IL2CPP:dump 出来是
a1/b2,有运行时反 dump - 资源:首包加热更统一、按需分块、内存无整包明文
- 运行时:多信号组合、检测不收敛成单点 bool、特征可更新
- 上报:签名加
nonce报服务端、缺报也算信号、最终判定在服务端
强度取决于这几层接没接上:攻击者绕过一层,能不能顺势进下一层。哪层最弱,就决定了整体能挡到哪。
每层的原理和完整方案在字节暗面主站,可搜「字节暗面 iOS 游戏加固」。

956

被折叠的 条评论
为什么被折叠?



