大家好,我是你们今天的WebAssembly逆向分析向导,咱们今天一起聊聊怎么用Ghidra和IDA Pro这两个神器来扒WebAssembly二进制文件的皮。这玩意儿听起来玄乎,其实没那么可怕,咱们慢慢来,保证你听完能上手。
WebAssembly:这货是干啥的?
首先,得明白WebAssembly(简称Wasm)是啥。简单说,它是一种为Web设计的二进制指令集格式。你可以把它想象成一种中间语言,浏览器可以高效地执行它。它的优点在于:
- 速度快: 编译后的Wasm代码通常比JavaScript运行得更快,接近原生速度。
- 安全: Wasm在一个沙盒环境中运行,具有一定的安全性。
- 可移植性: 理论上,只要有Wasm虚拟机,就能运行。
现在很多Web应用,尤其是游戏、音视频处理等等,都开始用Wasm了。这也意味着,逆向分析Wasm二进制文件变得越来越重要。
准备工作:工具箱
咱们要用到的工具:
- Ghidra: 免费开源的逆向工程工具,来自NSA(美国国家安全局)。功能强大,支持Wasm。
- IDA Pro: 商业逆向工程工具,功能更强大,插件生态也更好。
- WebAssembly Binary Toolkit (WABT): 一组用于处理WebAssembly二进制文件的工具,比如
wasm2wat
(将Wasm转换为WAT文本格式)和wat2wasm
(将WAT转换为Wasm)。
安装好这些家伙,咱们就可以开始了。
步骤一:拿到Wasm文件
首先,你得有个Wasm文件才能逆向。你可以从Web应用里扒下来,或者自己写一个简单的Wasm程序。
例如,用C/C++写一个简单的函数:
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int main() {
int x = 10;
int y = 20;
int sum = add(x, y);
printf("Sum: %dn", sum);
return 0;
}
然后,用Emscripten编译成Wasm:
emcc add.c -o add.html -s WASM=1
这会生成一个add.wasm
文件,以及一个add.html
和add.js
文件。咱们主要关注add.wasm
。
步骤二:转换成可读的文本格式(WAT)
Wasm是二进制的,直接看很痛苦。所以,先用WABT的wasm2wat
命令把它转换成可读的文本格式,也就是WAT(WebAssembly Text Format)。
wasm2wat add.wasm -o add.wat
打开add.wat
,你会看到类似这样的内容:
(module
(type (;0;) (func (param i32 i32) (result i32)))
(type (;1;) (func (result i32)))
(type (;2;) (func (param i32)))
(type (;3;) (func))
(import "env" "memory" (memory (;0;) 256))
(import "env" "table" (table (;0;) 0 funcref))
(import "env" "tableBase" (global (;0;) i32))
(import "env" "abort" (func (;0;) (type 2) (param i32)))
(import "env" "___syscall146" (func (;1;) (type 2) (param i32)))
(import "env" "___syscall54" (func (;2;) (type 2) (param i32)))
(import "env" "___setErrNo" (func (;3;) (type 2) (param i32)))
(import "env" "___syscall6" (func (;4;) (type 2) (param i32)))
(import "env" "___syscall140" (func (;5;) (type 2) (param i32)))
(import "env" "___syscall145" (func (;6;) (type 2) (param i32)))
(import "env" "___syscall148" (func (;7;) (type 2) (param i32)))
(func (;8;) (type 0) (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add)
(func (;9;) (type 1) (result i32)
i32.const 10
i32.const 20
call 8
return)
(func (;10;) (type 3)
i32.const 0
call 9
call 1
drop
return)
(func (;11;) (type 3)
call 10
return)
(export "add" (func 8))
(export "main" (func 11))
(start 11))
虽然看起来还是有点吓人,但至少比二进制好多了。 你可以看到 add
函数的定义:
(func (;8;) (type 0) (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add)
这意味着add
函数接收两个i32
类型的参数,返回一个i32
类型的结果,并且执行的是i32.add
操作(整数加法)。
步骤三:用Ghidra分析Wasm
- 打开Ghidra: 启动Ghidra,创建一个新的Project。
- 导入Wasm文件: 将
add.wasm
文件导入到Ghidra中。 Ghidra会自动识别它为WebAssembly文件。 - 自动分析: Ghidra会提示你进行自动分析。选择默认的分析器,让它跑完。
跑完之后,你会在CodeBrowser中看到反汇编的代码。 Ghidra会自动识别一些函数和导入,并尝试进行类型推断。
- 导航: 你可以通过Symbol Tree(符号树)来导航到不同的函数。 找到
add
函数(在WAT中是func 8,Ghidra中可能会有不同的名字)。 - 反汇编代码: Ghidra会显示反汇编的Wasm代码。 它看起来有点像汇编,但实际上是Wasm指令。
例如,add
函数可能会显示成这样:
**************************************************************
* FUNCTION *
**************************************************************
undefined FUN_00100080()
int r0 <RAX>
int r1 <RSI>
FUN_00100080 XREF[2]: LAB_001000a7, LAB_001000c2
00100080 20 00 local.get $P0
00100082 20 01 local.get $P1
00100084 6a i32.add
00100085 0f return
- 函数调用图: Ghidra可以生成函数调用图,帮助你理解程序的整体结构。 在Graph窗口中,你可以看到
main
函数调用了add
函数。
步骤四:用IDA Pro分析Wasm
- 打开IDA Pro: 启动IDA Pro,创建一个新的数据库。
- 导入Wasm文件: 将
add.wasm
文件导入到IDA Pro中。 IDA Pro也会自动识别它为WebAssembly文件。 - 自动分析: IDA Pro也会提示你进行自动分析。 选择默认的分析器,让它跑完。
IDA Pro的反汇编界面比Ghidra更友好一些,因为它会尝试进行更好的符号解析和类型推断。
- 导航: 你可以通过Functions窗口来导航到不同的函数。
- 反汇编代码: IDA Pro会显示反汇编的Wasm代码,和Ghidra类似,但可能更易读一些。
例如,add
函数可能会显示成这样:
__wasm_function_8 proc near ; CODE XREF: __wasm_function_9+0
; __wasm_function_10+22
local.get arg.0
local.get arg.1
i32.add
return
__wasm_function_8 endp
- 函数调用图: IDA Pro也支持生成函数调用图,而且它的图形界面通常更美观。
- 插件: IDA Pro的插件生态非常丰富,你可以安装一些Wasm相关的插件,来增强它的分析能力。
高级技巧:动态调试
静态分析只能让你了解代码的结构,但有时候你需要动态调试才能真正理解程序的行为。
- Wasmtime/Wasmer: 这两个是流行的Wasm运行时环境。 你可以用它们来运行Wasm文件,并设置断点、单步执行。
例如,用Wasmtime运行add.wasm
:
wasmtime add.wasm
要进行调试,你需要使用支持Wasm调试的工具。 常见的选择包括:
- Chrome DevTools: Chrome DevTools支持Wasm调试。 你可以在Sources面板中打开Wasm文件,设置断点,并查看变量的值。
- VS Code + Wasm Debug扩展: VS Code也有一些Wasm调试扩展,可以让你在VS Code中进行调试。
常见Wasm指令
了解一些常见的Wasm指令,有助于你理解反汇编代码。 这里列出一些常见的指令:
指令 | 含义 |
---|---|
local.get |
获取局部变量的值 |
local.set |
设置局部变量的值 |
i32.add |
32位整数加法 |
i32.sub |
32位整数减法 |
i32.mul |
32位整数乘法 |
i32.div_s |
32位有符号整数除法 |
i32.div_u |
32位无符号整数除法 |
i32.rem_s |
32位有符号整数取余 |
i32.rem_u |
32位无符号整数取余 |
i32.const |
加载一个32位整数常量 |
f32.add |
32位浮点数加法 |
f64.add |
64位浮点数加法 |
call |
调用一个函数 |
return |
从函数返回 |
if |
条件分支 |
loop |
循环 |
br |
无条件跳转 |
br_if |
条件跳转 |
memory.load |
从内存加载数据 |
memory.store |
将数据存储到内存 |
逆向分析实战:破解一个简单的Wasm游戏
假设你拿到一个简单的Wasm游戏,目标是修改游戏分数。
- 找到存储分数的内存地址: 用Chrome DevTools或者Wasmtime调试游戏,找到存储分数的内存地址。
- 用Ghidra/IDA Pro找到更新分数的函数: 在Ghidra或者IDA Pro中,搜索访问该内存地址的代码。 这很可能是更新分数的函数。
- 修改Wasm代码: 修改更新分数的函数,让它直接设置一个很高的分数。 你可以用WAT文本编辑器或者直接修改Wasm二进制文件。
- 重新编译: 如果你修改了WAT文本,需要用
wat2wasm
命令将它重新编译成Wasm。 - 替换游戏中的Wasm文件: 将修改后的Wasm文件替换游戏中的原始Wasm文件。
- 运行游戏: 运行修改后的游戏,看看分数是不是被修改了。
一些坑和注意事项
- 混淆: 有些Wasm代码会进行混淆,增加逆向难度。 你可能需要使用一些反混淆工具或者手动分析。
- 动态生成代码: 有些Wasm代码会动态生成新的代码,这会使静态分析变得更加困难。 你需要使用动态调试来分析这些代码。
- 符号信息: 很多Wasm文件不包含符号信息,这会使逆向更加困难。 你可以尝试使用一些工具来恢复符号信息,或者手动添加符号信息。
- Emscripten的Runtime: 如果Wasm是用Emscripten编译的,它会包含Emscripten的Runtime。 你需要理解Emscripten Runtime的结构,才能更好地理解Wasm代码。
总结
WebAssembly逆向分析是一个充满挑战但也很有趣的过程。 通过学习和实践,你可以掌握这项技能,并应用到安全分析、漏洞挖掘等领域。 Ghidra和IDA Pro是你的好帮手,善用它们,你就能扒开Wasm的层层伪装,看到它的真面目。
希望今天的讲座对你有所帮助。 逆向分析需要耐心和实践,多尝试、多思考,你一定能成为Wasm逆向高手!下次有机会再和大家分享更深入的Wasm逆向技巧。