
前言
大家在做一些涉及wasm 的js逆向,尝试使用nodejs本地调用wasm执行一些加解密操作时,可能会遇到以下错误:
RuntimeError: unreachable (syscall/js.Value).Get (syscall/js.Value).Int
或者:
LinkError: function import requires a callable
根本原因是在于 WASM 代码是按“浏览器环境”编译的。
而 Node.js:
没有 window 没有 document 没有 navigator 没有 screen 没有 location
当 Go wasm 执行:
js.Global().Get("navigator").Get("userAgent")
在浏览器里:
window.navigator.userAgent → string
在 Node 里没有这些对象、属性,然后导致代码崩溃。
一、目标
如何在 Node.js 中本地执行 wasm,并精确记录 wasm 访问了哪些浏览器环境对象?
思路:
-
用
wasm_exec.js跑 wasm -
用
Proxy伪造浏览器环境 -
记录所有属性访问路径
-
用类型白名单避免 Go wasm 崩溃
二、wasm_exec.js
wasm_exec.js 是 Go编译器在生成WebAssembly目标代码时自动产生的JavaScript引导文件, 它负责在浏览器环境中加载和执行Go编译的WebAssembly模块,它是 Go wasm 的运行桥接层。
关键点:
-
实现
syscall/js.* -
所有 Go 调用 JS 的行为都走这里
-
核心函数:
"syscall/js.valueGet"
"syscall/js.valueSet"
"syscall/js.valueCall"
所以:
我们只要控制 JS 一侧的 global/window,就能掌握 wasm 行为。
三、proxy.js 核心机制
1️⃣ watch 是整个系统核心
function watch(obj, name)
作用:
给任意对象包一层递归 Proxy,记录所有 get / set。
2️⃣ 核心:get 拦截
get(t, p, r)
每一次 wasm 访问:
window.navigator.userAgent
都会打印:
[GET] window.navigator [GET] window.navigator.userAgent
3️⃣ 递归代理机制
if (v0 && typeof v0 === "object")
return makeProxy(v0, key);
保证:
-
支持无限链式访问
-
每一层都能被追踪
4️⃣ cache 的意义(非常重要)
const cache = new Map();
作用:
同一路径返回同一个对象
否则 Go wasm 会炸。
这是保持引用稳定性的关键。
四、对象属性类型白名单
这是整个系统能稳定运行的核心。
为什么需要类型白名单?
Go wasm 在 JS 侧做强类型判断:
-
Int
-
String
-
Bool
如果:
window.innerWidth
返回 {}
Go 会崩。
1️⃣ NUM_PROPS
const NUM_PROPS = new Set([
"width", "height",
"innerWidth", "innerHeight",
"devicePixelRatio",
"length"
]);
遇到这些属性:
t[p] = 0;
返回 number。
避免 Go int 转换 panic。
2️⃣ STR_PROPS
"userAgent"
"platform"
"cookie"
"href"
统一返回:
""
保证 Go 的 stringVal 不出错。
3️⃣ BOOL_PROPS
"webdriver"
"cookieEnabled"
统一返回:
false
防止 Go 布尔解析崩溃。
六、如何扩展这三类属性
实际逆向中:
第一次运行一定会崩。
崩溃日志里会出现:
panic: interface conversion
或者:
value is not int
这时:
第一步
看最后访问的路径日志:
[GET] window.screen.availWidth
第二步
判断类型:
-
如果是尺寸 → NUM_PROPS
-
如果是字符串 → STR_PROPS
-
如果是布尔 → BOOL_PROPS
第三步
加入对应 Set
例如:
NUM_PROPS.add("availWidth");
再次运行。
不断补齐。
实战建议
按浏览器对象结构补齐:
常见数值
screen.* window.* performance.*
常见字符串
navigator.* location.* document.domain
常见布尔
navigator.webdriver document.hidden
补齐 20~40 个属性后,基本稳定。
七、挂载伪浏览器环境
globalThis.window = fakeBrowser;
globalThis.document = fakeBrowser;
globalThis.navigator = fakeBrowser;
globalThis.location = fakeBrowser;
目的:
所有入口都指向同一个 Proxy
这样:
无论 wasm 访问哪个全局对象,都能被记录。
八、执行 wasm
var go = new Go();
go.run(instance);
执行后:
所有 JS 访问行为都会被记录。
九、最终效果
输出示例:
[GET] window.navigator [GET] window.navigator.userAgent [GET] window.screen.width [GET] window.innerWidth

评论(0)