type
status
date
slug
summary
tags
category
icon
password
控制流平坦化是什么
控制流平坦化指的是将正常控制流中基本块之间的跳转关系删除,用一个集中 的分发块来调度基本块的执行顺序
如图所示,右边的图就是通过控制流平坦化实现的(因为看起来很平坦,所以被称为控制流平坦化)
控制流平坦化流程结构
大致分为四个结构
- 入口块(进入函数第一个执行的基本块)
- 主分发块与子分发块(:负责跳转到下一个要执 行的原基本块)
- 原基本块(混淆之前的基本块,真正完成程序工 作的基本块)
- 返回块(返回到主分发块)
程序流程混淆效果
- 当我们分析正常的控制流时,我们能很容易的分析出程序的执行顺序,以及每一段代码完成的工作如何执行(一段代码可能由多个互相关联的基本块组成),进而掌握整个程序的逻辑
- 当我们分析平坦化后的控制流时,在不知道基本块执行顺序的情况下,分别对每一个基本块进行分析是很难的,而如果要得知每一个基本块的执行顺序,必须分析分发块的调度逻辑
- 在实际分析中当函数比较复杂的时候,通过手动分析分发块还原原基本块的执行顺序是非常复杂的(比如上面这个图)
程序代码混淆效果
控制流平坦化混淆后的伪代码,while + switch 结构对应平坦化后的控制流代码结构
代码实现
实现步骤
大致分为 5 步
- 保存原基本块
- 创建分发块和返回块
- 实现分发块调度
- 实现调度变量自动调整
- 修复 PHI 指令和逃逸变量
保存原基本块
- 将除入口块以外的以外的基本块保存到 vector 容器中,方便后续处理(原因可以看上一篇的 LLVM 基本块分割里面说的有)
- 如果入口块的终结指令是条件分支指令,则将该指令单独分离出来作为一个基本块,加入到 vector 容器的最前面
代码实现:
创建分发块和返回块
- 除了原基本块之外,我们还要续创建一个分发块来调度基本块的执行顺序。并建立入口块到分发块的绝对跳转。
- 再创建一个返回块,原基本块执行完后都需要跳转到这个返回块,返回块会直接跳转到分发块。
代码实现:
实现分发块调度
- 在入口块中创建并初始化 switch 变量,在调度块中插入 switch 指令实现分发功能
- 将原基本块移动到返回块之前,并分配随机的 case 值,并将其添加到 switch 指令的分支中
代码实现:
实现调度变量自动调整
- 在每个原基本块最后添加修改 switch 变量值的指令,以便返回分发块之后,能够正确执行到下一个基本块
- 删除原基本块末尾的跳转,使其结束执行后跳转到返回块
代码实现:
修复 PHI 指令和逃逸变量
- PHI 指令的值由前驱块决定,平坦化后所有原基本块的前驱块都变成了分发块,因此 PHI 指令发生了损坏
- 逃逸变量指在一个基本块中定义,并且在另一个基本块被引用的变量。在原程序中某些基本块可能引用之前某个基本块中的变量,平坦化后原基本块之间不存在确定的前后关系了(由分发块决定),因此某些变量的引用可能会损坏
- 修复的方法是,将 PHI 指令和逃逸变量都转化为内存存取指令
代码实现:
最终效果
没有加基本块分割实现的控制流平坦化
加了基本块分割后