ADVMP 源码分析与上手

ADVMP 项目介绍

ADVMP(Android Virtual Machine Protection)是一个开源的 Android VMP 实现项目,旨在帮助安全研究人员学习和理解 VMP 的内部原理。与商业 VMP 产品不同,ADVMP 的源码完全公开,代码结构清晰,注释详尽,是学习 VMP 逆向分析的绝佳教材。

为什么要学习 ADVMP

VMP 是 Android 逆向领域中最具挑战性的保护技术之一。直接分析商业 VMP(如阿里聚安全、腾讯御安全)保护的代码,由于缺乏文档且实现复杂,学习曲线非常陡峭。ADVMP 提供了一个"简化版"的 VMP 实现,让你可以:

  1. 从源码层面理解 VM 解释器的工作原理:不是猜测,而是看到每一行实现代码
  2. 实验性地修改和扩展 VM 指令集:添加自定义指令,观察执行效果
  3. 练习 VMP 逆向分析的完整流程:因为你已经知道源码,可以验证自己的分析是否正确
  4. 理解商业 VMP 的设计思路:商业 VMP 在 ADVMP 的基础上增加了更多混淆和反调试手段

源码结构分析

项目目录结构

ADVMP/
├── app/
│   ├── build.gradle
│   └── src/
│       └── main/
│           ├── java/com/advmp/
│           │   ├── MainActivity.java      # 主界面:触发 VMP 执行
│           │   └── NativeLib.java         # JNI 接口声明
│           └── jni/
│               ├── Android.mk             # NDK 构建脚本
│               ├── Application.mk
│               ├── vm_core.c              # VM 解释器核心
│               ├── vm_core.h              # VM 解释器头文件
│               ├── vm_handlers.c          # Handler 实现集合
│               ├── vm_handlers.h
│               ├── vm_compiler.c          # 编译器:原始代码→VM字节码
│               ├── vm_compiler.h
│               ├── vm_disasm.c            # 反汇编器:VM字节码→可读文本
│               ├── vm_disasm.h
│               └── test_functions.c       # 被保护的测试函数
├── README.md
└── docs/
    ├── architecture.md                    # 架构设计文档
    ├── opcode_spec.md                     # 指令集规范
    └── extension_guide.md                 # 扩展开发指南

VM 解释器的核心架构

ADVMP 的 VM 解释器由三个核心组件构成:字节码分发循环Handler 数组虚拟寄存器

虚拟寄存器

ADVMP 定义了一组虚拟寄存器,模拟真实 CPU 的寄存器:

// vm_core.h
#define VM_REG_COUNT 16

typedef struct {
    uint32_t vreg[VM_REG_COUNT];  // 16 个 32 位虚拟寄存器
    uint32_t vpc;                 // 虚拟程序计数器
    uint32_t vsp;                 // 虚拟栈指针
    uint32_t vflags;              // 标志位寄存器(ZF, CF, NF, OF)
    uint32_t vstack[1024];        // 虚拟栈空间(1024 × 32bit)
} vm_context_t;

虚拟寄存器的映射关系:

虚拟寄存器 对应用途 说明
vreg[0] 函数返回值 存储函数的返回值
vreg[1]-vreg[3] 参数传递 对应 ARM 的 R0-R2(前三个参数)
vreg[4]-vreg[11] 通用寄存器 临时变量存储
vreg[12] 虚拟帧指针 类似 ARM 的 FP
vreg[13] 虚拟栈指针 类似 ARM 的 SP
vreg[14] 虚拟链接寄存器 函数返回地址
vreg[15] 虚拟程序计数器 下一条字节码地址

字节码分发循环

分发循环是 VM 的"调度中心",负责读取操作码并分发到对应的 Handler:

// vm_core.c - 简化版分发循环
typedef void (*handler_func_t)(vm_context_t *ctx, uint8_t *bytecode);

