第六章 反汇编与二进制分析基础
反汇编一般分为静态反汇编和动态反汇编(前者不执行,后者执行)
静态反汇编
加载二进制文件——>找出所有的机器指令——>反汇编指令为可读形式。
一般有线性反汇编和动态反汇编两种方法。
- 线性反汇编:它以二进制的形式遍历所有的代码段,连续解码所有的字节,并将其解析为指令列表。
- 并非所有的字节都是指令
- 内联数据甚至会导致指令流不同步
- 递归反汇编:先从已知的入口点开始进入二进制文件,然后递归地跟随控制流(如跳转和调用)来发现代码,这使得递归反汇编可以在少数情况下解决有数据字节包围的问题。
- 通常很难静态地找出间接跳转或调用的可能位置 ,可能会导致丢失
动态反汇编
动态反汇编工具(也称为执行跟踪程序或指令跟踪程序)能够在程序执行时简单地转储指令(以及可能的内存、寄存器内容)。
动态反汇编的主要缺点是代码覆盖率的问题:动态反汇编工具不能看到所有的指令,而只会看到它所执行的指令。
如何提高代码覆盖率?
- 精心设计的测试套件
- 模糊测试(使用Fuzzer,例如AFL)
- 符号执行(我的理解是已经实现了代码的分块,然后通过符号执行的约束,得到可以进入这部分代码的符号条件,再进行执行,成功后更换分支,确定新的条件再执行,从而提高覆盖率)
构建反汇编的代码和数据
这部分工作就是说:将大型的非结构化的指令进行划分,从而恢复代码和数据。
-
构建代码:
-
分块(划分函数):一般利用函数签名(常见的函数开头的函数序言都是push ebp;mov ebp,esp,并且函数结尾是leave;ret )书里面说目前有很多研究是基于代码结构进行函数检测,另外还可以使用ELF文件的.eh_frame节进行函数检测
-
揭示控制流
有控制流图(IDA pro默认的图表视图)和调用图(IDA pro中的引用函数图)
注意:对于面向对象的代码,想要恢复有层次的类以及虚函数等,目前大部分的反汇编工具无能为力
-
-
构建数据
- 根据库函数的规范自动推断数据类型
- 在动态分析中,可以根据对象在代码中的访问方式来判断内存中对象的数据类型
理论上,无法准确检测复合数据类型。为了便于手动构建数据,IDA Pro允许你自定义复合类型(通过逆向代码来推断),并且将它们分配给数据项。
另外,在反汇编基础上,例如IDA pro还可以进行反编译(但是,注意反编译后的代码可以与原始代码相去甚远)
中间表示(Intermediate Representation,IR)是一种简化分析的好方法,机器码——>IR——>汇编。
基本分析方法
二进制分析的特性
- 程序间和程序内分析
- 前者是会结合程序间关系进行分析
- 后者的每段分析限于函数本身
- 流敏感性:意味着分析将指令的顺序也考虑在内
- 上下文敏感性:在程序间分析中,考虑函数调用的顺序
书上说的这三点,程序间分析其实就一定程度上包含了上下文的敏感性,区别只是上下文敏感的程度(对连续多少个上下文敏感)
控制流分析
主要方法是进行循环检测
书中提出的主要是通过支配树的方式(A支配B==A是到达B的必经点),如果B也能到达A,那么就构成了一个自然循环。
具体的算法是使用DFS,然后在回溯的时候看有没有出现重复节点。
数据流分析
-
到达定义分析
gen:函数生成的数据
kill:函数使用并不再需要了的数据
in:in[B] = U[out(P)] ,P∈pre[B]
out: out[B] = gen[B]U(in[B]-kill[B])
-
def-use 分析
在B中使用的数据y的定义位置。
-
程序切片
分析数据y,只提取处和y有关的内容
另外,编译器的优化会很大的影响反汇编