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

IDA Microcode去除OLLVM混淆技术详解

1. 控制流平坦化概述

控制流平坦化(Control Flow Flattening)是一种常见的代码混淆技术,通过将程序的控制流转换为类似状态机的结构,使得逆向分析变得困难。OLLVM(OpenLLVM)是一个开源的LLVM混淆框架,其中就包含了控制流平坦化的实现。

2. 识别被展平的函数

2.1 启发式识别方法

被展平的函数通常具有以下特征:

  1. 使用块号变量与伪随机生成的数字常量进行比较
  2. 大量使用jz和jg指令进行跳转

识别算法步骤:

  1. 遍历函数中的所有microcode
  2. 记录所有将变量与常数比较的jz/jg指令信息
  3. 选择被比较次数最多的变量对应的所有常量
  4. 计算这些常量的比特熵值(1的位数除以总位数)

判断标准:

  • 伪随机生成的常量熵值应接近0.5
  • 如果熵值在0.4-0.6之间,可确定函数已被展平

3. 简化图结构

3.1 去除冗余跳转

被展平的函数常包含:

  • 直接导致其他跳转的跳转指令
  • microcode翻译器插入的冗余goto指令

简化方法:

  1. 第一次遍历所有块,记录每个块的第一条指令是否为goto及其目标地址
  2. 第二次遍历所有块,处理以下情况:
    • 如果块最后是call指令或多后继者,跳过
    • 如果是goto指令,获取目标地址
    • 如果不是goto指令,获取唯一后继者
  3. 使用while循环跟踪跳转链:
    • 遇到循环则标记为不替换
    • 遇到非goto块则退出
  4. 更新跳转目标为最终块,并修改前驱/后继关系

4. 提取块号信息

4.1 块变量识别

平坦化函数通常使用两个变量:

  1. 块更新变量:在基本块中更新的变量
  2. 块比较变量:在switch(block)中用于比较的变量

识别步骤:

  1. 找到switch(block)前的第一个块(有多个前驱者的块之前)
  2. 检查该块中对变量的常量赋值:
    • 如果找到块比较变量,则不存在块更新变量
    • 否则,查找被拷贝到块比较变量的变量作为块更新变量
  3. 从jz指令中提取与块比较变量比较的数字常量

5. 取消控制流平坦化

5.1 处理两种平坦块类型

  1. 无条件分支块

    • 将块更新变量设置为单个固定值
    • 直接更新goto指令指向对应mblock_t
  2. 条件分支块

    • 使用CMOV指令设置块更新变量为两个可能值
    • 更新jz为true执行的块的goto指令
    • 拷贝jz为true执行的块到false执行的块
    • 更新jz为false执行的块的goto指令

5.2 处理多mblock_t情况

由于HexRays在函数调用边界分割基本块,单个平坦块可能对应多个mblock_t。解决方法:

  1. 计算函数的支配树(dominator tree)
  2. 找到将回到switch(block)且由平坦块头支配的块

支配树算法:

  1. 每个基本块用X位bitset表示支配关系
  2. 初始化:每个块支配所有块
  3. 设置第一个块只支配自己
  4. 迭代更新:
    • 更新块的bitset为其前驱bitset的交集
    • 设置块自己支配自己
  5. 重复直到bitset不再变化

6. 查找块更新变量赋值

6.1 简单情况处理

FindNumericDefBackwards函数:

  1. 从块底部开始反向搜索对块更新变量的赋值
  2. 如果是变量赋值,递归搜索
  3. 找到数字常量则返回true
  4. 到达支配块头仍未找到则返回false

6.2 复杂情况处理

当存在指针写入内存时:

  1. 常量先存入寄存器,再保存到栈变量
  2. 中间可能有指针写入内存
  3. 最后从栈变量读取

解决方法:

  1. 当FindNumericDefBackwards返回false且最后是栈读取时
  2. 调用FindForwardStackVarDef函数
  3. 跳转到平坦块开头查找赋给栈变量的常量

注意:这种方法在指针混叠情况下不完全可靠,但在实践中对特定混淆器有效。

7. 工具与实现

现有工具:

  • Rolf Rolles的开源代码
  • idapython实现的pyhexraysdeob
  • 用于处理APT10样本中类似混淆的扩展工具

8. 总结

控制流平坦化是常见的混淆技术,通过:

  1. 识别被展平函数
  2. 简化控制流图
  3. 提取块号信息
  4. 重建原始控制流

