菜单

核心反编译过程

相关源文件

本文档描述了 Ghidra 中二进制代码反编译的基本流程。它涵盖了机器代码如何转换为中间表示、进行分析,并最终转换为类似 C 语言的源代码。有关类型传播和数据流等具体方面的信息,请参见函数分析和类型传播以及SSA 形式构建和数据流

反编译流程概述

Ghidra 中的反编译过程遵循一个多阶段管道,逐步将二进制机器代码转换为可读的类似 C 语言的代码。以下是该过程的高级概述

来源:Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh45-55 Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.hh33-86

关键组件

P-code:中间表示

P-code 作为 Ghidra 在机器代码和反编译源代码之间的中间表示。P-code 将不同处理器架构的细节抽象为一组通用操作。

来源:Ghidra/Features/Decompiler/src/decompile/cpp/op.hh Ghidra/Features/Decompiler/src/decompile/cpp/varnode.hh

P-code 的主要特征

  • PcodeOp:表示中间语言中的单个操作
  • Varnode:表示系统中的一个值或变量
  • P-code 操作组织成基本块,并显式表示控制流

Funcdata:核心分析容器

Funcdata 类是用于反编译函数的所有数据结构的主要容器。

来源:Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh42-205

反编译流程详解

1. 操作启动和初始化

反编译过程始于初始化操作,为函数分析做准备。

ActionStart → ActionStartTypes → ActionStackPtrFlow

来源:Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.hh33-86

2. P-code 生成

机器代码指令通过处理器特定模块转换为 P-code 操作。这创建了函数的初始表示。

来源:Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_op.cc

3. SSA 形式构建(Heritage)

Heritage 系统将 P-code 转换为静态单赋值 (SSA) 形式,确保每个变量只被赋值一次。这简化了各种分析。

来源:Ghidra/Features/Decompiler/src/decompile/cpp/heritage.cc

SSA 特性

  • 每个变量只有一个定义
  • Phi 函数插入到控制流合并点
  • 显著简化数据流分析

4. 基于规则的转换

应用多条转换规则来简化和规范代码。这些规则定义为从 Rule 基类派生的类。

转换示例

  • RuleEarlyRemoval:消除未使用的操作
  • RuleCollectTerms:收集并组合算术表达式中的项
  • RulePiece2Zext/RulePiece2Sext:将位操作转换为更简单的操作

来源:Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc24-252 Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh85-252

5. 子变量恢复

SubvariableFlow 系统识别何时将小型逻辑变量存储在较大的容器中并将其提取。

来源:Ghidra/Features/Decompiler/src/decompile/cpp/subflow.cc26-67 Ghidra/Features/Decompiler/src/decompile/cpp/subflow.hh42-52

6. 类型分析和传播

类型信息通过以下方式在函数中推断和传播:

  • 数据流分析
  • 基于大小的推断
  • 操作上下文

来源:75-205 20-71

7. 控制流结构化

非结构化控制流(跳转和分支)被转换为结构化控制流(if-else 语句,循环)。

来源:

8. C 代码生成

最后,PrintC 系统从转换后的 P-code 表示生成人类可读的类似 C 语言的代码。

来源:121-139

核心操作和规则

反编译过程由一组转换代码的操作和规则驱动。以下是关键操作类的表格:

操作目的
ActionStart初始化反编译过程
ActionStartTypes开始类型分析
ActionStackPtrFlow分析函数调用中栈指针的变化
ActionLaneDivide将大寄存器拆分为逻辑通道
ActionSegmentize处理分段内存地址
ActionForceGoto应用来自用户覆盖的 goto 约束
ActionConstbase初始化上下文寄存器
ActionMultiCse消除公共子表达式
ActionShadowVar处理影子变量
ActionConstantPtr将原始地址转换为指针

来源:33-242 24-262

基于规则的转换

规则是较小、有针对性的转换,重复应用直到无法进行进一步更改。

规则转换
RuleEarlyRemoval移除未使用的操作
RuleCollectTerms组合算术项:V * c + V * d => V * (c + d)
RulePiece2Zext将连接转换为零扩展:concat(#0,W) => zext(W)
RulePiece2Sext将连接转换为符号扩展:concat(V s>> #0x1f, V) => sext(V)
RuleBxor2NotEqual将布尔异或转换为不等于:V ^^ W => V != W
RuleOrMask简化与全掩码的 OR 操作:V = W | 0xffff => V = W

来源:Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc24-252 Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh85-252

转换示例

以下是反编译如何将机器代码转换为类似 C 语言代码的示例:

  1. 机器代码:表示指令的字节序列
  2. P-code 生成:转换为 P-code 操作
  3. SSA 形式:变量被唯一地定义
  4. 转换:规则简化代码
  5. 控制流结构化:创建 if 语句和循环
  6. C 代码生成:生成可读的 C 代码

结论

Ghidra 的核心反编译过程是一个多阶段管道,将机器代码转换为可读的类似 C 语言的源代码。该过程包括:

  1. 将机器代码转换为 P-code
  2. 构建 SSA 形式
  3. 应用大量转换和分析
  4. 恢复类型和控制流结构
  5. 生成人类可读的 C 代码

每个阶段都建立在前一个阶段的基础上,逐步从低级二进制表示中重建高级编程结构。