WebAssembly 案例分析与爬取实战

WebAssembly 简介

WebAssembly 是一种可以使用非 JS 编程语言编写代码并且能在浏览器上运行的技术

借助 Emscripten 工具,我们能将 C/C++ 文件转成 wasm 格式的文件, JS 可以直接调用该文件执行其中的方法

这样做的好处如下:

一些核心逻辑(比如 API 参数的加密逻辑) 使用 C/C++ 实现,这样这些逻辑就可以"隐藏" 在编译生成的 wasm 文件中, 其逆向难度比 JS 更大

一些逻辑基于 C/C++ 编写的, 有更高的执行效率, 这使得以各种语言编写的代码都可以以接近原生的速度在 Web 中运行

对于这种类型的网站,我们一般会看到网站会加载一些 wasm 后缀的文件,这就是 WebAssembly 技术常见的呈现形式, 即原生代码被编译成了 wasm 后缀的文件, JS 通过调用 wasm 文件得到对应的计算结果, 然后配合其他 JS 代码实现页面数据的加载和页面的渲染

案例介绍

网站: https://spa14.scrape.center

我们按照常规的方法,加载首页,然后通过 Nerwork 面板分析 Ajax 请求,可以看到,这里就找到了第一页数据的 Ajax 请求, limit , offset 参数用来控制分页, sign 参数用来做校验,它的值是一个数字。通过观察后面几页的内容,我们发现 sign 的值一直在变化

因此,这里关键就是找到 sign 值的生成逻辑, 我们再模拟请求即可

我们这里设置一个 Ajax 断点, 在 Sources 面板的 XHR/fetch Breakpins 这里添加一个断点, 内容为 /api/movie , 就是在请求加载数据的时候进入断点

然后翻页,就可以看到页面执行到断点的位置就停了下来

这里我们通过 Call Stack 找到构造逻辑, 经过简单的查找和推测, 我们可以判断逻辑入口在 onFetchData 方法里

我们可以看到, params 有三个参数,分别是 limit , offset , sign 这和 Ajax 请求一致。

如果在平时可以继续添加断点,进一步验证其正确性

这里的关键参数就是 sign 了, 可以看到它的值是用变量 e 表示的,而 e 的生成代码就在上面

var n = (this.page - 1) * this.limit

, e = this.$wasm.asm.encrypt(n, parseInt(Math.round((new Date).getTime() / 1e3).toString()));

可以看到,它通过调用了 this.$wasm.asm 对象的 encrypt 方法传入了 n 和一个时间戳构造出来的,接下来我们进一步调试, 在

var n = (this.page - 1) * this.limit

添加断点,重新刷新页面,运行到该断点停下来

这相当于 JS 上下文处于 onFetchData 方法内部,所以我们可以访问方法内部的所有变量, 比如 this , this.$wasm 等

接下来我们就在 Watch 面板添加一个 this.$wasm , 先看看它是什么对象

可以看到,这个 this.$wasm 对象里面又定义了很多对象和方法,其中包括了 asm 对象。因为代码中又调用了 asm 对象的 encrypt 来产生 sign , 所以我们进一步看看 asm 对象, encrypt 方法都是什么? 我们可以看到 asm 对象里面又包含了几个对象和方法, 比较重要的就是 encrypt 方法了, 其中它的 [[ function ]] 指向另一个位置, 名称是 Wasm.wasm:0xd9 。因为我们就是想知道这个方法内部是什么逻辑,所以直接点击进入

可以看到我们进入了一个不是 JS 代码的位置,文件名称叫作 Wasm.wasm ,在代码中我们可以看到 encrypt 字样

