JS `Ghidra` / `IDA Pro` 对 `WebAssembly` 二进制的逆向分析

大家好,我是你们今天的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.htmladd.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

  1. 打开Ghidra: 启动Ghidra,创建一个新的Project。
  2. 导入Wasm文件:add.wasm文件导入到Ghidra中。 Ghidra会自动识别它为WebAssembly文件。
  3. 自动分析: 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

  1. 打开IDA Pro: 启动IDA Pro,创建一个新的数据库。
  2. 导入Wasm文件:add.wasm文件导入到IDA Pro中。 IDA Pro也会自动识别它为WebAssembly文件。
  3. 自动分析: 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游戏,目标是修改游戏分数。

  1. 找到存储分数的内存地址: 用Chrome DevTools或者Wasmtime调试游戏,找到存储分数的内存地址。
  2. 用Ghidra/IDA Pro找到更新分数的函数: 在Ghidra或者IDA Pro中,搜索访问该内存地址的代码。 这很可能是更新分数的函数。
  3. 修改Wasm代码: 修改更新分数的函数,让它直接设置一个很高的分数。 你可以用WAT文本编辑器或者直接修改Wasm二进制文件。
  4. 重新编译: 如果你修改了WAT文本,需要用wat2wasm命令将它重新编译成Wasm。
  5. 替换游戏中的Wasm文件: 将修改后的Wasm文件替换游戏中的原始Wasm文件。
  6. 运行游戏: 运行修改后的游戏,看看分数是不是被修改了。

一些坑和注意事项

  • 混淆: 有些Wasm代码会进行混淆,增加逆向难度。 你可能需要使用一些反混淆工具或者手动分析。
  • 动态生成代码: 有些Wasm代码会动态生成新的代码,这会使静态分析变得更加困难。 你需要使用动态调试来分析这些代码。
  • 符号信息: 很多Wasm文件不包含符号信息,这会使逆向更加困难。 你可以尝试使用一些工具来恢复符号信息,或者手动添加符号信息。
  • Emscripten的Runtime: 如果Wasm是用Emscripten编译的,它会包含Emscripten的Runtime。 你需要理解Emscripten Runtime的结构,才能更好地理解Wasm代码。

总结

WebAssembly逆向分析是一个充满挑战但也很有趣的过程。 通过学习和实践,你可以掌握这项技能,并应用到安全分析、漏洞挖掘等领域。 Ghidra和IDA Pro是你的好帮手,善用它们,你就能扒开Wasm的层层伪装,看到它的真面目。

希望今天的讲座对你有所帮助。 逆向分析需要耐心和实践,多尝试、多思考,你一定能成为Wasm逆向高手!下次有机会再和大家分享更深入的Wasm逆向技巧。

发表回复

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