反汇编简介
概念
在传统的软件开发模型中,程序员使用编译器、汇编器、链接器中的一个或者几个创建可执行程序,为了回溯编程过程,可以使用各种工具来撤销汇编和编译过程,这些工具就叫做反汇编器和反编译器。
反汇编器:以机器语言作为输入,得到汇编语言形式的输出结果
反编译器:以机器语言作为输入,得到高级语言形式的输出结果
反汇编面临的困难
- 编译过程会造成丢失:机器语言中没有变量或函数名,只有通过数据的用途来确定。
- 编译属于多对多操作:源程序可以通过多种不同的方式转换成汇编语言,机器语言也可以通过许多不同的方式转换成源程序。
- 反编译器非常依赖于语言和库
- 想要准确的反编译一个二进制文件,需要近乎完美的反汇编能力
反汇编的基本流程
第一步:
确定进行反汇编的代码区域,若指令和代码混杂在一起,则区分它们就十分重要,以反汇编可执行文件为例,该文件必须符合可执行文件的某种通用格式,如Windows使用的可移植可执行(Portable Executable, PE)格式还有Unix常用的可执行和链接格式(Executable and linking format, ELF),这些格式通常含有一种机制来确定文件中代码和代码入口点的位置。
第二步:
知道指令的起始地址后,下一步就是读取该地址(或文件偏移量)所包含的值,并执行一次表查找,将二进制操作码的值与它的汇编语言助记符对应起来。
第三步:
获取指令并解码任何所需的操作数后,需要对它的汇编语言等价形式进行格式化,并将其在反汇编代码中输出。
第四步:
输出一条指令后,继续反汇编下一条指令,并重复上述过程,知道反汇编完文件中的所有指令。
关于如何确定从何处开始反汇编、如何选择下一条反汇编的指令、如何区分代码和数据,以及如何确定何时完成对最后一条指令的反汇编,有两种主要的反汇编算法。
线性扫描反汇编
该算法采用非常简单的方法来确定需要反汇编的指令的位置:一条指令结束,另一条指令开始的地方。
因此,确定起始位置最难,通常对于特定的文件格式,可以通过文件头部找到标注为代码的节,于是反汇编从一个代码段的第一个字节开始,以线性模式扫描整个代码段,逐条反汇编每条指令,直到完成整个代码段。
执行的基本步骤:
- 位置指针指向代码段的开始处
- 从指针位置尝试匹配指令,并得到指令长度n
- 若第2步成功,则反汇编接下来的n个数据,如果失败则退出
- 位置指针指向上一条指令的末尾
- 判断位置指针是否超出代码段结尾,如果超出则结束,不超出则转至第2步
优点:
能够完全覆盖程序的所有代码段,执行起来简单方便
存在的问题:
这种算法并不会通过识别分支等非线性指令来了解程序的控制流。
对于指令长度相同的RISC,反汇编时不会出现回溯现象,而对于指令长度可变的CISC则可能出现回溯,从而导致效率降低。
该算法最主要的缺点就是,无法区分代码中混入的数据,如果在某几条指令中间插入一些无意义的数据,则会造成反编译器将该数据当作代码来反编译,于是产生错误。
采用该算法的反编译器:
GNU调试器(gdb), 微软公司的WinDbg调试器和objdump实用工具的反汇编引擎。
递归下降反汇编
递归下降算法强调控制流的概念,控制流根据一条指令是否被另一条指令引用来决定是否对其进行反汇编。
递归下降算法的一个主要思路就是模拟CPU的执行过程,根据执行流程逐步对指令进行反汇编,或者说,形如编译技术中的语法树,根据树的路径递归下降式的对指令进行反汇编。
要准确理解递归下降的原理,首先要根据指令对CPU指令指针的影响进行分类:
1、顺序流指令:
顺序流指令将执行权限传递给紧随其后的下一条指令,这种指令的反汇编过程以线性扫描的方式进行。
2、条件分支指令:
条件分支指令提供两条可能的指令路径。如果为真,则执行分支,并且必须修改指令指针,使其指向分支的目标。但是,如果条件为假,则继续以线性模式执行指令,并使用线性扫描方法反汇编下一条指令。因为不可能在静态环境中确定条件测试的结果,递归下降算法会反汇编上述两条路径。同时,它将分支目标指令的地址添加到稍后才进行反汇编的地址列表中,从而推迟分支目标指令的反汇编过程。
3、无条件分支指令:
无条件分支并不遵循线性流模式,因此,它由递归下降算法以不同的方式处理。与顺序流指令一样,执行权只能传递给一条指令,但那条指令不需要紧跟在分支指令后面,也就没有理由反汇编紧跟在无条件分支后面的字节。
递归下降反汇编器尝试确定无条件跳转的目标,并将目标地址添加到要反汇编的地址列表中。然而,对于静态分析条件下无法确定跳转目标的指令,则无法接着进行反编译。
4、函数调用指令:
函数调用指令的运行方式和无条件跳转指令非常相似,唯一的不同在于,一旦函数完成,执行权将返回给紧跟在调用指令后面的指令。在这方面与条件分支类似,因为都生成两条执行路径。调用指令的目标地址被添加到推迟进行反汇编的地址列表中,而紧跟在调用后面的指令则类似于线性扫描的方式进行反汇编。
但是,从被调用函数返回时,如果代码有意篡改函数的返回地址,则有可能在函数返回时,将控制权返回到一个反汇编器无法预知的地址。
5、返回指令:
有时,递归下降算法访问了所有的路径,而且,函数返回指令没有提供接下来将要执行的指令的消息。这时,如果程序确实正在运行,则可以从运行时栈顶获得一个地址,并从这个地址开始恢复指令执行。但是反汇编器并不能访问栈,因此,递归下降反汇编器会转而处理前面搁置一旁的延迟反汇编地址列表。反汇编器从这个列表取出一个地址,并从这个地址开始继续反汇编过程。递归下降反汇编算法正是因此而得名。
分析
递归下降算法的主要优点在于,它具有区分代码和数据的强大能力,作为一种基于控制流的算法,它很少会在反汇编过程中错误的讲数据值作为代码处理。
递归下降算法的主要缺点在于,他无法处理间接代码路径,如利用指针表来查找目标地址的跳转或调用,然而,通过采用一些用于识别指向代码的指针的启发式方法,递归下降反汇编器能够提供所有代码,并清楚的区分代码与数据。
IDA Pro是一种最为典型的递归下降反汇编器。