虽然存在一些特殊情况需要特殊处理,但基于microcode的分析方法能有效去除这类混淆。该方法已在多个实际样本中验证有效,包括APT10等高级威胁组织使用的混淆代码。

IDA Microcode去除OLLVM混淆技术详解 1. 控制流平坦化概述 控制流平坦化(Control Flow Flattening)是一种常见的代码混淆技术,通过将程序的控制流转换为类似状态机的结构,使得逆向分析变得困难。OLLVM(OpenLLVM)是一个开源的LLVM混淆框架,其中就包含了控制流平坦化的实现。 2. 识别被展平的函数 2.1 启发式识别方法 被展平的函数通常具有以下特征: 使用块号变量与伪随机生成的数字常量进行比较 大量使用jz和jg指令进行跳转 识别算法步骤: 遍历函数中的所有microcode 记录所有将变量与常数比较的jz/jg指令信息 选择被比较次数最多的变量对应的所有常量 计算这些常量的比特熵值(1的位数除以总位数) 判断标准: 伪随机生成的常量熵值应接近0.5 如果熵值在0.4-0.6之间,可确定函数已被展平 3. 简化图结构 3.1 去除冗余跳转 被展平的函数常包含: 直接导致其他跳转的跳转指令 microcode翻译器插入的冗余goto指令 简化方法: 第一次遍历所有块,记录每个块的第一条指令是否为goto及其目标地址 第二次遍历所有块,处理以下情况: 如果块最后是call指令或多后继者,跳过 如果是goto指令,获取目标地址 如果不是goto指令,获取唯一后继者 使用while循环跟踪跳转链: 遇到循环则标记为不替换 遇到非goto块则退出 更新跳转目标为最终块,并修改前驱/后继关系 4. 提取块号信息 4.1 块变量识别 平坦化函数通常使用两个变量: 块更新变量:在基本块中更新的变量 块比较变量:在switch(block)中用于比较的变量 识别步骤: 找到switch(block)前的第一个块(有多个前驱者的块之前) 检查该块中对变量的常量赋值: 如果找到块比较变量,则不存在块更新变量 否则,查找被拷贝到块比较变量的变量作为块更新变量 从jz指令中提取与块比较变量比较的数字常量 5. 取消控制流平坦化 5.1 处理两种平坦块类型 无条件分支块 : 将块更新变量设置为单个固定值 直接更新goto指令指向对应mblock_ t 条件分支块 : 使用CMOV指令设置块更新变量为两个可能值 更新jz为true执行的块的goto指令 拷贝jz为true执行的块到false执行的块 更新jz为false执行的块的goto指令 5.2 处理多mblock_ t情况 由于HexRays在函数调用边界分割基本块,单个平坦块可能对应多个mblock_ t。解决方法: 计算函数的支配树(dominator tree) 找到将回到switch(block)且由平坦块头支配的块 支配树算法: 每个基本块用X位bitset表示支配关系 初始化:每个块支配所有块 设置第一个块只支配自己 迭代更新: 更新块的bitset为其前驱bitset的交集 设置块自己支配自己 重复直到bitset不再变化 6. 查找块更新变量赋值 6.1 简单情况处理 FindNumericDefBackwards函数: 从块底部开始反向搜索对块更新变量的赋值 如果是变量赋值,递归搜索 找到数字常量则返回true 到达支配块头仍未找到则返回false 6.2 复杂情况处理 当存在指针写入内存时: 常量先存入寄存器,再保存到栈变量 中间可能有指针写入内存 最后从栈变量读取 解决方法: 当FindNumericDefBackwards返回false且最后是栈读取时 调用FindForwardStackVarDef函数 跳转到平坦块开头查找赋给栈变量的常量 注意:这种方法在指针混叠情况下不完全可靠,但在实践中对特定混淆器有效。 7. 工具与实现 现有工具: Rolf Rolles的开源代码 idapython实现的pyhexraysdeob 用于处理APT10样本中类似混淆的扩展工具 8. 总结 控制流平坦化是常见的混淆技术,通过: 识别被展平函数 简化控制流图 提取块号信息 重建原始控制流 虽然存在一些特殊情况需要特殊处理,但基于microcode的分析方法能有效去除这类混淆。该方法已在多个实际样本中验证有效,包括APT10等高级威胁组织使用的混淆代码。