一次游戏安全SO静态分析记录:腾讯ACE与FairGuard加固强度对比
最近用GPT-5.6 Sol分析了几份Android SO,感受比较明显:AI已经可以承担不少二进制静态分析中的重复工作。把ELF头、节区、符号、字符串、重定位和函数展开信息交给它批量整理,再回到工具输出逐项核对,效率比手工来回翻结果高很多。
这次我选择了两款手游中的安全模块,分别来自FairGuard和腾讯ACE。目的很直接:在相同分析口径下,看看两个SO向常规工具暴露了多少结构与语义信息,以及分析者能否快速建立代码地图。
下面记录完整的样本信息、复现命令、指标口径和量化结果。
一、测试对象与分析环境
| 项目 | FairGuard | 腾讯ACE |
|---|---|---|
| 游戏 | 《闪烁之光》 | 《火影忍者》 |
| 游戏版本 | 4.4.8 | 1.79.79.9 |
| 测试文件 | libFairGuard.so |
libtersafe.so |
| SHA-256 | e34dd793cb58f360fc235e0375486a9a116154d664724ce5e688a46c611bf4fe |
517f1e3891e7a7c5dd8254bedcb09a5fe1d2acb0147cec757efb3a06ec5b2a10 |
| 架构 | AArch64 | AArch64 |
| 文件大小 | 5.16 MiB | 5.52 MiB |
| ELF类型 | 64位共享库 | 64位共享库 |
| Strip | 是 | 是 |
解压安装包后,可以在lib/arm64-v8a目录中找到对应模块。两份样本架构一致、体积接近,并且都已经Strip,适合放在同一套静态指标下观察。
AI辅助部分使用GPT-5.6 Sol,推理强度设置为“极高”,基础指令为:
分析两个SO文件的加固强度并进行量化对比。
这里让模型负责整理检查项、批量统计数据和发现异常,再用ELF工具输出复核关键结论。常用基础命令如下:
sha256sum libFairGuard.so libtersafe.so
file libFairGuard.so libtersafe.so
readelf -hW -lW -SW -sW -rW -dW libFairGuard.so
readelf -hW -lW -SW -sW -rW -dW libtersafe.so
readelf --debug-dump=frames libFairGuard.so
readelf --debug-dump=frames libtersafe.so
strings -a -n 8 libFairGuard.so
strings -a -n 8 libtersafe.so
这些命令可以复核ELF头、程序段、节区、动态符号、重定位、动态项、字符串和FDE记录。节区覆盖率、无节区区域、分档字符串数量以及局部熵值,则需要在这些结果的基础上继续用脚本统计。
本次采用的几个指标口径如下:
- 节区表覆盖文件比例:有文件内容的节区所描述的文件范围,占整个文件的比例。
- 最大无节区描述区域:文件偏移范围内,没有被节区表描述的最大连续区间。
- 可打印字符串:连续可打印ASCII内容,按长度≥8、≥16和≥32分别统计。
- FDE记录:
.eh_frame中能够正常解析的Frame Description Entry。 - 局部熵值:将RX段按64 KiB分块,统计熵值≥7.8和熵值<1的块。
二、先看ELF结构:工具能否直接找到代码主体
| 指标 | FairGuard | 腾讯ACE |
|---|---|---|
| 节区数量 | 21 | 28 |
| 程序段数量 | 6 | 9 |
| 节区表覆盖文件比例 | 6.94% | 99.96% |
| 最大无节区描述区域 | 4,805,144字节 | 1,799字节 |
| 最大无节区区域占比 | 88.80% | 0.03% |
声明的.text大小 |
16字节 | 3,590,944字节 |
.text零字节比例 |
100% | 10.10% |
| RX段中可执行节区覆盖率 | 0.0039% | 65.74% |
FairGuard样本最明显的特征是.text只有16字节,而且内容全部为零。它的ELF入口为0xfe00,落在节区表没有描述的区域。整个文件最大的无节区区域达到4,805,144字节,占比88.8%。
对IDA、Ghidra、radare2一类工具来说,标准节区原本是建立初始代码地图的重要依据。遇到这种布局后,只按节区表分析会漏掉主体内容,需要转向程序段、入口地址和运行时装载逻辑继续识别。
腾讯ACE样本的节区表覆盖率为99.96%,.text、.rodata、.eh_frame、重定位表和版本表均能正常定位。常规工具导入后,可以直接依据标准ELF结构开始分析。
这一组数据也是两个样本拉开差距最大的地方:FairGuard优先破坏了工具赖以建立结构的静态地图,腾讯ACE则保留了更标准的共享库布局。
三、动态符号:符号很多,未必能提供有效信息
| 指标 | FairGuard | 腾讯ACE |
|---|---|---|
| 动态符号总数 | 1,028 | 288 |
| 空名称符号 | 1,002(97.47%) | 1(0.35%) |
| 可读符号比例 | 2.24% | 99.65% |
| 可读导出接口 | 0 | 71 |
| 唯一导入函数名 | 4 | 216 |
| 4字节伪函数条目 | 999 | 2 |
| 不落在声明节区的符号 | 1,007 | 0 |
readelf一致性警告 |
10条 | 0 |
单看数量,FairGuard拥有1,028个动态符号,比腾讯ACE更多。继续检查会发现,其中1,002个没有名称,999个函数条目的长度固定为4字节,还有1,007个符号地址与声明节区对不上。
它另外保留了3个非空导出名称,但内容属于不可读字节序列,因此可读导出接口统计为0。readelf还给出了10条本地符号顺序异常。这样的符号表很难充当有效的分析导航,更接近干扰信息。
腾讯ACE样本中的288个动态符号结构正常,可读符号比例达到99.65%,其中可直接识别71个导出接口,例如:
JNI_OnLoadtss_sdk_inittss_sdk_encryptpackettss_sdk_decryptpackettss_sdk_ischeatpackettss_recv_sec_signature
部分名称属于SDK需要公开的接口,但它们同样会成为静态分析中的路标。分析者可以从初始化、数据收发或加解密接口进入,再沿交叉引用向内部追踪。
四、字符串:还能看到多少功能语义
短字符串容易受到高熵数据随机组合的影响,因此这里重点统计8字节以上的连续可打印ASCII内容。
| 指标 | FairGuard | 腾讯ACE |
|---|---|---|
| 长度≥8的字符串 | 427 | 4,224 |
| 长度≥16的字符串 | 27 | 1,624 |
| 长度≥32的字符串 | 12 | 370 |
.rodata可打印字符串 |
6 | 10,880 |
| 源码路径 | 0 | 6 |
明文JNI_OnLoad |
未发现 | 可见 |
腾讯ACE中长度达到16字符的字符串有1,624条,约为FairGuard的60倍。样本里还可以看到NDK版本、Build ID和部分指向.cpp、LLVM源码的编译路径。
FairGuard主要保留依赖库名称及GCC 4.9编译痕迹,没有提取到能够直接指向反调试、Hook或签名校验等功能的明文关键词。只从字符串入手,很难快速还原模块内部的功能分布。
字符串保护看起来只是“少暴露一些文字”,实际会影响整个分析节奏。错误提示、日志、文件路径、接口名称和协议字段经常被用来给未知函数命名;这些线索收敛以后,后续交叉引用也会失去一批明确锚点。
五、重定位、初始化入口与函数边界
| 指标 | FairGuard | 腾讯ACE |
|---|---|---|
| 重定位总数 | 93 | 25,380 |
| INIT/FINI目标 | 4 | 66 |
位于标准.text的INIT/FINI目标 |
0 | 66 |
| 位于无节区区域的INIT/FINI目标 | 4 | 0 |
| 可恢复FDE记录 | 0 | 16,286 |
.eh_frame零字节比例 |
100% | 32.51% |
.gcc_except_table零字节比例 |
100% | 38.33% |
FairGuard的4个构造与析构目标全部位于无节区描述区域。.eh_frame和.gcc_except_table虽然保留了节区名称,内容却全部为零,readelf只能识别一个终止符,无法恢复有效FDE记录。
腾讯ACE可以解析出16,286条FDE记录。即使这些记录不带函数名称,IDA、Ghidra等工具仍然可以利用它们识别函数边界和栈展开关系。
重定位总数会受到代码规模、编译器和链接方式影响,单项数值不适合直接判定强弱。这里将它和节区布局、符号表、初始化入口、FDE一起观察,可以看到腾讯ACE保留了更多标准化交叉引用信息;FairGuard则让多个静态入口同时失效。
六、为什么整体熵值不能直接代表加固强度
| 指标 | FairGuard | 腾讯ACE |
|---|---|---|
| 整体信息熵 | 4.626 | 6.456 |
| 文件零字节占比 | 54.43% | 21.89% |
| RX段中熵≥7.8的64 KiB块 | 35/83,42.17% | 4/84,4.76% |
| RX段中熵<1的64 KiB块 | 42/83,50.60% | 0/84 |
如果只比较整体熵,腾讯ACE的6.456高于FairGuard的4.626,很容易把这个结果直接理解成腾讯ACE的加密或压缩程度更高。但FairGuard文件中有54.43%的零字节,大量零填充会显著拉低整体熵。
换成64 KiB分块后,FairGuard的RX段里有35个高熵块,同时存在42个低熵零块。结合88.8%的无节区区域,这种“高熵载荷与大块零区并存”的分布更接近加密数据、隐藏载荷或自定义布局。
腾讯ACE的整体数据分布更连续,RX段中仅有4个分块达到7.8以上。由此看,熵值要和文件布局、零字节比例及分块位置一起解释,单一平均值很容易掩盖真实结构。
七、基础ELF安全属性
| 属性 | FairGuard | 腾讯ACE |
|---|---|---|
| NX Stack | 通过 | 通过 |
| GNU RELRO | 通过 | 通过 |
| BIND_NOW | 通过 | 通过 |
| Full RELRO | 通过 | 通过 |
| TEXTREL | 无 | 无 |
| RWX加载段 | 无 | 无 |
这一部分双方表现基本一致:都开启了NX、GNU RELRO、BIND_NOW和Full RELRO,也没有TEXTREL或RWX加载段。基础编译和链接安全属性都比较完整。
八、静态抗分析强度量化
业内没有统一的SO加固百分制。为了汇总本次观察结果,我按“常规静态工具能够直接利用的信息量”设计了一个百分制模型。总分方便快速对照,具体判断仍以原始指标为准。
| 评分维度 | 权重 | FairGuard | 腾讯ACE |
|---|---|---|---|
| ELF结构与节区隐藏 | 25 | 23 | 8 |
| 符号与接口信息隐藏 | 20 | 18 | 11 |
| 字符串与构建信息保护 | 15 | 14 | 8 |
| 初始化、重定位及FDE隐藏 | 15 | 14 | 7 |
| 数据不透明度与局部熵特征 | 15 | 13 | 9 |
| 基础ELF安全属性 | 10 | 10 | 10 |
| 总分 | 100 | 92 | 53 |
libFairGuard.so在这套静态指标下得到92分。它的代码主体没有通过常规节区完整呈现,动态符号中存在大量无名、定长及越界条目,字符串、重定位和函数展开信息也明显收敛。常规工具很难直接恢复有效代码地图。
libtersafe.so得到53分。该样本同样完成Strip,并具备完整的基础ELF安全属性;它保留了较标准的共享库布局,符号、接口、字符串、重定位和FDE信息可以被工具较完整地提取,初始结构恢复相对直接。
九、这次分析留下的几个检查思路
做完这轮对比,我觉得判断SO静态强度时,下面几项值得形成固定检查顺序:
- 先看程序段和节区表是否一致。
.text大小正常,并不代表全部可执行内容都被节区完整描述;入口和构造函数落点同样重要。 - 统计有效符号,不只看符号总数。 空名称比例、固定长度函数、异常地址和可读导出数量,更能反映符号表是否可用。
- 字符串要分长度并结合所在节区。 单纯运行一次
strings很容易被随机短字符串干扰。 - 把FDE当成函数边界线索。 经过Strip的SO仍可能从
.eh_frame恢复出大量函数范围。 - 熵值必须分块看。 整体均值会被零填充拉低,也会掩盖局部高熵区域。
- 最后再做量化评分。 原始数据负责提供证据,分数只用于汇总差异。
小结
GPT-5.6 Sol在这次分析中的价值,主要体现在统计和整理:它能快速把分散在多条命令中的数据归到同一套指标下,也能提醒我关注符号地址、节区覆盖率和局部熵分布等异常点。涉及关键结论时,仍要回到readelf、strings和脚本统计结果逐项确认,这样输出才方便复现。
回到两个样本,双方的基础ELF安全配置相近,真正拉开静态分析难度的是结构和语义信息的暴露程度。FairGuard在节区隐藏、符号干扰、字符串收敛和函数边界隐藏方面更彻底;腾讯ACE保留了较多标准ELF结构及可读接口,常规工具更容易建立初始分析框架。
这也说明,只确认SO有没有Strip远远不够。把ELF布局、符号、字符串、重定位、初始化入口和FDE放到一起检查,才能更接近常用逆向工具实际面对的分析阻力。
浙公网安备 33010602011771号