// Handler 函数指针表
static handler_func_t handler_table[256] = {
    NULL,               // OP 0x00: NOP (未使用)
    vm_handler_add,     // OP 0x01: ADD
    vm_handler_sub,     // OP 0x02: SUB
    vm_handler_mul,     // OP 0x03: MUL
    vm_handler_xor,     // OP 0x04: XOR
    vm_handler_and,     // OP 0x05: AND
    vm_handler_or,      // OP 0x06: OR
    vm_handler_not,     // OP 0x07: NOT
    vm_handler_mov,     // OP 0x08: MOV
    vm_handler_load,    // OP 0x09: LOAD (从内存)
    vm_handler_store,   // OP 0x0A: STORE (到内存)
    vm_handler_cmp,     // OP 0x0B: CMP
    vm_handler_jmp,     // OP 0x0C: JMP (无条件跳转)
    vm_handler_jz,      // OP 0x0D: JZ  (为零则跳)
    vm_handler_jnz,     // OP 0x0E: JNZ (非零则跳)
    vm_handler_call,    // OP 0x0F: CALL
    vm_handler_ret,     // OP 0x10: RET
    vm_handler_push,    // OP 0x11: PUSH
    vm_handler_pop,     // OP 0x12: POP
    vm_handler_shl,     // OP 0x13: SHL
    vm_handler_shr,     // OP 0x14: SHR
    // ... 更多 Handler
};

// VM 执行入口
int vm_execute(vm_context_t *ctx, uint8_t *bytecode, int bytecode_len) {
    ctx->vpc = 0;  // 初始化虚拟 PC
    
    while (ctx->vpc < bytecode_len) {
        uint8_t opcode = bytecode[ctx->vpc++];
        
        if (handler_table[opcode] != NULL) {
            handler_table[opcode](ctx, bytecode);
        } else {
            // 未知操作码,停止执行
            LOGE("Unknown opcode: 0x%02X at offset %d", opcode, ctx->vpc - 1);
            return -1;
        }
    }
    
    return 0;  // 正常退出
}

Handler 实现示例

以下是几个典型 Handler 的实现:

ADD Handler(算术加法)

// vm_handlers.c
void vm_handler_add(vm_context_t *ctx, uint8_t *bytecode) {
    // 字节码格式:ADD vDst, vSrc1, vSrc2
    // 编码:[OP_ADD] [vDst_idx] [vSrc1_idx] [vSrc2_idx]
    uint8_t dst  = bytecode[ctx->vpc++];
    uint8_t src1 = bytecode[ctx->vpc++];
    uint8_t src2 = bytecode[ctx->vpc++];
    
    ctx->vreg[dst] = ctx->vreg[src1] + ctx->vreg[src2];
    
    // 更新标志位
    if (ctx->vreg[dst] == 0) ctx->vflags |=  FLAG_ZF;
    else                    ctx->vflags &= ~FLAG_ZF;
    
    LOGV("ADD: vReg[%d] = vReg[%d](%u) + vReg[%d](%u) = %u",
         dst, src1, ctx->vreg[src1], src2, ctx->vreg[src2], ctx->vreg[dst]);
}

CMP Handler(比较)

void vm_handler_cmp(vm_context_t *ctx, uint8_t *bytecode) {
    // 字节码格式:CMP vSrc1, vSrc2
    uint8_t src1 = bytecode[ctx->vpc++];
    uint8_t src2 = bytecode[ctx->vpc++];
    
    uint32_t a = ctx->vreg[src1];
    uint32_t b = ctx->vreg[src2];
    
    // 清除旧的标志位
    ctx->vflags = 0;
    
    // 设置标志位
    if (a == b)     ctx->vflags |= FLAG_ZF;  // Zero Flag
    if (a < b)      ctx->vflags |= FLAG_CF;  // Carry Flag (无符号小于)
    if ((int32_t)a < (int32_t)b) ctx->vflags |= FLAG_NF;  // Negative Flag (有符号小于)
    
    LOGV("CMP: vReg[%d](%u) vs vReg[%d](%u), Flags=0x%X",
         src1, a, src2, b, ctx->vflags);
}

JZ Handler(条件跳转)

void vm_handler_jz(vm_context_t *ctx, uint8_t *bytecode) {
    // 字节码格式:JZ offset32
    uint32_t offset = *(uint32_t*)(bytecode + ctx->vpc);
    ctx->vpc += 4;
    
    if (ctx->vflags & FLAG_ZF) {
        ctx->vpc = offset;  // 跳转到指定偏移
        LOGV("JZ: Zero flag set, jump to offset %u", offset);
    } else {
        LOGV("JZ: Zero flag not set, continue");
    }
}

CALL Handler(函数调用)

void vm_handler_call(vm_context_t *ctx, uint8_t *bytecode) {
    // 字节码格式:CALL target_offset32
    uint32_t target = *(uint32_t*)(bytecode + ctx->vpc);
    ctx->vpc += 4;
    
    // 将返回地址压入虚拟栈
    ctx->vstack[ctx->vsp++] = ctx->vpc;
    
    // 跳转到目标地址
    ctx->vpc = target;
    
    LOGV("CALL: target=0x%X, return_addr=%u", target, ctx->vpc);
}

