技术文档

Android Native ELF 加固推荐方案(JNI .so)

Android APK / AAB 内 JNI .so 的符号处理、ABI 与 16 KB 对齐、重打包重签名、JNI 加载验证与测试矩阵。

最后更新于 2025-06-06预计阅读 7 分钟

1. 适用范围与定位

本文针对 Android APK / AAB 中的 native 库 和 Android Native ELF 可执行文件,即:

lib/arm64-v8a/libxxx.so
lib/armeabi-v7a/libxxx.so
lib/x86_64/libxxx.so

典型来源:

  • C / C++ 核心算法、音视频处理、图像处理
  • 游戏引擎(Unity IL2CPP、Unreal、Cocos)
  • 加密、风控、反作弊 SDK
  • 第三方 .so(FFmpeg、OpenSSL、SQLCipher 等)

不适用于:

当前客户端的 Native ELF 能力可用于评估 .so 保护,但 Android 的加载器、包结构、对齐和签名有额外约束,必须重新打包、重签名并在真机验证。

2. 与整体 Android 加固的关系

Android 应用通常有两层保护:

层目标方案
Java / Kotlin 层Dex 反编译、改包、注入Dex 加密、反调试、反注入、Root / 模拟器检测
Native 层.so 逆向、Hook、算法还原符号移除、函数混淆、函数虚拟化、完整性校验

两层应组合使用,但不要重复处理同一份代码。只保护 .so 时,攻击者仍可修改 Java 层调用逻辑;只保护 Dex 时,核心算法仍可从 .so 中还原。

3. 保护选项与档位

3.1 可选能力

能力作用主要代价建议
移除符号删除导出符号与部分名称信息崩溃栈可读性下降发布版开
函数混淆打乱控制流单函数小幅开销核心函数开
函数虚拟化转换为自定义虚拟指令被虚拟化的函数慢数倍到数十倍少量核心函数
导入保护保护动态链接符号与 dlopen / dlsym 冲突视场景
压缩保护压缩 native 库启动解压 / 校验变慢,包越大越慢,可能影响 APK 对齐与加载谨慎评估
内存校验检测运行时修改少量运行时开销视场景
反调试检测调试器 / 注入与合法调试、部分设备冲突发布版开

性能提醒

  • 函数虚拟化会让被选中的 .so 函数运行速度慢数倍到数十倍;游戏每帧逻辑、音视频回调、高频 JNI 调用不要虚拟化。
  • 压缩保护与文件校验会增加 App 冷启动时间,.so / APK 越大越慢;extractNativeLibs=false 时还会影响加载与对齐。
  • 先用原始包测冷启动、首帧、JNI_OnLoad 初始化耗时,再决定保护范围;大体积游戏 / 音视频应用重点测低端机。

extractNativeLibs=false 的 APK 会把 .so 以未压缩、页对齐方式存放。修改 .so 后必须重新打包并正确对齐,否则可能出现 dlopen 失败或安装后无法加载。

3.2 基础档:兼容性优先

选项设置
移除符号开
函数保护5–10 个核心函数混淆
函数虚拟化关
导入保护关
压缩保护关

目标:先确认 .so 替换与重打包流程稳定,再增加强度。

3.3 标准档:商业发布

选项设置
移除符号开
函数保护授权、协议签名、核心算法虚拟化;业务逻辑混淆
函数虚拟化5–15 个核心函数
导入保护开(验证 JNI 与插件加载)
压缩保护视 APK 结构决定

3.4 强化档:高价值目标

选项设置
移除符号开
函数保护10–30 个核心函数虚拟化,其余敏感函数混淆
反调试 / 内存校验开,异常环境降级
完整性校验.so 哈希 + 服务端挑战
应用层Dex 加密 + 反注入 + Root / 模拟器检测

4. JNI 符号与加载机制

4.1 必须保留的符号

  • JNI_OnLoad:向 VM 注册 native 方法。
  • Java_<包名>_<类名>_<方法名>:静态注册的 JNI 方法。
  • 通过 RegisterNatives 动态注册时,被注册的函数本身可以隐藏,但注册逻辑必须保留。
  • 对外暴露的 C API(供其他 .so 链接)需要保留导出。

建议在编译期使用:

__attribute__((visibility("hidden")))

只导出必要符号,减少攻击面,也减少保护后符号解析失败的概率。

4.2 加载方式

  • System.loadLibrary("xxx"):由 Android 运行时按 ABI 选择 libxxx.so。
  • System.load("/absolute/path/libxxx.so"):验证绝对路径与权限。
  • dlopen / dlsym:验证符号名与命名空间限制。
  • Android 7+ 对私有平台库的链接有限制,避免依赖非 NDK 公开库。
  • 对插件化 / 动态加载框架,验证 .so 释放路径与 SELinux 权限。

4.3 常见崩溃点

  • JNI_OnLoad 被虚拟化或混淆后注册失败。
  • 导出符号被 strip 掉,导致 UnsatisfiedLinkError。
  • .so 与 ABI 目录不匹配,64 位设备加载了 32 位库。
  • 依赖 .so 的加载顺序变化,导致符号未定义。
  • 16 KB 页大小设备上段对齐不正确。

