一、加固的核心目标
应用加固的核心思路是将原始的 DEX(Dalvik Executable)和 SO 文件加密后嵌入壳程序,等应用启动时由壳程序在内存中解密并动态加载执行。这样,即使攻击者获取了 APK,看到的也只是加密后的乱码数据,无法直接通过 jadx 或 baksmali 反编译。
商业加固(如 360 加固、梆梆、爱加密)的原理大体分为三个阶段:打包阶段——将原 DEX 加密后存放在 assets 或 lib 目录下,替换 Application 类为壳的入口类;启动阶段——壳 Application 在 attachBaseContext() 中加载解密 SO 库,从 assets 读取加密 DEX 到内存;执行阶段——解密并通过 DexClassLoader 或 InMemoryDexClassLoader 动态加载,反射调用原 Application 并移交控制权。
二、DEX 加密与动态加载
DEX 加密是最基础的加固手段。壳程序将多个 DEX 文件整体加密后存储,运行时在 Native 层解密。为对抗内存 dump,高级加固还会采用分段解密策略:在方法执行时才解密对应的代码区域,执行完后立即恢复加密状态,使攻击者无法通过单次 dump 获得完整的明文 DEX。
三、SO 保护
加固方案中的 SO 库负责加解密逻辑、反调试和完整性校验。对 SO 的保护手段包括:代码段加密(在 .init_array 中运行时解密)、符号表剥离(strip)、控制流平坦化(OLLVM -mllvm -fla)、字符串加密(编译器插件在编译期替换)。部分加固还会将核心代码以加密形式存放于 .rodata 段的自定义位置,运行时才映射为可执行内存并跳转。
四、完整性校验流程
加固 APK 在启动时会执行多轮完整性检查:校验 APK 签名(防止二次打包替换壳包)、校验自身 DEX 和 SO 的 CRC/哈希值(防止静态修改)、检测环境特征(Root、模拟器、hook 框架注入)。这些检查分散在 Native 层多处,形成互相验证的检查网络。
// Native 层 CRC 校验示例 |
面试常考问题
Q1: 加固为什么不能 100% 防止逆向?
A: 因为应用最终必须在 CPU 上执行,无论怎么加密,解密后的代码必然出现在内存中。攻击者可以在解密完成后的时间窗口内进行内存 dump(如通过 Frida 的 Memory.scan 或自定义 Kernel 模块),从而获取明文代码。加固的目标是提高逆向的时间成本和经济成本,而非绝对防御。AOSP 中 DexClassLoader 的实现路径为 /libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java。
Q2: 分段解密与整体解密相比有什么优势?
A: 整体解密会在某个时刻让所有明文 DEX 同时存在于内存中,攻击者只需抓住一个时间点 dump 即可获取全部代码。分段解密只在方法执行前解密目标区域、执行后立即重新加密,使得内存中同时只有极少量的明文代码,大幅提升了 dump 的难度。
Q3: 绕过加固的常见思路有哪些?
A: 首先通过壳识别工具(如 ApkScan)确定加固厂商。然后选择策略:在解密完成后 Frida hook dump DEX(监控 DexFile::OpenCommon 或 dex_file.cc 中的相关函数);或者 hook 壳的签名校验、环境检测函数使其永远返回合法值;也可以从 /proc/pid/maps 和 /proc/pid/mem 直接读取进程内存。AOSP 中 DEX 加载的核心在 /art/runtime/dex_file.cc 和 /art/runtime/class_linker.cc。