RET Handler(函数返回)

void vm_handler_ret(vm_context_t *ctx, uint8_t *bytecode) {
    // 从虚拟栈弹出返回地址
    ctx->vpc = ctx->vstack[--ctx->vsp];
    
    LOGV("RET: return to offset %u", ctx->vpc);
}

VM 编译器

ADVMP 包含一个简单的编译器,将 C 函数转换为 VM 字节码。编译器的工作原理:

// vm_compiler.c
// 编译器将简单的 C 代码翻译为 VM 字节码

// 示例:编译以下 C 函数
// int add_and_xor(int a, int b, int c) {
//     int t = a + b;
//     int r = t ^ c;
//     return r;
// }

// 编译结果(字节码序列):
// OP_MOV  vReg[4], vReg[1]    ; t = a (参数1 → 临时变量)
// OP_ADD  vReg[4], vReg[4], vReg[2]  ; t = a + b
// OP_MOV  vReg[5], vReg[4]    ; r = t
// OP_XOR  vReg[5], vReg[5], vReg[3]  ; r = r ^ c
// OP_MOV  vReg[0], vReg[5]    ; return r (结果 → 返回值寄存器)
// OP_RET                      ; 返回

编译器的字节码格式

┌─────────────┬───────────────┬───────────────┬───────────────┐
│   Opcode    │  Operand 1    │  Operand 2    │  Operand 3    │
│   (1 byte)  │  (1 byte)     │  (1 byte)     │  (1/4 bytes)  │
└─────────────┴───────────────┴───────────────┴───────────────┘

操作数类型:
- 1 byte: 寄存器编号 (vReg[0] ~ vReg[15])
- 4 bytes: 32 位立即数或地址偏移

编译和运行 ADVMP

环境要求

组件 版本要求
Android NDK r21+
Android SDK API 21+
CMake 3.10+
Android Studio 4.0+

编译步骤

# 1. 克隆项目
git clone https://github.com/example/ADVMP.git
cd ADVMP

# 2. 使用 Android Studio 打开项目
# File → Open → 选择 ADVMP 目录

# 3. 编译
# Build → Make Project
# 或命令行编译:
cd app
../gradlew assembleDebug

运行测试

# 安装 APK
adb install -r app/build/outputs/apk/debug/app-debug.apk

# 运行测试
adb shell am start -n com.advmp/.MainActivity

# 查看日志
adb logcat | grep ADVMP

日志输出示例:

[*] ADVMP VM initialized
[*] Compiling test function: add_and_xor
[*] Bytecode generated: 21 bytes
[*] Executing VM...
[VM] MOV: vReg[4] = vReg[1]  (5)
[VM] ADD: vReg[4] = vReg[4](5) + vReg[2](3) = 8
[VM] MOV: vReg[5] = vReg[4]  (8)
[VM] XOR: vReg[5] = vReg[5](8) ^ vReg[3](6) = 14
[VM] MOV: vReg[0] = vReg[5]  (14)
[VM] RET
[*] VM execution completed, return value: 14
[*] Expected result: 14 ✓

编写自定义 Handler 扩展 VM 指令集

理解 ADVMP 最好的方式是扩展它。下面演示如何添加一个新的 MUL(乘法)Handler:

步骤一:定义操作码

vm_core.h 中添加新的操作码定义:

// 在现有操作码之后添加
#define OP_MUL   0x15
#define OP_DIV   0x16
#define OP_MOD   0x17

步骤二:实现 Handler

vm_handlers.c 中实现 MUL Handler:

void vm_handler_mul(vm_context_t *ctx, uint8_t *bytecode) {
    // 字节码格式:MUL vDst, vSrc1, vSrc2
    uint8_t dst  = bytecode[ctx->vpc++];
    uint8_t src1 = bytecode[ctx->vpc++];
    uint8_t src2 = bytecode[ctx->vpc++];
    
    ctx->vreg[dst] = ctx->vreg[src1] * ctx->vreg[src2];
    
    // 更新标志位
    if (ctx->vreg[dst] == 0) ctx->vflags |=  FLAG_ZF;
    else                    ctx->vflags &= ~FLAG_ZF;
    
    LOGV("MUL: vReg[%d] = vReg[%d](%u) * vReg[%d](%u) = %u",
         dst, src1, ctx->vreg[src1], src2, ctx->vreg[src2], ctx->vreg[dst]);
}

步骤三:注册到 Handler 表

vm_core.chandler_table 中注册:

