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文件拿出来调用(文章写的这种)......