使用IDA microcode去除ollvm混淆(下)
字数 1754 2025-08-24 20:49:22
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等高级威胁组织使用的混淆代码。