11 KiB
11 KiB
name, description, capabilities
| name | description | capabilities | |
|---|---|---|---|
| re-wasm | WASM 逆向:格式解析、wasm2wat、浏览器侧。触发词:WASM、wasm、WebAssembly、wat |
|
WebAssembly 逆向(WABT / wasmtime / Chrome DevTools)
何时使用 / 何时不用
- 用:.wasm 模块还原逻辑(网页前端加密/校验、浏览器插件、游戏、恶意网页/供应链 JS 加载的 wasm 载荷)
- 用:
WebAssembly.instantiate/WebAssembly.Module/.wasmfetch 出现的样本——wasm 与 JS 胶水一体分析 - 不用:非 wasm(native 走 re-binary-core、.NET 走 re-dotnet、Java 走 re-java、纯 JS/脚本走 re-script-deob)
- 不用:wasm 模块本身可读性尚可、只需读逻辑——直接
wasm2wat读 WAT 即可,无需完整流程 - 注意:动态运行默认沙箱(re-analyze/platform-tips 最高原则);wasm 模块相对隔离,但宿主 JS 胶水会调 DOM/网络等敏感 API——JS 侧先还原(re-script-deob)
工具准备
参考 re-analyze/platform-tips——wasm 解析/反编译为静态步骤,免沙箱;动态运行(wasmtime/wasm3/浏览器加载)按最高原则进沙箱(至少断网)。
WABT(wasm2wat / wasm-decompile / wasm-objdump,解析与反编译主力)
- Debian/Ubuntu:
apt install wabt;Fedora:dnf install wabt;Arch:pacman -S wabt - macOS:
brew install wabt;Windows:choco install wabt或 GitHubWebAssembly/wabtrelease 的 bin 目录 - 验证:
wasm2wat --version && wasm-decompile --version && wasm-objdump --version
wasmtime(WASI CLI 运行时,--invoke 直调导出函数)
- 官方安装器(全平台,装到
~/.wasmtime/bin):curl https://wasmtime.dev/install.sh -sSf | bash - Debian/Ubuntu: 无官方包——用官方安装脚本/二进制(install.wasmtime.dev)或源码构建;Fedora: 官方仓库无 CLI 包——用官方安装脚本或第三方仓库(GetPageSpeed/COPR);Arch:
pacman -S wasmtime;macOS:brew install wasmtime;Windows:winget install BytecodeAlliance.Wasmtime - 验证:
wasmtime --version
wasm3(轻量解释器,无 WASI 依赖也可跑,嵌入式/快速验证场景)
- Arch: 官方仓库无 wasm3——AUR(如 wasm3 包)或源码构建;macOS:
brew install wasm3 - Debian/Ubuntu/Fedora 无官方包 → GitHub
wasm3/wasm3release 下载对应平台单文件二进制,或源码构建:git clone --depth 1 https://github.com/wasm3/wasm3 && cmake -B build -S wasm3 && cmake --build build - 验证:
wasm3 --version
Chrome DevTools(浏览器侧动态分析)
- Chrome/Chromium: Debian/Ubuntu
apt install chromium(Chrome 用官方 deb);Fedoradnf install chromium;Archpacman -S chromium;macOSbrew install --cask google-chrome;Windows 官方安装器 - 验证: 启动浏览器
F12打开 DevTools → Sources 面板能展开 .wasm 文件 - 本地起服务供浏览器加载:
python3 -m http.server 8000
binaryen(wasm-opt 优化/混淆还原,wasm-dis 反汇编)
- Debian/Ubuntu:
apt install binaryen;Fedora:dnf install binaryen;Arch:pacman -S binaryen - macOS:
brew install binaryen;Windows:choco install binaryen或 GitHubWebAssembly/binaryenrelease - 验证:
wasm-opt --version
操作步骤
按顺序执行,每步记录证据(路径 + sha256,见 re-triage)。
-
识别 wasm 模块:
file sample.wasm # "WebAssembly (wasm) binary module version 0x1 (MVP)" xxd sample.wasm | head -1 # 魔数 00 61 73 6D(\0asm)+ 版本 01 00 00 00- 魔数
\0asm(00 61 73 6D)+ 版本01 00 00 00= 标准 wasm;不符 → 不是 wasm(可能加密/混淆/损坏,见坑 3) - 常以内嵌形态出现,先还原成 .wasm 文件:
atob(...)的 base64 串、JS 里Uint8Array([0,97,115,109,...])数组字面量 - 从 JS 侧确认:
WebAssembly.instantiate/WebAssembly.Module/.wasmfetch 出现即 wasm 载荷(JS 胶水走 re-script-deob)
- 魔数
-
section 解析(wasm-objdump / wasm2wat):
wasm-objdump -x sample.wasm # 全部 section:Type/Import/Function/Table/Memory/Global/Export/Code/Data wasm-objdump -h sample.wasm # 只列 section 摘要 wasm2wat sample.wasm -o sample.wat # 二进制 → WAT 文本(有扩展指令时加 --enable-all) wasm-objdump -d sample.wasm # 反汇编全部函数体(WAT 风格指令)- 各 section 的逆向意义:Type(函数签名,辅助还原类型)、Import(宿主 JS/WASI 注入的函数——调用边界)、Export(JS 可调的对外函数名)、Data(字符串常量区)、Code(指令体)
- 先读 Import + Export 建立调用面,再进 Code
-
反编译(wasm-decompile):
wasm-decompile sample.wasm -o sample.c # 类 C 伪码(可读性优先)- 与 WAT 对照:wasm-decompile 可读性好但丢部分类型/边界信息;关键函数以
wasm2wat产出的 WAT 为准精读 - 入口定位:
(export "checkPassword" ...)→ JS 侧instance.exports.checkPassword;import 的函数 → 到 JS 胶水找实现(坑 2) - 字符串:
wasm-objdump -s sample.wasmdump 全部 section 原始内容(hex dump,含 Data 段;Data section 亦可从-x输出读);WAT 里找(data (i32.const 偏移) "字符串"),把代码里i32.const 偏移映射回字符串(坑 1) - 函数多/疑似混淆时先用 wasm-decompile 粗读,挑出目标函数再精读
- 与 WAT 对照:wasm-decompile 可读性好但丢部分类型/边界信息;关键函数以
-
浏览器侧(DevTools Sources / JS 调用面):
- 打开样本页面(本地起服务),DevTools → Sources → 左侧展开
.wasm文件:Chrome 自动给出反编译伪代码视图,可下断点、单步,Scope 面板看 wasm 局部变量,Call Stack 同时显示 JS↔wasm 帧 - 调用面:JS 侧
WebAssembly.instantiate(bytes, importObject)的importObject定义了 wasm 的 import 实现;instance.exports.xxx(...)是 wasm 对外函数——在 JS 调用点下断点,单步进入 wasm 帧观察入参/返回值 - 无头环境:Chrome headless + DevTools Protocol(
--headless --remote-debugging-port=9222),或 NodeWebAssembly.instantiate等价复现调用 - 动态改值:DevTools 在 wasm 伪代码视图里修改参数重放调用,黑盒对比输出
- 打开样本页面(本地起服务),DevTools → Sources → 左侧展开
-
动态(wasmtime + 插桩/黑盒差分):
wasmtime run sample.wasm # WASI 程序(需 _start 导出) wasmtime run --invoke checkPassword sample.wasm '"test"' # 直调导出函数(参数按 JSON 解析) wasmtime run --dir . --env FLAG=xxx sample.wasm # 显式授权 WASI 文件/环境能力 wasm3 sample.wasm checkPassword test # 无 WASI 模块用 wasm3(参数原样)- 插桩:
wasm-objdump -d静态指令 +--invoke黑盒差分——同一导出函数喂不同输入,观察输出变化定位分支/校验逻辑 - WASI 系统调用:
strace -f wasmtime run sample.wasm(坑 5,走 re-tracing) - 需要 BigInt 参数或进 JS 侧跟踪时,写 node 胶水:
const { instance } = await WebAssembly.instantiate(bytes, imports); console.log(instance.exports.f(1n, 2n)); // i64 必须 BigInt(坑 4) - 一切动态执行默认沙箱(re-analyze/platform-tips 最高原则)
- 插桩:
跨域联合
- re-managed:网关工作流步骤①(识别运行时)②(反编译)固定调用本技能;wasm 宿主程序(JS/Node)属托管代码域
- re-script-deob:JS 胶水代码(instantiate/import 对象/fetch wasm/atob 内嵌)先去混淆还原——imports 的实现基本都在 JS 侧,两技能必须联用
- re-tracing:WASI 程序动态侧——
strace -f wasmtime run观察系统调用边界 - re-emulation:可选——无合适运行时的 WASI 模块模拟执行(wasm 常经 Emscripten 编译,模拟前先按 WASI 映射理解宿主依赖)
- re-firmware:嵌入式/路由器 web 界面中的 wasm 模块
- 底座 re-binary-core:初勘(re-triage);wasm 与 native 混合样本
- re-malware:恶意网页/供应链 JS 加载 wasm 载荷的完整链路
- re-deobfuscate:wasm 混淆(wasm-obfuscator 等)属混淆对抗,深层还原思路一致
常见坑与陷阱
- 字符串在 data 段不在代码里:现象——wasm2wat/反编译只见
i32.const 偏移数字,grep 不到任何明文;原因——wasm 没有字符串类型,字符串常量存 Data 段、代码用内存地址引用;对策——wasm-objdump -s sample.wasmdump 全部 section 原始内容(hex dump,Data 段在其中;也可从-x输出读 Data section)(WAT 里看(data (i32.const 偏移) "…")),把偏移映射回引用处 - imports 是 JS 边界不是库调用:现象——wasm 文件里找不到关键校验/解密逻辑;原因——wasm 的 import 由宿主 JS 注入(
WebAssembly.instantiate(bytes, { env: {...} })),敏感逻辑在 JS 侧;对策——wasm-objdump -x列出 Import section → 到 JS 胶水代码找对应实现(re-script-deob)——wasm 只证明"调用了什么",不证明"它做了什么" - wasm 混淆(wasm-obfuscator 等)先反混淆再分析:现象——wasm2wat 输出大段控制流扁平化、死代码、
select噪音,字符串被切碎/加密,函数名全抹;原因——混淆器做控制流平坦化 + 字符串加密 + 名称抹除;对策——binaryen 逐遍优化还原平坦化:wasm-opt --flatten --simplify-locals --vacuum --dce -O3多轮迭代;字符串加密需定位解密逻辑(思路同 re-deobfuscate,注意解密函数可能是 import——回到坑 2) - BigInt 调用约定:现象——JS 调 wasm 导出函数报
TypeError: Cannot convert a BigInt value to a number,或 i64 参数静默截断;原因——i64 参数/返回值在 JS↔wasm 边界只能走 BigInt(Number 仅 2^53 精度);对策——JS 侧instance.exports.f(1n, 2n)传 BigInt;wasmtime--invoke参数按 JSON 解析不支持 BigInt——需要时写 node 胶水;复刻算法用 Python int / JS BigInt,别用 Number - WASI 系统调用边界:现象——wasmtime 跑 WASI 程序报
unknown import: wasi_snapshot_preview1.…,或浏览器与运行时行为不一致;原因——WASI 程序通过 importwasi_snapshot_preview1拿文件/时钟/随机数/网络能力,运行时未授权该能力即失败;浏览器里宿主 import 是 JS 而非 WASI,同一模块两端行为可以不同;对策——wasm-objdump -x看 Import 段确认用到的 WASI 函数 →wasmtime run --dir . --env K=v显式授权 →strace -f wasmtime run观察实际系统调用(re-tracing);结论标注运行环境(browser vs wasi)