Ghidra / IDA Pro 对 WebAssembly 二进制的逆向分析:如何识别 Wasm 函数、数据段,并将其转换为伪代码?

各位同学,晚上好!今天咱们来聊聊 WebAssembly 的逆向分析,目标是用 Ghidra 或者 IDA Pro 这两个大杀器,把 Wasm 里的函数、数据段扒个精光,再把它们变成我们看得懂的伪代码。准备好了吗?Let’s rock!

第一部分:Wasm 基础回顾——温故而知新

在深入逆向分析之前,咱们先简单回顾一下 WebAssembly 的基本结构,省得一会儿对着一堆 0 和 1 抓瞎。

  1. 模块(Module): Wasm 的基本单元,相当于一个库或者一个程序。一个模块包含多个部分,比如类型定义、函数、表、内存、全局变量、导出和导入。

  2. 类型定义(Types): 定义了函数的签名,包括参数类型和返回值类型。

  3. 函数(Functions): 实际的代码逻辑。每个函数都有一个类型索引,指向类型定义中的函数签名。

  4. 表(Tables): 存储函数指针,用于实现函数调用。

  5. 内存(Memory): 线性内存,Wasm 程序可以读写这块内存。

  6. 全局变量(Globals): 存储全局数据。

  7. 导出(Exports): 声明哪些函数、内存等可以被外部访问。

  8. 导入(Imports): 声明需要从外部导入的函数、内存等。

记住这些概念,对我们理解 Wasm 的结构至关重要。

第二部分:Ghidra 与 IDA Pro 的配置——工欲善其事

想用 Ghidra 或者 IDA Pro 分析 Wasm,首先得确保它们支持 Wasm。一般来说,最新版本的 Ghidra 和 IDA Pro 都自带了 Wasm 模块。如果不行,可能需要安装对应的插件。

  • Ghidra: Ghidra 默认支持 Wasm,无需额外配置。

  • IDA Pro: IDA Pro 需要安装 Wasm 插件。通常情况下,安装目录下的 plugins 文件夹里会有,或者你可以从网上下载。

第三部分:识别 Wasm 函数——大海捞针,也要捞出来

现在,我们开始实战。首先,我们要识别 Wasm 文件中的函数。

1. Ghidra:

  • 导入 Wasm 文件: 打开 Ghidra,创建一个新的项目,然后把 Wasm 文件拖进去。Ghidra 会自动识别文件类型。
  • 自动分析: Ghidra 会提示你是否要进行自动分析,选择 "Yes"。在分析选项中,确保选择了 "WebAssembly Analyzer"。
  • 函数列表: 分析完成后,在 "Symbol Tree" 窗口中,展开 "Functions" 节点,就能看到 Ghidra 识别出来的所有函数了。

2. IDA Pro:

  • 导入 Wasm 文件: 打开 IDA Pro,选择 "File" -> "Open",打开 Wasm 文件。
  • 自动分析: IDA Pro 也会自动分析文件。
  • 函数窗口: 在 IDA Pro 中,按 "Shift+F2" 可以打开 "Functions window",里面列出了所有识别出来的函数。

代码示例:

咱们用一个简单的 Wasm 例子来演示。假设我们有这样一个 Wasm 文件(add.wasm):

(module
  (func $add (param $p1 i32) (param $p2 i32) (result i32)
    local.get $p1
    local.get $p2
    i32.add)
  (export "add" (func $add)))

这个 Wasm 文件定义了一个 add 函数,接收两个 i32 类型的参数,返回它们的和。

在 Ghidra 或者 IDA Pro 中打开这个文件,你会看到类似这样的函数列表:

工具 函数名 地址
Ghidra FUN_00010000 00010000
IDA Pro sub_10000 00010000

注意:Ghidra 和 IDA Pro 可能会给函数分配默认的名字,比如 FUN_00010000 或者 sub_10000。我们需要根据上下文来重命名函数,让它们更有意义。

第四部分:识别 Wasm 数据段——数据才是王道

Wasm 的数据段存储了程序使用的数据,比如字符串、数组等。识别数据段对于理解程序的行为至关重要。

1. Ghidra:

  • 数据类型管理器: 在 Ghidra 的 "Data Type Manager" 窗口中,可以查看 Ghidra 识别出来的数据类型。
  • 内存映射: 在 "Listing" 窗口中,可以看到 Wasm 的内存映射。数据段通常会被标记为 "data" 或者 "initialized data"。
  • 手动定义数据: 如果 Ghidra 没有正确识别数据类型,可以手动定义数据类型。选中一段内存,右键选择 "Data" -> "Define",然后选择合适的数据类型。

2. IDA Pro:

  • Segments 窗口: 在 IDA Pro 中,按 "Shift+F7" 可以打开 "Segments window",里面列出了 Wasm 文件的所有段。
  • Strings 窗口: 按 "Shift+F12" 可以打开 "Strings window",里面列出了 Wasm 文件中的所有字符串。
  • 手动定义数据: 类似于 Ghidra,IDA Pro 也允许手动定义数据类型。选中一段内存,按 "D" 键,然后选择合适的数据类型。

代码示例:

假设我们的 Wasm 文件包含一个字符串:

