使用IDA microcode去除ollvm混淆(上)
字数 2002 2025-08-24 20:49:22

IDA Microcode 反混淆技术详解:对抗 OLLVM 混淆

1. 前言

本文详细讲解如何使用 IDA 的 microcode API 来对抗 OLLVM 混淆技术。OLLVM 是一种流行的代码混淆框架,它通过多种技术使代码难以分析和理解。本文将重点介绍如何利用 IDA 7.1+ 提供的 microcode 中间语言和相关 API 来去除 OLLVM 的混淆。

2. IDA 反编译器的两种表示方式

IDA 反编译器中二进制代码有两种表示方式:

  1. microcode

    • 处理器指令被翻译成 microcode
    • 反编译器对其进行优化和转换
    • 使用 HexRaysDeob 插件可以查看和处理 microcode
  2. ctree

    • 由优化的 microcode 构建而成
    • 用 C 语句和表达式表示,类似 AST 的树结构
    • 使用 HexRaysCodeXplorer 插件或 IDApython 中的 vds5.py 可以查看 ctree

反编译流程:二进制指令 → microcode → 优化转换 → ctree → C 代码

3. Microcode 核心数据结构

3.1 mbl_array_t

保存关于反编译代码和基本块数组的信息:

class mbl_array_t {
    int qty;  // 基本块数组的数量
    const mblock_t* get_mblock(int n) const;  // 根据序号返回基本块
    mblock_t* insert_block(int bblk);  // 插入一个基本块
    bool remove_block(mblock_t* blk);  // 删除一个基本块
    bool remove_empty_blocks();  // 删除所有空的基本块
    bool combine_blocks();  // 合并线性的基本块
    int for_all_ops(mop_visitor_t& mv);  // 遍历所有操作数
    int for_all_insns(minsn_visitor_t& mv);  // 遍历所有指令
    int for_all_topinsns(minsn_visitor_t& mv);  // 遍历所有顶层指令
};

3.2 mblock_t

表示一个包含指令列表的基本块:

class mblock_t {
    mblock_t* nextb;  // 双向链表中的下一个基本块
    mblock_t* prevb;  // 双向链表中的上一个基本块
    minsn_t* head;  // 指向第一条指令
    minsn_t* tail;  // 指向最后一条指令
    mbl_array_t* mba;  // 所属的mbl_array_t
    
    int npred() const;  // 前驱者数目
    int nsucc() const;  // 后继者数目
    int pred(int n) const;  // 第n个前驱者
    int succ(int n) const;  // 第n个后继者
    
    minsn_t* insert_into_block(minsn_t* nm, minsn_t* om);  // 插入指令
    minsn_t* remove_from_block(minsn_t* m);  // 删除指令
    int for_all_ops(mop_visitor_t& mv);  // 遍历所有操作数
    int for_all_insns(minsn_visitor_t& mv);  // 遍历所有指令
};

3.3 minsn_t

表示一条指令(可以嵌套):

class minsn_t {
    mcode_t opcode;  // 操作码
    int iprops;  // 指令性质的位组合
    minsn_t* next;  // 双向链表中的下一条指令
    minsn_t* prev;  // 双向链表中的上一条指令
    ea_t ea;  // 指令地址
    
    mop_t l;  // 左操作数
    mop_t r;  // 右操作数
    mop_t d;  // 目标操作数
    
    int for_all_ops(mop_visitor_t& mv);  // 遍历所有操作数
    int for_all_insns(minsn_visitor_t& mv);  // 遍历所有指令
};

3.4 mop_t

表示一个操作数,可以表示不同类型的信息:

class mop_t {
    mopt_t t;  // 操作数类型
    uint8 oprops;  // 操作数属性
    uint16 valnum;  // 操作数的值
    int size;  // 操作数大小
    
    union {
        mreg_t r;  // mop_r 寄存器数值
        mnumber_t* nnn;  // mop_n 立即数的值
        minsn_t* d;  // mop_d 另一条指令
        stkvar_ref_t* s;  // mop_S 堆栈变量
        ea_t g;  // mop_v 全局变量
        int b;  // mop_b 块编号
        // ... 其他类型
    };
};

