16、Android加固技术概览:DEX加固、SO加固、资源加固、VMP与Ollvm

各位同学,今天我们来聊聊Android加固。说实话,这行干久了你会发现,加固和脱壳就像猫鼠游戏,永远在互相追赶。我最早接触加固是在2016年,那时候一个简单的DEX整体加密就能拦住大部分逆向工具。现在?嗯,你想想看,连VMP和Ollvm都开始往移动端跑了。

这一章我不会讲得太深,主要是带大家建立一个全局认知。毕竟后面每一类加固我们都会单独开章节细讲。今天的目标很简单:搞懂市面上常见的加固手段,知道它们各自防什么、弱点在哪。

Android 加固技术全景 Android 加固 DEX 加固 SO 加固 资源加固 VMP (虚拟机保护) Ollvm (控制流平坦化) DEX加固 → 整体加密 / 函数抽取 / 动态加载 SO加固 → 加壳 / 反调试 / 混淆 资源加固 → 图片加密 / XML 对抗 / 资源名称混淆

一、DEX加固:最基础的防线

DEX加固是入门最早、应用最广的一类。说白了,就是把你的classes.dex藏起来。我见过很多新手以为DEX加固就是整体加密,其实远不止这些。

核心思路: 不让逆向工具直接拿到完整的DEX文件。要么加密,要么拆分,要么运行时动态组装。

常见的DEX加固手段有这几种:

  • 整体加密:把整个DEX文件用AES或RC4加密,运行时由壳代码解密加载。优点是实现简单,缺点是内存中会暴露完整的DEX。
  • 函数抽取:只加密每个方法的CodeItem,执行时才解密。我曾在项目中遇到过某加固厂商的抽取方案,结果发现它解密后没有及时清理内存,被我在/proc/self/maps里抓到了明文。
  • 动态加载:把DEX拆成多个小dex,按需加载。这种方案对启动速度影响较大,但对抗静态分析效果不错。
我的经验: 对付DEX整体加密,最直接的办法就是内存dump。找到壳调用DexClassLoader或InMemoryDexClassLoader的地方,在解密完成后把内存中的DEX镜像dump出来。我曾经用frida写了个脚本,三分钟就扒了一个整体加密的壳。

二、SO加固:Native层的攻防

SO加固比DEX加固要复杂一些。为什么?因为Native层有更多的系统调用和内存操作空间。我个人习惯把SO加固分成两类:加壳和混淆。

类型 原理 常见对抗手段
SO加壳 加密.so文件的代码段,运行时由stub解密 内存dump、断点绕过、手动修复ELF
SO混淆 对Native代码进行指令替换、控制流混淆 符号执行、动态跟踪、反混淆脚本
反调试 检测ptrace、/proc/self/status、时间差等 patch掉检测点、frida绕过、内核模块

我记得有一次分析某直播App的SO加固,它用了UPX壳的变种。UPX本身是开源的,但厂商改了魔数和解密入口。我当时花了半天时间定位到解密函数,然后手动dump出了原始SO。嗯,这种经验多了之后,你会发现大部分SO加壳都有规律可循。

注意: 现在的SO加固越来越喜欢结合反调试。我曾经遇到一个壳,它在JNI_OnLoad里做了三次ptrace检测,还在子线程里轮询/proc/self/status。如果你直接挂gdb或lldb,进程会立刻退出。这时候我建议先用frida的Stalker做动态跟踪,或者patch掉检测点。

三、资源加固:别小看图片和XML

很多人觉得资源加固没什么技术含量,其实不然。你想想看,很多App的核心逻辑虽然写在代码里,但关键的配置、算法参数、甚至一些敏感字符串都藏在资源文件里。

常见的资源加固手段:

  • 图片加密:把PNG或JPG文件头抹掉,或者整体AES加密,运行时解密到内存。
  • XML对抗:修改AndroidManifest.xml或布局文件的二进制格式,让aapt和AXMLPrinter2解析失败。
  • 资源名称混淆:把res/values/strings.xml里的key改成无意义字符串,增加静态分析的阅读成本。

我在项目中遇到过最狠的资源加固,是把所有图片都转成了自定义格式,然后通过Native代码解析。当时我为了提取一张启动图,不得不逆向它的图片解码算法。说实话,这种加固对普通逆向者来说挺恶心的,但如果你有耐心,还是能通过hook BitmapFactory或skia库来抓到原始数据。

四、VMP:虚拟机保护

VMP(Virtual Machine Protection)是加固领域的大杀器。它的思路是把Dalvik字节码或Native指令翻译成自定义的虚拟机指令,然后由解释器执行。这样一来,逆向工具看到的只是一堆虚拟机字节码,完全看不懂原始逻辑。

VMP的典型实现流程:

  1. 提取原始方法的指令序列
  2. 将每条指令映射到自定义的opcode
  3. 生成虚拟机字节码,并存储在so或dex中
  4. 运行时由解释器逐条执行这些opcode
关键点: VMP的强度取决于虚拟机的指令集复杂度和解释器的混淆程度。有些VMP实现得比较粗糙,opcode只有几十条,很容易通过统计规律还原。而商业级的VMP(比如某数字公司的方案)会有上千条opcode,还加入了花指令和自修改代码。

我曾经分析过一个使用了VMP的金融App。它的核心加密算法被VMP保护了,我尝试用unicorn模拟执行,但发现解释器里有很多反模拟器检测。最后我用了frida的Stalker配合trace记录,硬是把opcode的执行序列录了下来,然后手动还原了算法逻辑。整个过程花了两周,但收获很大。

五、Ollvm:控制流平坦化

Ollvm(Obfuscator-LLVM)是LLVM的一个分支,专门用来做代码混淆。它最出名的是控制流平坦化(Control Flow Flattening)和指令替换。你想想看,原本一个简单的if-else,经过Ollvm处理后,会变成一堆switch-case和状态变量,阅读起来非常痛苦。

Ollvm的常见混淆方式:

  • 控制流平坦化:把函数的所有基本块放到一个大的switch结构中,通过一个状态变量控制执行流。
  • 指令替换:把简单的运算(如加法、异或)替换成等价的复杂指令序列。
  • 虚假控制流:插入永远不会执行的分支,增加静态分析的复杂度。
我的建议: 对付Ollvm,不要试图手动还原。我推荐使用deflat.py脚本(基于angr的符号执行)来自动化去除控制流平坦化。当然,如果Ollvm开启了反符号执行检测,那就需要手动patch或者用unicorn做动态分析。我曾经在一个so里遇到了Ollvm + VMP的混合加固,那真是双重折磨。

六、各类加固的对比与选择

最后,我给大家整理了一个对比表格。在实际项目中,选择哪种加固方案,取决于你的威胁模型和性能要求。

加固类型 防护强度 性能影响 对抗难度 典型应用场景
DEX整体加密 普通App防静态分析
DEX函数抽取 核心算法保护
SO加壳 Native代码保护
SO混淆 中高 安全SDK、加密库
资源加固 素材、配置文件保护
VMP 极高 金融、支付、核心算法
Ollvm 中高 Native代码混淆、防逆向

好了,这一章的内容就到这里。记住,加固技术没有银弹。每种方案都有它的弱点,而我们逆向工程师要做的,就是找到那个最薄弱的环节。下一章我们会深入DEX加固的细节,到时候我会带大家手撕一个真实的加固壳。

公众号:蓝海资料掘金营,微信deep3321