OLLVM学习以及平坦化源码分析
字数 2253 2025-09-01 11:25:53

OLLVM学习与平坦化源码分析教学文档

1. LLVM基础

1.1 LLVM架构概述

LLVM是一种模块化编译器框架,具有以下特点:

  • 采用前后端分离设计
  • 使用中间表示(IR)作为桥梁
  • 支持多种编程语言和目标平台

与GCC相比的优势:

  • 扩展性强:新语言只需添加前端解释器
  • 平台无关性:后端可生成ELF、SO、EXE等多种格式

1.2 LLVM编译流程

  1. 前端:执行词法分析、语法分析、语义分析,生成中间代码(IR)
  2. 中间层优化:对IR进行各种优化和转换
  3. 后端:将优化后的IR转换为目标平台机器码

1.3 IR中间表示

IR有两种表现形式:

  • .ll文件:人类可读的文本格式
  • .bc文件:机器处理的二进制格式

常见IR语法元素:

  • @:全局符号
  • define:函数体定义
  • declare:外部函数声明
  • %1, %2:SSA寄存器
  • i32:32位整数类型

2. 代码优化与混淆

2.1 常见代码优化技术

  1. 删除无用代码:移除不会被使用的计算结果
  2. 删除公共子表达式:复用重复计算结果
  3. 常量合并:编译时计算常量表达式
  4. 循环展开:减少循环控制开销
  5. 寄存器分配:优化变量存储位置

2.2 代码混淆原理

与优化相反,混淆通过增加冗余代码和复杂控制流来提高逆向难度。

3. LLVM Pass机制

3.1 Pass基本概念

Pass是在IR阶段对代码进行处理的通道,特点:

  • 链式处理:前一个Pass的输出作为下一个Pass的输入
  • 分为两类:
    • Analysis Pass:提供信息为主
    • Transform Pass:优化/转换代码为主

3.2 Pass类型

  1. ModulePass:处理整个模块(源文件),支持跨函数分析
  2. FunctionPass:针对单个函数运行,实现runOnFunction方法
  3. BasicBlockPass:处理基本块(顺序执行的指令组)

3.3 类型查询与转换

LLVM提供的模板函数:

  • isa<T>(x):检查x是否为T类型,返回bool
  • dyn_cast<T>(x):动态转换为T类型指针(失败返回null)
  • cast<T>(x):断言转换(失败则报错)

4. OLLVM平坦化源码分析

4.1 平坦化混淆原理

将程序的控制流转换为基于switch-case的状态机结构,使原始控制流难以识别。

4.2 关键实现步骤

  1. 初始化阶段

    • 生成随机密钥用于switch-case值
    • 收集原始基本块到orignBB列表
    • 移除第一个基本块(通常为初始化块)
  2. 创建框架

    • 插入loopEntryloopEnd基本块
    • 创建switchVar变量并用密钥加密
  3. 构建switch分发

    • 创建switch指令并设置默认处理块
    • 删除原终结指令,改为跳转到loopEntry
  4. 重写控制流

    • 为每个基本块分配case值
    • 修改终结指令为更新switchVar并跳转到loopEnd
    • 处理三种情况:
      • 无后继(终止块):不操作
      • 无条件跳转:写入对应case值
      • 条件跳转:为每个分支写入不同case值
  5. 修复栈帧

    • 使用fixStack修复因插入指针而打乱的栈布局

4.3 混淆前后对比

原始控制流

基本块A → 基本块B → 基本块C

平坦化后

loopEntry → switch(switchVar) {
  case 1: 基本块A; 更新switchVar; goto loopEnd;
  case 2: 基本块B; 更新switchVar; goto loopEnd;
  case 3: 基本块C; 更新switchVar; goto loopEnd;
}
loopEnd: goto loopEntry;

5. 反混淆技术

5.1 现有解决方案

  1. d810插件:Hex-Rays的逆向工程插件
  2. angr框架:符号执行分析
  3. unicorn引擎:模拟执行恢复控制流

5.2 反混淆思路

  1. 识别switch-case状态机结构
  2. 重建原始基本块间的跳转关系
  3. 消除中间状态变量和冗余跳转

5.3 增强混淆的思考

在基本块中间插入间接跳转(br寄存器)可以:

  • 干扰angr/unicorn的CFG分析
  • 增加动态跳转的复杂性
  • 需要处理x64/ARM架构差异

6. 实践指南

6.1 编译OLLVM

  1. 获取源码:https://github.com/buffcow/ollvm-project/tree/14.x
  2. 编译选项:DCMAKE_BUILD_TYPE=Debug(便于调试)
  3. 注意:Release版本无法命中断点

6.2 生成测试IR

clang -emit-llvm -S test.cpp -o output.ll

6.3 自定义Pass开发

  1. 继承适当的Pass基类(FunctionPass等)
  2. 实现核心处理逻辑
  3. 注册Pass到LLVM框架
  4. 编译为动态库(.so)使用

7. 进阶研究方向

  1. 混合混淆:结合平坦化与其他混淆技术
  2. 架构适配:优化ARM平台的混淆效果
  3. 抗反混淆:设计对抗符号执行的技术
  4. 性能平衡:在安全性和性能间取得平衡