4. OLLVM 混淆技术分析

4.1 基于模式的混淆(不透明谓词)

样本中常见的混淆模式:

if ((x * (x - 1)) & 1 == 0) {
    // 这部分代码永远不会执行
}
  • x 是偶数或奇数
  • x-1 和 x 的奇偶性相反
  • 偶数 × 奇数 = 偶数
  • 偶数的最低位为 0,因此 &1 结果为 0
  • 这是一种不透明谓词(Opaque Predicate)混淆方式

4.2 控制流平坦化(Control Flow Flattening)

控制流平坦化的原理:

  1. 为每个基本块分配一个数字
  2. 引入块号变量,指示应执行哪个块
  3. 每个块更新块号变量为其后继者
  4. 普通控制流被循环内的 switch 语句代替

典型结构:

int block_var = RANDOM_NUMBER;
while (1) {
    switch (block_var) {
        case BLOCK_A: ...; block_var = NEXT_A; break;
        case BLOCK_B: ...; block_var = NEXT_B; break;
        // ...
    }
}

4.3 异常的栈操作

混淆器使用 __alloca_probe 为函数参数和局部变量保留栈空间,而不是常规的 push 指令,这使得 IDA 难以正确跟踪栈指针。

5. 反混淆器设计与实现

5.1 代码结构

HexRaysDeob 反混淆器的核心组件:

  • AllocaFixer: 处理 __alloca_probe
  • CFFlattenInfo: 控制流平坦化预处理
  • PatternDeobfuscate: 处理基于模式的混淆
  • Unflattener: 处理控制流平坦化
  • DefUtil/HexRaysUtil/TargetUtil: 辅助功能

5.2 对抗控制流平坦化的步骤

第一步:确定平坦块编号到 mblock_t 的映射

  1. 识别 switch(block) 部分的 microcode 表示
  2. 分析块号变量(如 ST14_4.4)的比较和跳转
  3. 建立块编号与 mblock_t 的对应关系

第二步:确定每个平坦块的后继者

  1. 分析每个平坦块的 microcode
  2. 识别块号变量的更新操作
  3. 确定无条件转移(一个后继者)和条件转移(两个后继者)

第三步:直接转移控制流

  1. 修改 goto 指令的目标,直接指向后继块
  2. 删除不必要的块号变量更新
  3. 对于条件转移:
    • 复制指令到合适位置
    • 修改 goto 指令指向不同目标
    • 清理不必要的赋值

5.3 关键实现技术

使用 microcode 而非 ctree 的优势

  1. microcode 更底层,能更精确地匹配模式
  2. 可以利用 HexRays 已有的控制流恢复算法
  3. 在 microcode 级别操作可以保留更多原始信息

成熟度阶段(maturity phases)

HexRays 优化 microcode 时会经历不同成熟阶段(mba_maturity_t):

  • MMAT_GENERATED: 刚生成的 microcode
  • MMAT_LOCOPT: 局部优化后
  • MMAT_CALLS: 函数调用分析后

通过 gen_microcode() API 可以指定需要的成熟度级别。

6. 总结

本文详细介绍了如何利用 IDA 的 microcode API 对抗 OLLVM 混淆,重点讲解了控制流平坦化的去除方法。通过分析 microcode 的核心数据结构、理解混淆技术原理,并按照三个步骤(映射块编号、确定后继者、直接转移控制流)实现反混淆,我们可以有效地恢复原始的控制流结构。

关键要点:

  1. microcode 提供了对反编译中间表示的精细控制
  2. 控制流平坦化的核心是识别和重建原始控制流转移
  3. 在 microcode 级别操作可以充分利用 IDA 的优化能力
  4. 模式匹配和程序分析相结合是反混淆的有效方法
