wasm-reverse
对 $ARGUMENTS 执行 WASM 逆向分析。
工具链:
wasm2wat/wasm-objdump/wasm-decompile(WABT 工具集)ghidra-mcp(反编译,可选)js-reverse-mcp(动态分析 JS↔WASM 交互)
分析流程
Step 1: 提取 WASM 模块
方式 A: 从网页提取
1. list_network_requests → 筛选 .wasm 文件请求
2. 或 search_in_sources("WebAssembly") → 找到 instantiate/compile 调用
3. save_script_source 保存 WASM 文件(如果是通过脚本加载)
4. 或直接 curl 下载 .wasm URL
方式 B: 从 JS bundle 中提取
某些打包工具将 WASM 编码为 Base64 嵌入 JS 中:
1. search_in_sources("data:application/wasm") → 找到嵌入的 WASM
2. 提取 Base64 字符串 → 解码保存为 .wasm
方式 C: 本地文件
直接分析用户提供的 .wasm 文件
Step 2: 静态分析
2.1 基础信息
# 查看 WASM 模块信息
wasm-objdump -h module.wasm # 段信息
wasm-objdump -x module.wasm # 导入/导出详情
wasm-objdump -d module.wasm # 反汇编
# 转为文本格式 (WAT)
wasm2wat module.wasm -o module.wat
# 反编译为伪 C 代码
wasm-decompile module.wasm -o module.dcmp
2.2 关键信息提取
导出函数 (exports):
- 这些是 JS 可以直接调用的函数
- 通常加密函数会在这里暴露
- wasm-objdump -j Export -x module.wasm
导入函数 (imports):
- WASM 需要宿主(JS)提供的函数
- 通常包含内存操作、环境 API
- 需要在 instantiate 时提供
内存 (Memory):
- WASM 的线性内存
- 输入/输出数据通过内存传递
字符串:
- wasm-objdump -j Data -x module.wasm
- 数据段中可能包含密钥、算法常量
2.3 识别加密算法
在 WASM 反编译/反汇编中搜索特征常量:
| 常量 | 算法 |
|---|---|
0x67452301, 0xEFCDAB89 |
MD5 初始值 |
0x6A09E667, 0xBB67AE85 |
SHA-256 初始值 |
0x5A827999, 0x6ED9EBA1 |
SHA-1 K 值 |
0x63, 0x7C, 0x77, 0x7B |
AES S-Box |
0x428A2F98, 0x71374491 |
SHA-256 K 值 |
Step 3: 动态分析(JS ↔ WASM 交互)
用 js-reverse-mcp 追踪 JS 如何调用 WASM:
1. search_in_sources("WebAssembly.instantiate")
→ 找到 WASM 加载和初始化代码
2. 分析 imports 对象:
WebAssembly.instantiate(wasmBytes, {
env: {
memory: new WebAssembly.Memory(...),
__memory_base: 0,
// ... JS 提供给 WASM 的函数
}
})
3. 在 JS 调用 WASM 导出函数处设断点:
set_breakpoint_on_text("instance.exports.encrypt")
→ 观察输入参数如何写入 WASM 内存
→ 观察输出如何从 WASM 内存读取
4. trace_function 追踪导出函数调用:
trace_function(scriptId, lineNumber)
→ 监控每次调用的参数
3.1 内存交互分析
JS 和 WASM 通过线性内存通信:
// 典型模式: JS 写入输入 → 调用 WASM → 读取输出
// 写入字符串到 WASM 内存
var inputPtr = instance.exports.malloc(input.length);
var mem = new Uint8Array(instance.exports.memory.buffer);
for (var i = 0; i < input.length; i++) {
mem[inputPtr + i] = input.charCodeAt(i);
}
// 调用 WASM 加密函数
var outputPtr = instance.exports.encrypt(inputPtr, input.length);
// 读取输出
var output = '';
for (var i = 0; outputPtr + i < mem.length && mem[outputPtr + i] !== 0; i++) {
output += String.fromCharCode(mem[outputPtr + i]);
}
在断点处用 evaluate_script 检查:
// 查看 WASM 内存中的数据
new Uint8Array(instance.exports.memory.buffer, ptr, length)
Step 4: 算法还原或直接调用
方案 A: 还原算法(适用于标准加密)
如果识别出标准算法(MD5/SHA/AES),直接用 Python 标准库实现:
# 如果是 SHA-256
import hashlib
result = hashlib.sha256(data).hexdigest()
方案 B: 直接调用 WASM(推荐,适用于复杂/自定义算法)
Python (wasmer)
from wasmer import engine, Store, Module, Instance, ImportObject, Function, Memory, MemoryType
import ctypes
store = Store()
module = Module(store, open("module.wasm", "rb").read())
# 构建 imports(如果 WASM 需要)
import_object = ImportObject()
# import_object.register("env", {"log": Function(store, log_func)})
instance = Instance(module, import_object)
memory = instance.exports.memory
# 写入输入
input_data = b"data_to_encrypt"
ptr = instance.exports.malloc(len(input_data))
mem_view = memory.uint8_view(offset=ptr)
for i, b in enumerate(input_data):
mem_view[i] = b
# 调用加密
result_ptr = instance.exports.encrypt(ptr, len(input_data))
# 读取输出
output = bytes(memory.uint8_view(offset=result_ptr)[:32])
print(output.hex())
Node.js
const fs = require('fs');
const wasmBuffer = fs.readFileSync('module.wasm');
(async () => {
const module = await WebAssembly.compile(wasmBuffer);
const instance = await WebAssembly.instantiate(module, {
env: {
memory: new WebAssembly.Memory({ initial: 256 }),
// ... 其他 imports
}
});
const { encrypt, memory, malloc } = instance.exports;
const input = new TextEncoder().encode("data_to_encrypt");
const ptr = malloc(input.length);
new Uint8Array(memory.buffer, ptr, input.length).set(input);
const resultPtr = encrypt(ptr, input.length);
const result = new Uint8Array(memory.buffer, resultPtr, 32);
console.log(Buffer.from(result).toString('hex'));
})();
反模式
- 不要忽略 imports — 很多 WASM 模块依赖宿主提供的函数(如内存分配、环境 API),直接调用会失败。
- 不要假设内存布局 — 不同编译器(Emscripten/Rust/Go)的内存管理方式不同,需要分析 JS glue code 理解约定。
- 不要直接修改 WASM 字节码 — 除非非常了解 WASM 二进制格式,否则用 WAT 文本格式修改后再编译回来。
- 优先直接调用而非还原 — WASM 中的自定义算法还原成本高,直接用 Python/Node.js 加载 WASM 模块调用更高效。
完成标准
WASM 分析结果:
- 文件:module.wasm (XX KB)
- 导出函数:encrypt(ptr, len) → ptr
- 算法类型:[标准算法 | 自定义算法]
- 调用方案:[Python 还原 | Python/Node.js 直接调用 WASM]
- 验证:相同输入 → 相同输出
References
| 文件 | 何时读取 |
|---|---|
references/wasm-tools.md |
WABT 工具集安装和详细用法 |
references/wasm-js-bridge.md |
JS ↔ WASM 通信模式和内存管理详解 |