(func encrypt (;4;) (export "encrypt") (param var0 i32) (param $var1 i32) (result i32)

local.get $var0

local.get $var1

i32.const 3

i32.div_s

i32.add

i32.const 16358

i32.add

如果你了解汇编语言的话,这里会发现有点汇编语言的味道

这其实就是 wasm 文件,这里面的逻辑其实原本是用 C++ 编写的, 通过 Emscripten 转化为 wasm 文件, 就成了现在的样子

这时候我们找下 Network 请求, 搜索 wasm 后缀文件

可以看到,这里就有 wasm 后缀的文件, 其逻辑就是刚才看到的内容。到了这里代码就看不懂了

解决方法有两种: 一种是直接把 wasm 文件反编译, 还原成 C++ 代码,这种方法上手难度大,需要了解 WebAssembly 和逆向相关的知识,另一种就是通过模拟执行的方式来直接得到加密结果

这里我们主要使用第二种方案,拿到 wasm 文件, 然后通过 Python 模拟执行的方式调用 wasm 文件, 模拟调用它的 encrypt 方法,传入对应参数即可

模拟执行

首先我们可以把文件下载下来,复制,或者使用 Overrides 另存都可以

要使用 python 模拟执行 wasm ,可以使用两个库, 一个叫作 pywasm 另一个叫作 wasmer-python 前者使用简单,后者功能强大

pywasm

这个库比较简单,其主要功能就是加载 wasm 文件, 然后用 Python 执行

安装 pip install pywasm

然后是加载文件

复制代码
import pywasm

runtime = pywasm.load("./Wasm.wasm")
print(runtime)

<pywasm.Runtime object at 0x00000163CA23C150>

这里我们调用了 pywasm 的 load 方法,直接将 wasm 文件的路径传入, 实现了 wasm 文件的读取, 返回的结果是一个 pywasm.Runtime 类型的对象

有了这个 Runtime 对象之后,我们就可以调用它的 exec 方法来模拟执行 Wasm 里面的方法

比如,在网页中我们可以看到它执行了 encrypt 方法,并传入了两个参数。我们来试一下, 要调用 wasm 的方法, 只需要调用 Runtime 对象的 exec 方法并传入对应的方法名和参数内容即可

复制代码
runtime = pywasm.load("./Wasm.wasm")
result = runtime.exec('encrypt', [1, 2])
print(result)

16359

这里我们调用了 exec 方法,第一个参数就是要调用的 wasm 中的方法名, 这里我们传入字符串 encrypt , 第二个参数是一个列表, 代表 encrypt 方法所接收的参数, 如果是两个,那么列表的长度就是 2, 参数与列表元素一 一对应即可,结果出来了,但似乎并不是我们想要的,因为参数是我们自定义的,而要想真正模拟 Ajax 请求, 就要用网站里的实参。通过逻辑分析,我们知道传入的参数其实是一个 offset 和一个时间戳

后者的实现是这样的

parseInt(Math.round((new Date).getTime() / 1e3).toString())

这时 JS 的实现,我们将其输出到控制台

输出的其实是一个时间戳,结果是数值类型, 位数是10位。 使用 python 实现同样的结果

import time

int(time.time())

最终,我们可以将爬虫逻辑实现如下

复制代码
import pywasm
import time
import requests

BASE_URL = 'https://spa14.scrape.center'
TOTAL_PATE = 10

runtime = pywasm.load('./Wasm.wasm')
for i in range(TOTAL_PATE):
    offset = i * 10
    sign = runtime.exec('encrypt', [offset, int(time.time())])
    url = f'{BASE_URL}/api/movie/?limit=10&offset={offset}&sign={sign}'
    response = requests.get(url)
    print(response.json())

{'count': 103, 'results': [{'id': 21, 'name': '黄金三镖客', 'alias': 'Il buono, il brutto, il cattivo.', 'cover':

这里省略了很多内容。。。。。。

'https://p0.meituan.net/movie/b0d986a8bf89278afbb19f6abaef70f31206570.jpg@464w_644h_1e_1c', 'categories': ['剧情', '历史', '战争'], 'published_at': '1993-11-30', 'minute': 195, 'score': 9.5, 'regions': ['美国']}, {'id': 100, 'name': '魂断蓝桥', 'alias': 'Waterloo Bridge', 'cover': 'https://p0.meituan.net/movie/58782fa5439c25d764713f711ebecd1e201941.jpg@464w_644h_1e_1c', 'categories': ['剧情', '爱情', '战争'], 'published_at': '1940-05-17', 'minute': 108, 'score': 9.5, 'regions': ['美国']}]}

这里我们先定义了 TOTAL_PAGE 是 10, 就是 10 页, 然后开始一个 for 循环遍历, i 就是 0-9 的数字 offset 就是 0, 10 ,20.....90 , sign 就是利用刚才的实现,将参数转化为 offset 变量和时间戳,最后构造出 URL 即可

wasmer-python

除了使用 pywasm 库, 我们还可以使用另一个库 wasmer-python 来完成同样的操作。 相比 pywasm , wasmer-python 的更能更为 强大,它提供了更为底层的 API 。如果遇到更为复杂的wasm 调用情形, 推荐使用 wasmer-python

安装: pip install wasmer wasmer_compiler_cranelift

注意: 2024. 8.12 , 截止今天,python 3.10 以上的版本不支持

复制代码
from wasmer import engine, Store, Module, Instance
from wasmer_compiler_cranelift import Compiler

store = Store(engine.JIT(Compiler))
module = Module(store, open('./Wasm.wasm', 'rb').read())
instance = Instance(module)
result = instance.exports.encrypt(1, 2)
print(result)

在 pychrm 中会提示,但是可以正常执行

16359

更多关于 API 参考: https://wasmerio.github.io/wasmer-python/api/wasmer

根据前面的逻辑,我们再实现一下爬取过程

复制代码
import requests
import time
import pywasm
from wasmer import engine, Store, Module, Instance
from wasmer_compiler_cranelift import Compiler

store = Store(engine.JIT(Compiler))
module = Module(store, open('Wasm.wasm', 'rb').read())
instance = Instance(module)

BASE_URL = 'https://spa14.scrape.center'
TOTAL_PAGE = 10

runtime = pywasm.load('./Wasm.wasm')
for i in range(TOTAL_PAGE):
    offset = i * 10
    sign = instance.exports.encrypt(offset, int(time.time()))
    url = f'{BASE_URL}/api/movie/?limit=10&offset={offset}&sign={sign}'
    response = requests.get(url)
    print(response.json())

运行结果一样

相关推荐
REDcker6 小时前
Wasm 软解 H.265 方案与原理
wasm·h.265
步步为营DotNet6 天前
ASP.NET Core 10中的Blazor WebAssembly性能优化实践
性能优化·asp.net·wasm
前端之虎陈随易7 天前
Vite 8正式发布,内置devtool,Wasm SSR 支持
前端·人工智能·typescript·npm·node.js·wasm
古城小栈9 天前
Rust 开发 WebAssembly 一眼案例
开发语言·rust·wasm
csdn_aspnet10 天前
.NET 10 中的 Blazor:新增功能及常见问题
wasm·blazor·.net10
zhojiew1 个月前
使用envoy配置jwt校验和ratelimit限流以及通过wasm扩展统计llm消耗token
wasm·envoy
狗都不学爬虫_2 个月前
JS逆向 -最新版 盼之(decode__1174、ssxmod_itna、ssxmod_itna2)纯算
javascript·爬虫·python·网络爬虫·wasm
老百姓懂点AI2 个月前
[WASM实战] 插件系统的安全性:智能体来了(西南总部)AI调度官的WebAssembly沙箱与AI agent指挥官的动态加载
人工智能·wasm
坚定信念,勇往无前2 个月前
unity发布BuildWebGL.wasm 加载过慢
unity·wasm