各位同学,晚上好!今天咱们来聊聊 WebAssembly 的逆向分析,目标是用 Ghidra 或者 IDA Pro 这两个大杀器,把 Wasm 里的函数、数据段扒个精光,再把它们变成我们看得懂的伪代码。准备好了吗?Let’s rock!
第一部分:Wasm 基础回顾——温故而知新
在深入逆向分析之前,咱们先简单回顾一下 WebAssembly 的基本结构,省得一会儿对着一堆 0 和 1 抓瞎。
-
模块(Module): Wasm 的基本单元,相当于一个库或者一个程序。一个模块包含多个部分,比如类型定义、函数、表、内存、全局变量、导出和导入。
-
类型定义(Types): 定义了函数的签名,包括参数类型和返回值类型。
-
函数(Functions): 实际的代码逻辑。每个函数都有一个类型索引,指向类型定义中的函数签名。
-
表(Tables): 存储函数指针,用于实现函数调用。
-
内存(Memory): 线性内存,Wasm 程序可以读写这块内存。
-
全局变量(Globals): 存储全局数据。
-
导出(Exports): 声明哪些函数、内存等可以被外部访问。
-
导入(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 逆向分析的高手!
好了,今天的讲座就到这里。希望大家有所收获!下次再见!