WASM 逆向工程流程规范 v4.0
适用范围:Web 风控、签名参数、Token 生成、HLS Token、WebSocket 握手签名、加密参数复刻。
核心原则:先逆 JS 对 WASM 的调用关系,再逆 WASM 本身。v4.0 整合了普通样本、Rust / Go / Emscripten、混淆样本、控制流平坦化、VM 型 WASM 的完整分析体系。
目录
- 零、总原则
- 一、阶段一:识别编译器
- 二、阶段二:Import 分析(第一优先级)
- 三、阶段三:JS Glue 分析(第二优先级)
- 四、阶段四:Export 分析(第三优先级)
- 五、阶段五:Memory 分析(第四优先级)
- 六、阶段六:算法定位
- 七、阶段七:静态反汇编(高难度场景)
- 八、阶段八:动态调试(Chrome DevTools)
- 九、阶段九:Browser → Node 复刻
- 十、Go WASM 特殊流程
- 十一、完整决策树
- 十二、工具优先级
- 十三、实战口诀
- 十四、一句话总结
- 十五、混淆样本处理(Obfuscation Workflow)
- 十六、混淆场景实战口诀
- 十七、最终原则:三类样本统一分析体系
- 十八、v4.0 完整结构总览
- 十九、v4.0 一句话总结
零、总原则
0.1 分析顺序(不可颠倒)
JS Glue → Import → Export → Memory → WASM
反例(很多人搞反的):
WASM → Memory → Export → JS
0.2 效率原则
| 原则 | 说明 |
|---|---|
| 找输入输出 > 完全理解算法 | 前者半小时,后者可能三天 |
| 动态 > 静态 | 能 hook 就不要啃 CFG |
| Import > Export | Import 暴露外部依赖,比函数列表信息量大 |
| Memory API > 目标函数 | hook 内存写入比 hook sign() 更通杀 |
| 长度猜算法 > 逐行读代码 | 32 hex = MD5,64 hex = SHA256 |
0.3 时间分配
- 80% 时间:找入口、输入输出、内存布局、参数预处理
- 20% 时间:理解算法本身
- 算法只占 20%,参数预处理占 80%
一、阶段一:识别编译器
1.1 检查调试信息
# name section
wasm-objdump -h app.wasm | grep -i name
# DWARF
wasm-objdump -h app.wasm | grep debug
优先级:
- 有 name section →
wasm2wat直接带函数名 - 有 DWARF →
llvm-dwarfdump提取源码路径、函数名、变量名 - 都没有 → strip 模式,靠指纹判断
1.2 编译器特征指纹
| 特征 | 来源 | 备注 |
|---|---|---|
__wbindgen_* | Rust + wasm-bindgen | strip 后仍保留 malloc/free/realloc |
_ZN... | Rust / C++ | legacy mangling |
_RNv... | Rust | v0 mangling |
_emscripten_* | C/C++ + Emscripten | 同时有 emscripten_resize_heap |
golang.wasm + Go.importObject | Go | Go 1.21+ 用 go:wasmimport |
__new / __pin / __collect | AssemblyScript | 体积小、无 GC 运行时 |
wasi_snapshot_preview1 | WASI | 文件、时钟、随机 |
1.3 快速指纹命令
# Rust
strings app.wasm | grep -E "panicked at|unwrap|assertion|alloc::|core::|std::|__wbindgen"
strings app.wasm | grep -E "^_R"
# Go
strings app.wasm | grep -E "go:wasmimport|runtime.wasmExit|syscall/js"
# AssemblyScript
strings app.wasm | grep -E "__new|__pin|__unpin|__collect"
二、阶段二:Import 分析(第一优先级)
2.1 导出 Import Section
wasm-objdump -x app.wasm | sed -n '/Import/,/^$/p'
分析时第一件事:先看 Import Section,而不是先看函数。
2.2 按模块名分类
| Import 模块 | 典型来源 | 含义 |
|---|---|---|
env | Emscripten / Rust | 系统调用、随机数、时间 |
wasi_snapshot_preview1 | WASI | 文件、时钟、随机 |
wbg | wasm-bindgen | JS 函数桥接 |
go | Go | runtime 桥接 |
js | AssemblyScript | JS 交互 |
2.3 关键 import 名
(import "env" "random" ...) ; 签名依赖随机数
(import "env" "now" ...) ; 签名依赖时间戳
(import "env" "get_device_id" ...) ; 直接暴露风控来源
(import "env" "log" ...) ; 定位调用点
(import "env" "abort" ...) ; 错误处理
2.4 Hook 所有 Import
const origImports = { ...imports.env };
for (const k in origImports) {
imports.env[k] = (...args) => {
console.log('import', k, args);
return origImports[k](...args);
};
}
产出: 外部依赖清单,直接暴露风控来源。
三、阶段三:JS Glue 分析(第二优先级)
3.1 找 Glue 文件
| 编译器 | Glue 文件 |
|---|---|
| Rust wasm-bindgen | *_bg.js |
| Emscripten | *.js + *.wasm |
| Go | wasm_exec.js |
| AssemblyScript | *.js + *.wasm |
3.2 wasm-bindgen 的三种调用模式
// 模式 A:直接传 (ptr, len)
export function sign(data) {
const ptr0 = passStringToWasm0(data, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
return wasm.sign(ptr0, len0);
}
// 模式 B:传对象(struct)
export function verify(obj) {
const ptr0 = passStringToWasm0(obj.a, ...);
const ptr1 = passStringToWasm0(obj.b, ...);
return wasm.verify(ptr0, len0, ptr1, len1);
}
// 模式 C:返回字符串
export function getToken() {
const ret = wasm.getToken();
return getStringFromWasm0(ret[0], ret[1]);
}
3.3 关键锚点
passStringToWasm0
getStringFromWasm0
WASM_VECTOR_LEN
__wbindgen_add_to_stack_pointer
3.4 Rust 返回值:经常不是返回值
这是 Rust WASM 逆向最容易踩的坑。
// Rust: pub fn sign(s: String) -> String
// 编译后: (func sign (param $retptr i32) (param $ptr i32) (param $len i32))
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
wasm.sign(retptr, ptr, len);
const r0 = new DataView(wasm.memory.buffer).getUint32(retptr, true);
const r1 = new DataView(wasm.memory.buffer).getUint32(retptr + 4, true);
const result = getStringFromWasm0(r0, r1);
wasm.__wbindgen_add_to_stack_pointer(16);
关键点:
retptr是栈上 16 字节区域(两个 u32:ptr + len)__wbindgen_add_to_stack_pointer(-16)分配,(+16)释放- 返回
Result<T, E>时结构为[tag, ptr, len],tag 0 = Ok,1 = Err
看 Rust 样本时,一定搜 __wbindgen_add_to_stack_pointer,很多输出都藏这里。
3.5 产出
- 调用约定(参数格式、返回值格式)
- 输入输出类型
- 字符串构造方式
- malloc 实现
- 300 个 wasm func → 3 个候选
四、阶段四:Export 分析(第三优先级)
4.1 导出 Export Section
wasm-objdump -x app.wasm | sed -n '/Export/,/^$/p'
4.2 Hook 所有 Export
for (const k in wasm) {
const fn = wasm[k];
if (typeof fn !== 'function') continue;
wasm[k] = function(...args) {
const t0 = performance.now();
let ret;
try {
ret = fn.apply(this, args);
} catch (e) {
console.log(`[${k}] threw`, e);
throw e;
}
const dt = performance.now() - t0;
if (dt > 0.1 || args.length > 2 || typeof ret === 'string') {
console.log(`[${k}]`, { args, ret, time: dt.toFixed(2) + 'ms' });
}
return ret;
};
}
4.3 筛选目标函数的启发式
| 特征 | 含义 |
|---|---|
| 调用频率低 | 不是工具函数 |
| 耗时高 | 有加密运算 |
| 参数长 | 输入是待签名数据 |
| 返回字符串 | 输出是签名/token |
| 调用栈深 | 内部有复杂逻辑 |
4.4 Hook instantiate
const origInst = WebAssembly.instantiate;
WebAssembly.instantiate = async function(...args) {
const r = await origInst.apply(this, args);
window.__wasm = r.instance ? r.instance.exports : r.exports;
console.log('WASM exports:', window.__wasm);
return r;
};
const origStream = WebAssembly.instantiateStreaming;
WebAssembly.instantiateStreaming = async function(...args) {
const r = await origStream.apply(this, args);
window.__wasm = r.instance.exports;
console.log('WASM exports (stream):', window.__wasm);
return r;
};
五、阶段五:Memory 分析(第四优先级)
5.1 线性内存本质
uint8_t heap[16MB];
5.2 Memory Growth 导致 buffer 失效(关键坑)
// 错误:缓存 buffer
const mem = new Uint8Array(wasm.memory.buffer);
wasm.someFunction(); // 可能触发 memory.grow
mem[0]; // 读的是旧 buffer,全错
// 正确:每次重新获取
function mem() {
return new Uint8Array(wasm.memory.buffer);
}
5.3 Hook memory.grow
const origGrow = wasm.memory.grow;
wasm.memory.grow = function(pages) {
const ret = origGrow.call(this, pages);
console.log('memory.grow', pages, 'new size', wasm.memory.buffer.byteLength);
window.HEAP8 = new Int8Array(wasm.memory.buffer);
window.HEAPU8 = new Uint8Array(wasm.memory.buffer);
window.HEAP32 = new Int32Array(wasm.memory.buffer);
return ret;
};
5.4 Hook Memory API(通杀技巧)
很多人 Hook sign(),实际上更通杀的是 Uint8Array.prototype.set 或 TextEncoder.prototype.encode。
// 1. Uint8Array.prototype.set —— 谁在写内存
const oldSet = Uint8Array.prototype.set;
Uint8Array.prototype.set = function(...args) {
if (this.buffer === wasm.memory.buffer) {
console.log('wasm memory write', args[0], 'at', args[1]);
}
return oldSet.apply(this, args);
};
// 2. TextEncoder.prototype.encode —— 字符串转字节
const oldEncode = TextEncoder.prototype.encode;
TextEncoder.prototype.encode = function(str) {
console.log('encode', str);
return oldEncode.call(this, str);
};
// 3. DataView.prototype.setUint32 —— 写整数
const oldSetU32 = DataView.prototype.setUint32;
DataView.prototype.setUint32 = function(...args) {
if (this.buffer === wasm.memory.buffer) {
console.log('setUint32', args);
}
return oldSetU32.apply(this, args);
};
马上能看到:谁在往 wasm memory 写数据、写了什么、长度多少。对高度混淆的站点特别有效。
5.5 Rust 字符串读取
function readRustString(ptr, len) {
return new TextDecoder().decode(
new Uint8Array(wasm.memory.buffer, ptr, len)
);
}
5.6 Memory 修改攻击(字段定位)
const mem = new Uint8Array(wasm.memory.buffer);
const original = mem.slice(ptr, ptr + len);
for (let i = 0; i < len; i++) {
const backup = mem[ptr + i];
mem[ptr + i] = backup ^ 0xFF;
const sig = wasm.sign(ptr, len);
if (sig !== originalSig) {
console.log(`byte ${i} participates in signature`);
}
mem[ptr + i] = backup;
}
快速定位:参与签名字段 vs 不参与签名字段。
六、阶段六:算法定位
6.1 长度猜算法(先根据长度猜,效率非常高)
| 输入长度 | 输出长度 | 高概率算法 |
|---|---|---|
| 任意 | 32 hex | MD5 |
| 任意 | 40 hex | SHA1 |
| 任意 | 64 hex | SHA256 |
| 任意 | 128 hex | SHA512 |
| 任意 | 44 字符 | Base64(SHA256) |
| 任意 | 24 字符 | Base64(MD5) |
| 任意 | 与输入相近 | Base64 / Hex |
| 固定短输出 | 8-16 字符 | CRC / 自定义 hash |
输出带 . 分隔 | 3 段 | JWT |
6.2 识别“假加密”
很多网站 token / sign / secure / encrypt 名字吓人,实际上:
base64(md5(JSON))
甚至:
JSON.stringify() → btoa()
结束。
常见组合:
base64(JSON.stringify(obj))hex(md5(str + salt))btoa(unescape(encodeURIComponent(str)))str.split('').reverse().join('')
6.3 常数搜索
xxd app.wasm | grep -i "428a2f98"
wasm2wat app.wasm | grep -i "428a2f98"
SHA256 初始常量:
6a09e667 bb67ae85 3c6ef372 a54ff53a
510e527f 9b05688c 1f83d9ab 5be0cd19
SHA256 轮常量:
428a2f98 71374491 b5c0fbcf e9b5dba5
MD5 常量:
d76aa478 e8c7b756 242070db c1bdceee
AES S-box: 0x63, 0x7c, 0x77, 0x7b... 开头,256 字节表。
CRC32: edb88320。
Rust 常见 hash: sha2、ring、blake3、tiny-keccak。
七、阶段七:静态反汇编(高难度场景)
7.1 WABT vs Binaryen
# 指令级,精确对照
wasm2wat app.wasm > app.wat
# 伪代码,接近 C
wasm-decompile app.wasm -o app.dcmp
策略: 先用 wasm-decompile 扫一遍找可疑函数,再用 wasm2wat 精读关键函数。
7.2 Ghidra
- 装 WASM 插件
- 导入时选
wasm:LE:32:default __wbindgen_malloc是关键锚点call_indirect对应 table,需手动恢复目标
7.3 call_indirect 分析
不要硬啃,直接看 table。
wasm-objdump -x app.wasm | grep -A20 "Table\|Elem"
(elem (i32.const 1)
$f12
$f13
$f14)
call_indirect 的实际候选函数已经有限。
C++ 虚函数:90% 都是 table dispatch,不是动态生成的。
7.4 Rust 特殊技巧
# 先搜索
__wbindgen_malloc
__wbindgen_free
__wbindgen_realloc
# 然后找
sha256 / md5 / hmac / sign / encrypt / verify
即使符号被裁掉,也通常能通过:
malloc → memcpy → 核心算法 → 返回字符串
定位签名路径。
八、阶段八:动态调试(Chrome DevTools)
8.1 Memory Inspector(隐藏神器)
- Sources 面板打开 wasm 文件
- 指令级下断点
- 断到
local.get或i32.load时,右侧 Scope 显示memory - 右键
memory→ “Inspect Memory”
支持视图: hex、uint8、uint32、float32、ascii、utf8。
分析 JSON 序列化、protobuf、msgpack 非常舒服。
8.2 Frida
Interceptor.attach(wasmExports.sign, {
onEnter(args) {
console.log('sign called', args[0], args[1]);
},
onLeave(retval) {
console.log('sign returned', retval);
}
});
九、阶段九:Browser → Node 复刻
9.1 复刻流程
WASM → 定位 export.sign → 抓输入 → 抓输出 → 分析 memory 格式 → Node 复现
9.2 真正难的不是算法
算法只占 20%,参数预处理占 80%。
优先逆 passStringToWasm0() 附近逻辑,而不是先研究 10000 行加密代码。
9.3 参数预处理清单
// 1. 时间戳格式
Date.now() // 毫秒
Math.floor(Date.now()/1000) // 秒
new Date().toISOString() // ISO
// 2. 随机数来源
Math.random() // 可预测
crypto.getRandomValues() // 不可预测
performance.now() // 高精度
// 3. 字符串拼接顺序
const signStr = `${a}&${b}&${c}`;
// 4. 编码方式
encodeURIComponent()
btoa()
JSON.stringify()
9.4 Hook 时间戳和随机数
const origNow = Date.now;
Date.now = function() {
const v = origNow();
console.trace('Date.now', v);
return v;
};
十、Go WASM 特殊流程
10.1 识别
strings app.wasm | grep -E "go:wasmimport|runtime.wasmExit|syscall/js"
典型特征: new Go()、go.run(instance)、wasm_exec.js。
10.2 Rust 经验套 Go 会失效
| Rust | Go | |
|---|---|---|
| 字符串传递 | ptr + len 非常明显 | runtime 打包 |
| 内存 | 线性内存直接可见 | 自有 GC heap |
| 特征 | __wbindgen_* | runtime / GC / scheduler / stack growth 全打包 |
10.3 换思路:先看桥接点
syscall/js.valueCall
syscall/js.valueGet
syscall/js.valueSet
syscall/js.valueNew
syscall/js.valueInvoke
Go 大量逻辑通过 syscall/js 访问浏览器对象,比盯着 wasm 指令有价值。
十一、完整决策树
拿到 wasm
↓
有 name section? → 是 → wasm2wat 直接看
↓ 否
有 DWARF? → 是 → llvm-dwarfdump
↓ 否
是 Go WASM? → 是 → 看 syscall/js 桥接
↓ 否
Import 有 random/now/device_id? → 是 → 风控依赖这些
↓ 否
有 JS Glue? → 是 → 读 glue,定位 export 和调用约定
↓ 否
Export 名可读? → 是 → 直接 hook export
↓ 否
Hook export + memory → 观察输入输出
↓
长度猜算法 → 验证
↓
需要深入? → Ghidra + call_indirect 分析
十二、工具优先级
S 级
- Chrome DevTools(含 Memory Inspector)
- WABT(
wasm2wat) - Ghidra + WASM Plugin
wasm-tools print
A 级
- Binaryen(
wasm-dis/wasm-decompile) - IDA Pro
llvm-dwarfdump
B 级
- Frida
- Burp Suite
- mitmproxy
- radare2
2026 年个人工具优先级(针对签名/风控/Token)
Chrome DevTools
↓
JS Glue
↓
wasm-tools print
↓
Ghidra
↓
Frida
而不是:
Ghidra → IDA → 看 3 天 CFG
因为:找到输入输出 > 完全理解算法。
十三、实战口诀
一看 Import → 外部依赖(随机数、时间、设备指纹)
二看 JS Glue → 调用约定、输入输出格式
三看 Export → 入口函数列表
四看 Memory → 数据布局、buffer 失效
五看 malloc → 内存分配点、字符串构造
六看返回字符串 → 签名/token 输出
七看算法常数 → SHA/MD5/AES/CRC 指纹
八看 CFG → call_indirect、复杂控制流
Import、JS Glue、Memory 往往已经能解决 80% 的 Web 签名样本。
真正需要深入到 Ghidra、call_indirect、CFG 恢复,通常是最后 20% 的高难度场景。
十四、一句话总结
不要逆 WASM,先逆 JS 对 WASM 的调用关系。
不要先看算法,先找输入输出和内存布局。
不要从 export 开始追,先从 Import、JS Glue、算法常数反推调用链。
实际项目里,能快速拿下签名和 token 的突破口,90% 都在 JS Glue、Import、memory layout、export 调用链,而不是 WASM 指令本身。
十五、混淆样本处理(Obfuscation Workflow)
15.1 什么时候怀疑存在混淆
JS 侧特征
出现:
_0x31a2
_0x7fd1
_0xa8b3
或者:
eval(...)
new Function(...)
Function(...)
或者:
while(!![])
(function(_0x1234){...})()
通常意味着:JS 已混淆。
WASM 侧特征
Export 极少
正常样本:
malloc
free
sign
verify
encrypt
混淆后:
a
b
c
d
甚至:仅 1~2 个 Export。
Name Section 消失
wasm-objdump -h app.wasm
不存在:
name
同时:
debug
也被删除。
call_indirect 异常多
call_indirect
call_indirect
call_indirect
占比远高于正常 Rust / Emscripten 项目。
可能存在:
- 控制流平坦化
- 虚拟机调度
- 动态派发
超大 Table
(table 2000 funcref)
正常业务代码通常:几十到数百 即可。
大量 table 通常需要重点关注。
15.2 先 Dump 运行时 WASM
原则
很多项目:
网络中的 wasm ≠ 真正执行的 wasm
实际流程:
下载 → 解密 → 重组 → instantiate
因此:先拿运行时 wasm,再做分析。
Hook instantiate
const oldInstantiate = WebAssembly.instantiate;
WebAssembly.instantiate = async function(bytes, ...args) {
if (bytes instanceof ArrayBuffer) {
window.__wasmBinary = bytes.slice(0);
}
return oldInstantiate.apply(this, arguments);
};
Hook instantiateStreaming
const oldStreaming = WebAssembly.instantiateStreaming;
WebAssembly.instantiateStreaming = async function(resp, ...args) {
const clone = resp.clone();
const buf = await clone.arrayBuffer();
window.__wasmBinary = buf;
return oldStreaming.apply(this, arguments);
};
15.3 动态解密 WASM 处理
常见结构:
fetch → AES → ArrayBuffer → instantiate
或者:
fetch → xor → inflate → instantiate
重点搜索:
crypto.subtle.decrypt
atob
pako.inflate
xor
decode
Hook:
const oldDecrypt = crypto.subtle.decrypt;
crypto.subtle.decrypt = async function(...args) {
const ret = await oldDecrypt.apply(this, args);
console.log(ret);
return ret;
};
目标:拿到解密后的 wasm,而不是研究解密器本身。
15.4 字符串加密处理
特征
strings app.wasm
几乎看不到可读字符串。
优先 Hook
const oldDecode = TextDecoder.prototype.decode;
TextDecoder.prototype.decode = function(...args) {
const s = oldDecode.apply(this, args);
if (s.length > 3) {
console.log(s);
}
return s;
};
价值
直接获得运行时解密字符串,包括:
- 接口路径
- 盐值
- 错误消息
- 算法名
- 配置字段
15.5 控制流平坦化(Control Flow Flattening)
典型结构
while (1) {
switch(state) {
case 1:
...
break;
case 2:
...
break;
}
}
对应 WAT
loop
block
br_table
end
end
判断依据
出现:
- 一个巨大循环
- 大量
br_table - 状态变量反复修改
即可怀疑。
分析原则
不要立即恢复 CFG。
先记录:
state 变化
真实执行路径。
动态执行路径通常比静态 CFG 恢复效率高很多。
15.6 VM 型混淆
越来越常见。
典型结构
for(;;){
opcode = code[ip++];
switch(opcode){
...
}
}
识别特征
- 巨大函数
- 巨大 switch
- 大量 br_table
通常:单函数占整个 wasm 体积 30%~70%。
优先目标
找到 Dispatcher,而不是恢复所有 Handler。
分析顺序
Dispatcher → Opcode 格式 → Handler
15.7 Runtime Table Dump
针对 call_indirect 特别多的样本。
Hook:
const oldGet = WebAssembly.Table.prototype.get;
WebAssembly.Table.prototype.get = function(index) {
const fn = oldGet.call(this, index);
console.log("table", index);
return fn;
};
得到:间接调用目标分布。
有助于恢复:
- 虚表
- trait dispatch
- VM dispatch
15.8 Hook 全部随机源
很多风控系统:
复杂的不是算法
复杂的是随机源
统一 Hook:
Math.random
Date.now
performance.now
crypto.getRandomValues
例如:
const oldRandom = crypto.getRandomValues;
crypto.getRandomValues = function(buf) {
console.log("random", buf.length);
return oldRandom.call(this, buf);
};
快速判断:
- 哪些随机值参与签名
- 哪些随机值只是噪声
15.9 混淆样本决策树
拿到 wasm
↓
存在 name/debug ?
↓ 否
检查 Import
↓
检查 Glue
↓
Hook instantiate
↓
Dump Runtime Wasm
↓
存在解密流程 ?
↓ 是
Hook decrypt
↓
获取真实 wasm
↓
call_indirect 很多 ?
↓ 是
Dump Table
↓
恢复 Dispatch
↓
巨大 Loop ?
↓ 是
怀疑 VM
↓
定位 Opcode 解释器
↓
动态跟踪执行
十六、混淆场景实战口诀
先看 Import
再看 Glue
然后 Dump Runtime Wasm
能 Hook
别硬删混淆
能 Dump
别硬猜算法
能拿输入输出
别先看 CFG
先解密
后分析
先定位 Dispatcher
后看 Opcode
先跑执行路径
后恢复控制流
十七、最终原则:三类样本统一分析体系
17.1 普通样本
JS Glue → Import → Export → Memory → Algorithm
17.2 混淆样本
JS Glue → Runtime Dump → Memory → Dispatcher → Algorithm
17.3 VM 样本
JS Glue → Runtime Dump → Opcode → Handler → Algorithm
十八、v4.0 完整结构总览
| 章节 | 内容 | 适用样本 |
|---|---|---|
| 1~10 | 正常样本分析流程 | 普通 WASM |
| 11 | 决策树 | 全部 |
| 12 | 工具优先级 | 全部 |
| 13 | 实战口诀 | 普通样本 |
| 14 | 一句话总结 | 全部 |
| 15 | 混淆样本处理 | 混淆 / 平坦化 |
| 16 | 混淆场景口诀 | 混淆 / 平坦化 |
| 17 | 三类样本统一原则 | 全部 |
| 18 | 结构总览 | — |
| 19 | 一句话总结 | 全部 |
十九、v4.0 一句话总结
普通样本看 JS Glue,混淆样本先 Dump Runtime Wasm,VM 样本先找 Dispatcher。
能 Hook 别硬删,能 Dump 别硬猜,能拿输入输出别先看 CFG。
先解密后分析,先定位 Dispatcher 后看 Opcode,先跑执行路径后恢复控制流。
这样就把 普通 WASM、Rust / Go / Emscripten、混淆样本、控制流平坦化、VM 型 WASM 全部纳入同一套分析体系了。