static handler_func_t handler_table[256] = {
    // ... 现有 Handler
    vm_handler_shl,     // OP 0x13: SHL
    vm_handler_shr,     // OP 0x14: SHR
    vm_handler_mul,     // OP 0x15: MUL  ← 新增
    vm_handler_div,     // OP 0x16: DIV  ← 新增
    vm_handler_mod,     // OP 0x17: MOD  ← 新增
};

步骤四:在编译器中支持新指令

vm_compiler.c 中添加对 MUL 指令的编译支持:

// 编译乘法表达式
case NODE_MUL:
    emit_opcode(bytecode, OP_MUL);
    emit_operand(bytecode, node->result_reg);
    emit_operand(bytecode, node->left->reg);
    emit_operand(bytecode, node->right->reg);
    break;

步骤五:测试新指令

// test_functions.c
// 添加测试函数
void test_mul() {
    vm_context_t ctx = {0};
    uint8_t bytecode[] = {
        OP_MOV,  4, 1,       // vReg[4] = vReg[1]
        OP_MOV,  5, 2,       // vReg[5] = vReg[2]
        OP_MUL,  6, 4, 5,    // vReg[6] = vReg[4] * vReg[5]
        OP_MOV,  0, 6,       // vReg[0] = vReg[6]
        OP_RET               // return
    };
    
    ctx.vreg[1] = 7;  // 参数 a = 7
    ctx.vreg[2] = 8;  // 参数 b = 8
    
    vm_execute(&ctx, bytecode, sizeof(bytecode));
    
    printf("MUL result: %d (expected: 56)\n", ctx.vreg[0]);
    assert(ctx.vreg[0] == 56);
}

ADVMP 与商业 VMP 的差距分析

虽然 ADVMP 是学习 VMP 的好工具,但需要理解它与商业 VMP 之间的差距:

特性 ADVMP 商业 VMP
Handler 表结构 明文的函数指针表 间接跳转表,可能加密
操作码映射 固定映射(0x01=ADD) 每次编译随机化
分发器实现 简单 switch-case 间接跳转 + 反调试
字节码编码 线性编码,操作数紧随操作码 可能分散存储、加密
指令集复杂度 20+ 条基本指令 100+ 条指令,含特殊指令
反调试 多层反调试保护
性能优化 字节码缓存、JIT 等
多架构支持 仅 ARM32 ARM32/ARM64/x86/x64

商业 VMP 的额外保护手段

  1. 操作码随机化:每次编译生成的字节码,同一个 ADD 操作可能使用不同的操作码
  2. Handler 内联混淆:Handler 内部的代码也经过 OLLVM 混淆
  3. 字节码加密:字节码在存储时是加密的,运行时由壳解密
  4. VM 指纹检测:检测是否在虚拟机/模拟器中运行
  5. 完整性校验:运行时校验 VM 解释器代码的完整性

使用 ADVMP 学习 VMP 逆向的方法论

方法一:已知答案练习

  1. 在 ADVMP 中选择一个测试函数
  2. 查看其 C 源码和编译后的字节码
  3. 使用 IDA 打开编译好的 SO 文件
  4. 假装不知道源码,仅通过 IDA 逆向分析
  5. 将分析结果与源码对照,检验正确性

方法二:逐步增加复杂度

  1. 先分析最简单的函数(只有 MOV 和 RET)
  2. 逐步增加算术运算(ADD、SUB、XOR)
  3. 加入循环和条件跳转(CMP、JZ、JNZ)
  4. 加入函数调用(CALL、RET)
  5. 加入内存访问(LOAD、STORE)
  6. 最后分析完整的加密算法实现

方法三:修改后分析

  1. 修改 ADVMP 的 Handler 实现(如改变操作码顺序)
  2. 添加新的 Handler
  3. 使用 IDA 分析修改后的版本
  4. 体验不同的 VM 实现对分析难度的影响

方法四:构建自动化工具

  1. 编写 IDA 脚本自动识别 Handler
  2. 编写字节码反编译工具
  3. 构建 Handler 映射表
  4. 与 Hyperpwn 等专业工具对比

总结

ADVMP 是学习 VMP 逆向分析的绝佳起点。通过阅读源码理解 VM 解释器的架构,通过扩展 Handler 深入理解 VM 的设计,通过"先理解再分析"的练习方法巩固逆向技能。虽然 ADVMP 相比商业 VMP 简化了很多,但其核心原理是相通的。掌握了 ADVMP 的分析方法后,面对商业 VMP 保护的代码,你将具备坚实的基础和系统的方法论。