wasm加载流程与逆向实战
WebAssembly(Wasm)加载运行
mdn文档:https://developer.mozilla.org/en-US/docs/WebAssembly/Loading_and_running
查看文档可知:
获取网络资源Fetch或者XMLHTTPRequest
获取、编译和实例化Wasm模块有两种方法:
较新的 WebAssembly.compileStreaming/WebAssembly.instantiateStreaming 方法效率更高 - 它们直接对来自网络的原始字节流执行操作,从而无需执行该 ArrayBuffer 步骤。
旧的 WebAssembly.compile/WebAssembly.instantiate 方法要求您在获取原始字节后创建一个 ArrayBuffer 包含 WebAssembly 模块二进制文件,然后编译/实例化它。
实例化Wasm模块的时候需要传入两个参数:
module:要被实例化的 WebAssembly.Module 对象
一个包含值的对象,导入到新创建的 实例, 比如函数或 WebAssembly.Memory 对象。There must be one matching property for each declared import of module否则抛出 WebAssembly.LinkError 异常
Synchronously instantiating a WebAssembly module
The preferred way to get an Instance is asynchronously, for example using the WebAssembly.instantiateStreaming() function like this:
const importObject = {
imports: {
imported_func(arg) {
console.log(arg);
},
},
};
WebAssembly.instantiateStreaming(fetch("simple.wasm"), importObject).then(
(obj) => obj.instance.exports.exported_func(), // 导出的方法,在爬虫,这个就是加密方法以及其依赖
);
The WebAssembly.Instance() constructor function can be called to synchronously instantiate a given WebAssembly.Module object, for example:
const importObject = {
imports: {
imported_func(arg) {
console.log(arg);
},
},
};
fetch("simple.wasm")
.then((response) => response.arrayBuffer())
.then((bytes) => {
const mod = new WebAssembly.Module(bytes);
const instance = new WebAssembly.Instance(mod, importObject);
instance.exports.exported_func(); // 导出的方法,在爬虫,这个就是加密方法以及其依赖
});
WebAssembly(Wasm)逆向实战
网址:aHR0cHM6Ly93d3cuc2F0ZW5hLmNvbS8=
追栈,找目标值
全局搜索"wasm_token"
r值
{
"departure_date": "2024-02-13",
"origin": "HAY",
"destination": "APO",
"adults": 1,
"minors": 0,
"infants": 0,
"cabin": "economy",
"country_setting": {
"currency_code": "COP",
"default": true,
"device_id": "BOG009RW04",
"enable": true,
"id": 17,
"iso_country_code": "CO",
"setting": "b67a0fd6-da2e-4c6f-81f9-628086daf8ec",
"user_id": "BOG009RWK"
},
"return_date": null,
"session_key": "backendsearchflight1bd9678c38ee050c71eb5694AAD-584a32c2acf6f7940e85c69bcce516ac44d0737de252e569",
"device_model": "???",
"device_id": "???",
"device_branding": "???",
"device_os_system_version": 10,
"device_os_system": "Windows",
"agent_preferred_language": "zh-CN",
"device_category": "DESKTOP",
"promo_code": ""
}
继续追
单步进入
单步进入
获取、编译和实例化wasm模块
搜索 "WebAssembly.instantiateStreaming"
上面这个是void.wasm文件,继续追
看到WebAssembly.instantiate关键词,它的传入是binary,info两个参数,binary是wasm文件的字节数据,info是导入实例的对象
现在看binary是如何生成的,这里明显是getBinaryPromise()这个异步方法成功的回调,进入该方法
这里直接fetch目标的wasm文件,然后将响应转成arrayBuffer,然后用WebAssembly.instantiate(binary, info)进行实例化
binary, info
binary
import requests
headers = {
'authority': 'web.satena.com',
'accept': '*/*',
'accept-language': 'zh-CN,zh;q=0.9',
'referer': 'https://web.satena.com/booking/widget?carrier=9r&lang=es',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
}
response = requests.get('https://web.satena.com/wasm/void.wasm', cookies=cookies, headers=headers)
# 保存void.wasm放到本地
with open('./void.wasm', 'wb') as f:
f.write(response.content)
# 后面再用nodejs读取即可
info
把info扣出来即可
wasm模块导出
wasm模块调用
将依赖的函数和变量等都扣出来之后,即可调用
window.validateForm = Module.cwrap("validateForm", "string", ["string"])
r["wasm_token"] = window.validateForm(r)
效果展示
结束
破解Wasm的方法不止一种,纯算(依赖工具wasm代码转成c++/js等进行静态分析)、直接把wasm文件拿出来调用(文章写的这种)......