8. 参考资料

  1. LLVM官方文档:https://llvm.org/docs/WritingAnLLVMNewPMPass.html
  2. OLLVM源码:https://github.com/buffcow/ollvm-project
  3. 反混淆工具:
    • https://github.com/mFallW1nd/deflat
    • https://eshard.com/posts/D810-a-journey-into-control-flow-unflattening
  4. 相关研究:
    • https://blog.quarkslab.com/deobfuscation-recovering-an-ollvm-protected-program.html
    • https://hex-rays.com/blog/hex-rays-microcode-api-vs-obfuscating-compiler
OLLVM学习与平坦化源码分析教学文档 1. LLVM基础 1.1 LLVM架构概述 LLVM是一种模块化编译器框架,具有以下特点: 采用前后端分离设计 使用中间表示(IR)作为桥梁 支持多种编程语言和目标平台 与GCC相比的优势: 扩展性强:新语言只需添加前端解释器 平台无关性:后端可生成ELF、SO、EXE等多种格式 1.2 LLVM编译流程 前端 :执行词法分析、语法分析、语义分析,生成中间代码(IR) 中间层优化 :对IR进行各种优化和转换 后端 :将优化后的IR转换为目标平台机器码 1.3 IR中间表示 IR有两种表现形式: .ll 文件:人类可读的文本格式 .bc 文件:机器处理的二进制格式 常见IR语法元素: @ :全局符号 define :函数体定义 declare :外部函数声明 %1, %2 :SSA寄存器 i32 :32位整数类型 2. 代码优化与混淆 2.1 常见代码优化技术 删除无用代码 :移除不会被使用的计算结果 删除公共子表达式 :复用重复计算结果 常量合并 :编译时计算常量表达式 循环展开 :减少循环控制开销 寄存器分配 :优化变量存储位置 2.2 代码混淆原理 与优化相反,混淆通过增加冗余代码和复杂控制流来提高逆向难度。 3. LLVM Pass机制 3.1 Pass基本概念 Pass是在IR阶段对代码进行处理的通道,特点: 链式处理:前一个Pass的输出作为下一个Pass的输入 分为两类: Analysis Pass:提供信息为主 Transform Pass:优化/转换代码为主 3.2 Pass类型 ModulePass :处理整个模块(源文件),支持跨函数分析 FunctionPass :针对单个函数运行,实现 runOnFunction 方法 BasicBlockPass :处理基本块(顺序执行的指令组) 3.3 类型查询与转换 LLVM提供的模板函数: isa<T>(x) :检查x是否为T类型,返回bool dyn_cast<T>(x) :动态转换为T类型指针(失败返回null) cast<T>(x) :断言转换(失败则报错) 4. OLLVM平坦化源码分析 4.1 平坦化混淆原理 将程序的控制流转换为基于switch-case的状态机结构,使原始控制流难以识别。 4.2 关键实现步骤 初始化阶段 : 生成随机密钥用于switch-case值 收集原始基本块到 orignBB 列表 移除第一个基本块(通常为初始化块) 创建框架 : 插入 loopEntry 和 loopEnd 基本块 创建 switchVar 变量并用密钥加密 构建switch分发 : 创建switch指令并设置默认处理块 删除原终结指令,改为跳转到 loopEntry 重写控制流 : 为每个基本块分配case值 修改终结指令为更新 switchVar 并跳转到 loopEnd 处理三种情况: 无后继(终止块):不操作 无条件跳转:写入对应case值 条件跳转:为每个分支写入不同case值 修复栈帧 : 使用 fixStack 修复因插入指针而打乱的栈布局 4.3 混淆前后对比 原始控制流 : 平坦化后 : 5. 反混淆技术 5.1 现有解决方案 d810插件 :Hex-Rays的逆向工程插件 angr框架 :符号执行分析 unicorn引擎 :模拟执行恢复控制流 5.2 反混淆思路 识别switch-case状态机结构 重建原始基本块间的跳转关系 消除中间状态变量和冗余跳转 5.3 增强混淆的思考 在基本块中间插入间接跳转(br寄存器)可以: 干扰angr/unicorn的CFG分析 增加动态跳转的复杂性 需要处理x64/ARM架构差异 6. 实践指南 6.1 编译OLLVM 获取源码: https://github.com/buffcow/ollvm-project/tree/14.x 编译选项: DCMAKE_BUILD_TYPE=Debug (便于调试) 注意:Release版本无法命中断点 6.2 生成测试IR 6.3 自定义Pass开发 继承适当的Pass基类(FunctionPass等) 实现核心处理逻辑 注册Pass到LLVM框架 编译为动态库(.so)使用 7. 进阶研究方向 混合混淆 :结合平坦化与其他混淆技术 架构适配 :优化ARM平台的混淆效果 抗反混淆 :设计对抗符号执行的技术 性能平衡 :在安全性和性能间取得平衡 8. 参考资料 LLVM官方文档:https://llvm.org/docs/WritingAnLLVMNewPMPass.html OLLVM源码:https://github.com/buffcow/ollvm-project 反混淆工具: https://github.com/mFallW1nd/deflat https://eshard.com/posts/D810-a-journey-into-control-flow-unflattening 相关研究: https://blog.quarkslab.com/deobfuscation-recovering-an-ollvm-protected-program.html https://hex-rays.com/blog/hex-rays-microcode-api-vs-obfuscating-compiler