什么是正确的反汇编程序?
我是rdis的作者,并且对这个问题进行了一些思考。如果您在此之后还有更多问题,我建议您查看我的博客。
我还会向您推荐 Andrew Ruef 的博客文章Binary Analysis Is not。关键是我们经常试图通过编译器的上下文来理解我们的程序,而不一定只是一个连续的指令。他创造了术语“编译器输出分析”,这或多或少是我们试图在反汇编器中实现的目标。
术语和定义
从您对反汇编常用术语的定义重新开始。我们有数据或状态,它可以由内存、寄存器和所有好东西组成。我们有代码,这是我们应用于我们期望机器执行的数据的标签(我们将回到代码)。我们有一个程序,它是一种编码在数据中的算法,当它被机器解释时,会导致数据以某种方式被操纵。我们有一台机器,它是一种状态到另一种状态的映射。我们有指令,就我们的目的而言,这些指令存在于单个时间点,由特定的数据组成,这些数据控制我们的机器操作数据的方式。
很多时候,我们认为我们的目标是将代码(我们期望机器执行的数据)转换为可读的反汇编。我相信我们这样做是因为我们将程序分析划分为控制流分析(代码)和数据流分析(数据)。在程序分析中,我们的代码是无状态的,而我们的数据是有状态的。实际上,我们的代码只是数据,它都有状态。
程序恢复
相反,我们的目标应该是通过机器的观察或预测来恢复程序。换句话说,我们对将数据转换为可读的反汇编不感兴趣,而是对发现将由我们的机器解释的指令感兴趣。
此外,我们的程序表示应该与数据的无状态表示分开存储,无状态表示通常是我们的可执行文件(ELF/PE/MACH-O/等)给我们的初始内存布局。实际上,它应该存储在有向图中。当我看到带有多个标记为指令的内存的线性表示时,我会关闭。你还不知道!
我相信拆卸的下一步涉及通过允许拆卸过程中的状态变化来更好地预测机器的过程。我相信我们将同时进行模拟反汇编和抽象反汇编。有些人或多或少已经这样做了,但我不确定是否有人为了创建可用且易于理解的“程序恢复”而明确这样做。
您可以在此处查看程序的递归反汇编和程序的模拟反汇编之间的区别示例。
什么是正确的反汇编程序?
那么,现在来回答您的问题,“什么是正确的反汇编程序?” 我相信正确的反汇编器是明确定义其程序恢复过程的行为并遵守此定义的反汇编器。一旦我们得到了这样做的反汇编器,更好的反汇编器将是那些定义最能预测它们恢复程序的机器行为的反汇编器。
什么是反汇编器?
我会将反汇编器分解为两部分,首先是一个解码器,它采用十六进制代码并输出一条汇编指令(如果汇编语言具有可变长度指令,则可能带有解码指令的长度)。然后,反汇编算法将使用解码器浏览可执行代码。
在我看来,反汇编程序的总体目标是恢复可以从给定的可执行文件构建的所有可能的运行,并以简洁和人类可读的格式呈现它。
拆卸中的问题
反汇编程序在反汇编二进制程序时会遇到很多问题。最困难的问题之一是处理自修改代码。事实上,直到现在还没有真正好的人类可读的自修改程序表示。因此,所有反汇编程序在面对自修改代码时都会惨败,无法输出清晰易懂的内容。
可以阻止反汇编程序的第二个问题是,二进制程序会时不时地跳到另一个地方来执行某些代码(函数调用、if-then-else、开关等)。而且,如果这些跳转中的大多数是静态的(跳转的地址被静态编码到代码中),那么有一些跳转取决于执行的上下文。我们通常将这些跳转称为动态跳转(与静态跳转相对)。这些动态跳转迫使反汇编程序不仅要跟踪指令的语法,还要跟踪它们的语义,以免在遇到指令时丢失。
最后一个问题是,并非所有的二进制程序都可以假定遵循精确的 ABI(应用程序二进制接口),定义函数调用的精确接口或处理数据结构的方法。事实上,一些二进制程序要么是手工编写的,要么是使用修改过的编译器编写的,这些编译器会试图误导反汇编程序。因此,反汇编程序必须通过其语义而不是仅通过其语法来识别函数调用。
反汇编器的正确性
如前所述,反汇编程序的最终目标是从二进制程序中重建所有可能的执行痕迹。当然,大多数时候这是极其困难的,因此我们可以定义三种类型的反汇编器:
- 精确反汇编器:假设它应该发出可以在二进制程序上运行的所有正确跟踪,并且仅发出这些跟踪。
- Over-approximated disassembler:这个的输出应该包括所有可能的跟踪,可能还有一些额外的。
- Under-approximated disassembler : 这个的输出应该包括在可能的跟踪中,但不提供任何不可行的。
现有技术及其分类
目前,两种最流行的技术是线性扫描和递归遍历(有关更多详细信息,请参见此处)。
两者都被众多逆向工程师广泛使用。但是,实际上,这些技术都不是精确的,也不是过度近似的,也不是近似不足的。他们都输出了一些我们之前看到的东西(有时他们会发明一条永远无法到达的路径,有时他们会忘记另一条)。
存在更关心正确性的更先进的技术(例如Jakstab、McVeto、自修改代码的 McVeto,...),但对精确恢复的追求肯定是遥不可及的。
因此,在欠逼近和过逼近之间进行选择取决于反汇编器输出的用途。