5. 打包、ABI 与重签名

5.1 处理流程

构建 APK / AAB
        ↓
解包,取出 lib/<abi>/*.so
        ↓
按 Native ELF 方案保护
        ↓
替换回原目录(保持文件名与 ABI)
        ↓
重新打包
        ↓
zipalign
        ↓
apksigner(原证书)
        ↓
真机回归

5.2 ABI 覆盖

ABI说明
arm64-v8a现代设备必测,Google Play 要求 64 位
armeabi-v7a仍支持 32 位设备时保留
x86_64模拟器、Chromebook、部分平板
  • 每个 ABI 目录都要处理,不能只替换 arm64-v8a。
  • 使用 unzip -l app.apk | grep lib/ 确认目录结构。
  • 使用 file libxxx.so 或 readelf -h 确认架构与保护前一致。

5.3 16 KB 页大小

面向 Android 15+ 与 16 KB 页大小设备时:

  • 确认 .so 的 LOAD 段对齐满足 16 KB 要求。
  • 使用 Android 官方工具检查 ELF 对齐。
  • 保护后重新打包,不要直接复用旧的未对齐产物。
  • 在支持 16 KB 页大小的真机或模拟器上验证加载。

5.4 重签名

zipalign -p -f 4 app-protected-unsigned.apk app-protected-aligned.apk

apksigner sign \
  --ks release.jks \
  --ks-key-alias release \
  --out app-protected.apk \
  app-protected-aligned.apk

apksigner verify --print-certs app-protected.apk
  • 必须使用原签名证书,否则无法覆盖安装。
  • AAB 不能直接安装,使用 bundletool 生成 APK 后测试。
  • Google Play 使用上传密钥重新签名,确认加固流程不影响上传签名。

6. 构建流水线

Gradle assembleRelease / bundleRelease
        ↓
原始包冒烟测试
        ↓
提取并保护 .so(所有 ABI)
        ↓
替换、重新打包
        ↓
zipalign + apksigner
        ↓
真机回归 + 性能测试
        ↓
分发 / 上架

建议:

  • 把 .so 保护放在签名之前,Dex 保护之后或之前均可,但要避免重复处理。
  • 每次构建记录输入 / 输出 .so 的 SHA-256 和 ABI。
  • 保留未 strip 的 .so 与符号文件,用于 tombstone 还原。
  • 对游戏 / 音视频应用,重点测试帧率、延迟和内存。

7. 验证清单

  • 原始 APK / AAB 可正常安装与启动
  • 所有 ABI 的 .so 均已处理
  • JNI_OnLoad、静态 / 动态注册方法正常
  • System.loadLibrary、dlopen、dlsym 正常
  • 依赖 .so 加载顺序正常
  • 覆盖安装、升级安装正常
  • 签名校验通过,可上架 Google Play
  • 16 KB 页大小设备加载正常
  • 崩溃 tombstone 可通过未 strip 符号还原
  • 最大体积 .so / APK 的冷启动已单独测试
  • 被虚拟化函数的帧率 / 延迟已对比基线
  • 冷启动、核心功能、帧率 / 延迟符合预期

8. 常见问题

8.1 启动时报 UnsatisfiedLinkError

  • 检查 .so 是否放入了正确的 ABI 目录。
  • 检查 JNI_OnLoad 与 Java_* 导出符号是否被移除。
  • 检查依赖库是否缺失或加载顺序错误。
  • 检查 APK 是否重新对齐、签名是否有效。

8.2 保护后 dlopen 失败

  • 检查 dlopen 的路径、命名空间和 SELinux 权限。
  • 检查导入符号是否被导入保护破坏。
  • 用 readelf -d 对比保护前后的 NEEDED 与 RPATH。
  • 在 adb logcat 中查看 linker 的具体错误。

8.3 只有部分设备崩溃

  • 优先检查 ABI 与 CPU 架构。
  • 检查 16 KB 页大小设备。
  • 检查厂商定制系统的加载限制与安全策略。
  • 收集 tombstone 并用未 strip 的 .so 还原。

8.4 游戏 / 音视频性能下降

  • 减少虚拟化函数数量,尤其是每帧调用的函数。
  • 改用混淆,或只保护非热路径。
  • 对引擎核心循环单独评估,不要全量虚拟化。
  • 在低端真机上测试,不只看旗舰机。

8.5 能否只保护 .so,不改 Dex

可以,但保护不完整。Java / Kotlin 层仍可被修改,建议同时使用 Dex 加密、反调试和反注入。整体方案见 Android 应用加固推荐方案(APK / AAB)。

8.6 AAB 如何测试

使用 bundletool build-apks 生成 APKS,再 install-apks 到设备。不要直接安装 .aab。

9. 相关文档

这份文档有帮助吗?欢迎反馈文档问题或补充场景。提交文档反馈