递归遍历反汇编中使用的算法是什么?

逆向工程 反汇编者
2021-06-16 03:24:57

反汇编二进制代码是一个相当困难的话题,但目前在工具中似乎只有两种(幼稚的)算法被广泛使用。

  • 线性扫描:一种基本算法,将所有标记为代码的部分通过依次读取指令进行反汇编。
  • 递归遍历:通过记住何时(何地)取得 acallcall在遇到 a 时返回到最后一个优化线性扫描ret

然而,这些算法的描述相当模糊。在现实生活中的工具中,它们经过了一些改进,以提高拆卸的准确性。

例如,objdump执行线性扫描但将从所有符号开始(而不仅仅是标记为code.

那么,有人可以对递归遍历算法(例如它在 IDAPro 中编码)给出更现实的描述吗?

2个回答

这是IDA 如何做到的一个非常简单的概述:

  1. 将所有已知入口点或用户指定的地址添加到分析队列
  2. 当队列不为空时,弹出下一个地址
  3. 要求处理器模块反汇编指令
  4. 要求处理器模块分析指令
  5. 处理器模块
    在最简单的情况下为所有可能的目标添加代码交叉引用,它是
    条件跳转和调用的下一条指令,它是下一条指令和
    间接跳转的目标- 未知,除非它是可识别的开关模式
  6. 将所有这些交叉引用的尚未分析的目标放入队列中
  7. 转到第 2 步

当然,实际上事情要复杂得多。首先,不是一个队列,而是多个队列。从 SDK 的auto.hpp

//
//      This file contains functions that work with the autoanalyzer
//      queue. The autoanalyzer works when IDA is not busy processing
//      the user keystrokes.
//      The autoanalyzer has several queues. Each queue has its priority.
//      A queue contains addresses or address ranges.
//      The addresses are kept sorted by their values.
//      The analyzer will process all addresses from the first queue, then
//      switch to the second queue and so on.
//      There are no limitations on the size of the queues.
//      The analyzer stops when all queues are empty.
//
//      Also this file contains functions that deal with the IDA status
//      indicator and the autoanalysis indicator.
//      You may use these functions to change the indicator value.
//

// Names and priorities of the analyzer queues

typedef int atype_t;
const atype_t           // priority,  description
  AU_NONE = 00,         //    placeholder, not used
  AU_UNK  = 10,         //  0 convert to unexplored
  AU_CODE = 20,         //  1 convert to instruction
  AU_WEAK = 25,         //  2 convert to instruction (ida decision)
  AU_PROC = 30,         //  3 convert to procedure start
  AU_TAIL = 35,         //  4 add a procedure tail
  AU_TRSP = 38,         //  5 trace stack pointer (not used yet)
  AU_USED = 40,         //  6 reanalyze
  AU_TYPE = 50,         //  7 apply type information
  AU_LIBF = 60,         //  8 apply signature to address
  AU_LBF2 = 70,         //  9 the same, second pass
  AU_LBF3 = 80,         // 10 the same, third pass
  AU_CHLB = 90,         // 11 load signature file (file name is kept separately)
  AU_FINAL=200;         // 12 final pass

我决定发布我的答案不是要推翻 Igor 的答案,而是要对其进行补充。我也不喜欢编辑他的帖子。我对论坛很陌生,不确定其他成员如何看待它。

最近学了一个小理论,分享一下。无论如何,我从IDA Pro Book(第一部分,第 1 节)中了解到的有关 IDA Pro 的内容是它使用Recursive Descent Disassembly基于控制流概念的 。这种方法的关键要素是分析每条指令,以确定它是否从任何其他位置引用。每条指令根据它与 EIP 的交互方式进行分类。主要有以下几种分类:

  1. 顺序流程指令这些是指令执行通到下一条指令遵循如addmovpushpop等。这些指令与拆卸线性扫描
  2. 条件分支指令那些是True/False条件指令je,诸如此类。条件指令仅提供 2 个可能的执行分支。如果条件为False并且不进行跳转,反汇编程序将进行线性扫描,并将跳转目标指令添加到延迟代码列表中,以便稍后使用递归下降算法进行反汇编。
  3. 无条件分支指令如果在运行时计算跳转目标,这些指令可能会导致递归下降反汇编程序出现特定问题。无条件分支不遵循线性流程。如果可能,反汇编器将尝试添加无条件跳转的目标以供进一步分析。如果未确定目标,则不会对特定分支进行反汇编。
  4. 函数调用说明调用指令大多被视为无条件分支指令,期望一旦函数完成,执行将返回到调用之后的指令。调用指令的目标地址地址排队等待延迟反汇编,调用后的指令作为线性扫描处理。然而,并非总是可以确定呼叫的目标(例如call eax)。
  5. 返回指令返回指令不向反汇编程序提供有关下一步执行什么的任何信息。反汇编器不能从栈顶弹出返回地址。所有这些都使反汇编程序停止。此时,反汇编程序转到保存的延迟目标列表以进行下一步。这正是它被称为递归的原因

总而言之,我想引用IDA Pro Book

递归下降算法的主要优势之一是其卓越的区分代码和数据的能力。作为基于控制流的算法,将数据值错误地分解为代码的可能性要小得多。递归下降的主要缺点是无法遵循间接代码路径,例如跳转或调用,它们利用指针表来查找目标地址。但是,通过添加一些启发式方法来识别代码指针,递归下降反汇编器可以提供非常完整的代码覆盖率和出色的代码与数据识别。