(module
  (memory (import "env" "memory") 1)
  (data (i32.const 0) "Hello, WebAssembly!")
  (func $get_string (result i32)
    i32.const 0)
  (export "get_string" (func $get_string)))

这个 Wasm 文件在内存的 0 地址处存储了字符串 "Hello, WebAssembly!"。

在 Ghidra 或者 IDA Pro 中打开这个文件,你会发现:

  • Ghidra 会在内存映射中显示 "Hello, WebAssembly!" 字符串,并将其标记为 "string"。
  • IDA Pro 会在 "Strings window" 中显示 "Hello, WebAssembly!" 字符串。

第五部分:将 Wasm 转换为伪代码——拨开迷雾见真相

识别了函数和数据段之后,下一步就是把 Wasm 代码转换为伪代码,让我们能够更容易地理解程序的逻辑。

1. Ghidra:

  • 反汇编视图: 在 Ghidra 的 "Listing" 窗口中,可以看到 Wasm 代码的反汇编形式。
  • Decompile: Ghidra 提供了强大的反编译功能。选中一个函数,右键选择 "Decompile",Ghidra 会尝试将 Wasm 代码转换为 C 语言风格的伪代码。
  • 代码注释: 在反汇编视图中,可以添加代码注释,帮助理解代码的含义。

2. IDA Pro:

  • 反汇编视图: 在 IDA Pro 中,可以看到 Wasm 代码的反汇编形式。
  • Decompiler: IDA Pro 也提供了反编译功能。选中一个函数,按 "F5" 键,IDA Pro 会尝试将 Wasm 代码转换为 C 语言风格的伪代码。
  • 代码注释: 类似于 Ghidra,IDA Pro 也允许添加代码注释。

代码示例:

回到我们的 add.wasm 例子:

(module
  (func $add (param $p1 i32) (param $p2 i32) (result i32)
    local.get $p1
    local.get $p2
    i32.add)
  (export "add" (func $add)))

在 Ghidra 中,反编译 add 函数,你可能会看到这样的伪代码:

int add(int p1, int p2) {
  return p1 + p2;
}

在 IDA Pro 中,反编译 sub_10000 函数,你可能会看到类似的伪代码。

第六部分:实战案例——从简单到复杂

理论讲了一堆,现在咱们来个稍微复杂点的例子,看看如何应用这些技巧。

假设我们有一个 Wasm 文件(factorial.wasm),实现了计算阶乘的功能:

(module
  (func $factorial (param $n i32) (result i32)
    (local $result i32)
    (local.set $result (i32.const 1))
    (block $B0
      (loop $L1
        (br_if $B0
          (i32.eqz
            (local.get $n)
          )
        )
        (local.set $result
          (i32.mul
            (local.get $result)
            (local.get $n)
          )
        )
        (local.set $n
          (i32.sub
            (local.get $n)
            (i32.const 1)
          )
        )
        (br $L1)
      )
    )
    (local.get $result)
  )
  (export "factorial" (func $factorial)))

这个 Wasm 文件定义了一个 factorial 函数,接收一个 i32 类型的参数,返回它的阶乘。

1. 识别函数:

在 Ghidra 或者 IDA Pro 中打开 factorial.wasm,你会看到一个名为 factorial 或者 sub_10000 的函数。

2. 反编译:

反编译 factorial 函数,你可能会看到这样的伪代码:

int factorial(int n) {
  int result = 1;
  while (n != 0) {
    result = result * n;
    n = n - 1;
  }
  return result;
}

3. 分析伪代码:

从伪代码中,我们可以很容易地看出 factorial 函数的逻辑:它使用一个循环来计算阶乘。

第七部分:高级技巧——更上一层楼

除了基本的操作之外,还有一些高级技巧可以帮助我们更好地进行 Wasm 逆向分析。

  • 动态分析: 使用调试器(比如 lldb 或者 gdb)来动态分析 Wasm 代码。可以设置断点、查看变量的值,等等。
  • 符号执行: 使用符号执行工具(比如 Triton 或者 Manticore)来自动分析 Wasm 代码,寻找漏洞。
  • 模糊测试: 使用模糊测试工具(比如 AFL 或者 Honggfuzz)来生成随机的 Wasm 输入,测试程序的健壮性。

第八部分:常见问题与解决方案——避坑指南

在 Wasm 逆向分析过程中,可能会遇到一些常见问题。

  • 混淆: 一些 Wasm 文件可能会被混淆,增加逆向分析的难度。可以使用反混淆工具来去除混淆。
  • 代码优化: Wasm 代码可能会被优化,导致伪代码难以理解。可以尝试关闭优化选项,或者手动分析汇编代码。
  • 符号信息缺失: Wasm 文件可能没有符号信息,导致 Ghidra 和 IDA Pro 无法识别函数名。可以尝试手动添加符号信息。

第九部分:总结——天下没有难逆的 Wasm

今天我们学习了如何使用 Ghidra 和 IDA Pro 进行 Wasm 逆向分析。我们了解了 Wasm 的基本结构,学习了如何识别函数和数据段,以及如何将 Wasm 代码转换为伪代码。

记住,逆向分析是一个不断学习和实践的过程。多做练习,多查资料,你就能成为 Wasm 逆向分析的高手!

好了,今天的讲座就到这里。希望大家有所收获!下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注