IDA Microcode 反混淆技术详解:对抗 OLLVM 混淆 1. 前言 本文详细讲解如何使用 IDA 的 microcode API 来对抗 OLLVM 混淆技术。OLLVM 是一种流行的代码混淆框架,它通过多种技术使代码难以分析和理解。本文将重点介绍如何利用 IDA 7.1+ 提供的 microcode 中间语言和相关 API 来去除 OLLVM 的混淆。 2. IDA 反编译器的两种表示方式 IDA 反编译器中二进制代码有两种表示方式: microcode : 处理器指令被翻译成 microcode 反编译器对其进行优化和转换 使用 HexRaysDeob 插件可以查看和处理 microcode ctree : 由优化的 microcode 构建而成 用 C 语句和表达式表示,类似 AST 的树结构 使用 HexRaysCodeXplorer 插件或 IDApython 中的 vds5.py 可以查看 ctree 反编译流程:二进制指令 → microcode → 优化转换 → ctree → C 代码 3. Microcode 核心数据结构 3.1 mbl_ array_ t 保存关于反编译代码和基本块数组的信息: 3.2 mblock_ t 表示一个包含指令列表的基本块: 3.3 minsn_ t 表示一条指令(可以嵌套): 3.4 mop_ t 表示一个操作数,可以表示不同类型的信息: 4. OLLVM 混淆技术分析 4.1 基于模式的混淆(不透明谓词) 样本中常见的混淆模式: x 是偶数或奇数 x-1 和 x 的奇偶性相反 偶数 × 奇数 = 偶数 偶数的最低位为 0,因此 &1 结果为 0 这是一种不透明谓词(Opaque Predicate)混淆方式 4.2 控制流平坦化(Control Flow Flattening) 控制流平坦化的原理: 为每个基本块分配一个数字 引入块号变量,指示应执行哪个块 每个块更新块号变量为其后继者 普通控制流被循环内的 switch 语句代替 典型结构: 4.3 异常的栈操作 混淆器使用 __alloca_probe 为函数参数和局部变量保留栈空间,而不是常规的 push 指令,这使得 IDA 难以正确跟踪栈指针。 5. 反混淆器设计与实现 5.1 代码结构 HexRaysDeob 反混淆器的核心组件: AllocaFixer : 处理 __alloca_probe CFFlattenInfo : 控制流平坦化预处理 PatternDeobfuscate : 处理基于模式的混淆 Unflattener : 处理控制流平坦化 DefUtil/HexRaysUtil/TargetUtil : 辅助功能 5.2 对抗控制流平坦化的步骤 第一步:确定平坦块编号到 mblock_ t 的映射 识别 switch(block) 部分的 microcode 表示 分析块号变量(如 ST14_ 4.4)的比较和跳转 建立块编号与 mblock_ t 的对应关系 第二步:确定每个平坦块的后继者 分析每个平坦块的 microcode 识别块号变量的更新操作 确定无条件转移(一个后继者)和条件转移(两个后继者) 第三步:直接转移控制流 修改 goto 指令的目标,直接指向后继块 删除不必要的块号变量更新 对于条件转移: 复制指令到合适位置 修改 goto 指令指向不同目标 清理不必要的赋值 5.3 关键实现技术 使用 microcode 而非 ctree 的优势 microcode 更底层,能更精确地匹配模式 可以利用 HexRays 已有的控制流恢复算法 在 microcode 级别操作可以保留更多原始信息 成熟度阶段(maturity phases) HexRays 优化 microcode 时会经历不同成熟阶段(mba_ maturity_ t): MMAT_GENERATED : 刚生成的 microcode MMAT_LOCOPT : 局部优化后 MMAT_CALLS : 函数调用分析后 通过 gen_microcode() API 可以指定需要的成熟度级别。 6. 总结 本文详细介绍了如何利用 IDA 的 microcode API 对抗 OLLVM 混淆,重点讲解了控制流平坦化的去除方法。通过分析 microcode 的核心数据结构、理解混淆技术原理,并按照三个步骤(映射块编号、确定后继者、直接转移控制流)实现反混淆,我们可以有效地恢复原始的控制流结构。 关键要点: microcode 提供了对反编译中间表示的精细控制 控制流平坦化的核心是识别和重建原始控制流转移 在 microcode 级别操作可以充分利用 IDA 的优化能力 模式匹配和程序分析相结合是反混淆的有效方法