En

利用符号执行去除控制流平坦化

作者:bird公布时间:2017-01-22阅读次数:171343评论:21

分享

1. 背景


1.1 控制流平坦化


控制流平坦化(control flow flattening)的基本思想主要是通过一个主分发器来控制程序基本块的执行流程,例如下图是正常的执行流程 



经过控制流平坦化后的执行流程就如下图 



这样可以模糊基本块之间的前后关系,增加程序分析的难度,同时这个流程也很像VM的执行流程。更多控制流平坦化的细节可以看Obfuscating C++ programs via control flow flattening,本文以Obfuscator-LLVM的控制流平坦化为例。 

1.2 符号执行


符号执行是一种重要的形式化方法和软件分析技术,通过使用符号执行技术,将程序中变量的值表示为符号值和常量组成的计算表达式,符号是指取值集合的记号,程序计算的输出被表示为输入符号值的函数,其在软件测试和程序验证中发挥着重要作用,并可以应用于程序漏洞的检测。

符号执行的发展是从静态符号执行到动态符号执行到选择性符号执行,动态符号执行会以具体数值作为输入来模拟执行程序,是混合执行(concolic execution)的典型代表,有很高的精确度,目前较新的符号执行工具有Tritonangr,本文是以angr为例。 

2. 分析


首先写一个简单的示例程序


编译


用IDA查看未经过控制流平坦化的控制流程图(CFG) 


添加控制流平坦化



可以看到控制流平坦化后的CFG非常漂亮 



通过分析可以发现原始的执行逻辑只在真实块(自己想的名称...)以及序言和retn块中,其中会产生分支的真实块中主要是通过CMOV指令来控制跳转到哪一个分支,因此只要确定这些块的前后关系就可以恢复出原始的CFG,这个思路主要是参考Deobfuscation: recovering an OLLVM-protected program。 

3. 实现


3.1 获取真实块、序言、retn块和无用块


由于angr的CFG跟IDA的有点不同,因此本文使用BARF来获取,后来问了Fish Wang可以用angr-management下的to_supergraph来获取。主要思路: 

1. 函数的开始地址为序言的地址
2. 序言的后继为主分发器
3. 后继为主分发器的块为预处理器
4. 后继为预处理器的块为真实块
5. 无后继的块为retn块
6. 剩下的为无用块

主要代码:



3.2 确定真实块、序言和retn块的前后关系


这个步骤主要是使用符号执行,为了方便,这里把真实块、序言和retn块统称为真实块,符号执行从每个真实块的起始地址开始,直到执行到下一个真实块。如果遇到分支,就改变判断值执行两次来获取分支的地址,这里用angr的inspect在遇到类型为ITE的IR表达式时,改变临时变量的值来实现,例如下面这个块


使用statement before类型的inspect 



修改临时变量28为false或true再执行就可以得到分支的地址 


如果遇到call指令,使用hook的方式直接返回 


主要代码:


3.3 Patch二进制程序 

首先把无用块都改成nop指令


然后针对没有产生分支的真实块把最后一条指令改成jmp指令跳转到下一真实块


针对产生分支的真实块把CMOV指令改成相应的条件跳转指令跳向符合条件的分支,例如CMOVZ 改成JZ ,再在这条之后添加JMP 指令跳向另一分支


上述就是去除控制流平坦化的总体实现思路。 

4. 演示


去除指定函数的控制流平坦化


用IDA查看恢复后的CFG


可以看到CFG跟原来的大致一样,然后反编译恢复出原始代码 




5. 总结


本文主要针对x86架构下Obfuscator-LLVM的控制流平坦化,但最重要的是去除控制流平坦化过程中的思路,同时当函数比较复杂时可能速度会有点慢。有时间会以此为基础尝试分析伪造控制流、指令替换和VM等软件保护手段,另外符号执行也可以应用于漏洞挖掘领域,例如借助符号执行生成覆盖率更高的Fuzzing测试集以及求解达到漏洞点的路径等。本文相关的脚本deflat请移步到TSRC实验室下载。

由于小弟刚学习符号执行,可能有理解错误的地方,欢迎研究符号执行或者认为有更好思路的师傅们批评指正。最后,感谢angr主要开发者Fish Wang在这期间的耐心帮助。 

评论留言

提交评论 您输入的漏洞名称有误,请重新输入