WebAssembly 案例分析与爬取实战

news/2024/9/23 8:56:27/

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 pywasmruntime = 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 requestsBASE_URL = 'https://spa14.scrape.center'
TOTAL_PATE = 10runtime = pywasm.load('./Wasm.wasm')
for i in range(TOTAL_PATE):offset = i * 10sign = 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 来完成同样的操作。 相比  pywasmwasmer-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 Compilerstore = 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 Compilerstore = Store(engine.JIT(Compiler))
module = Module(store, open('Wasm.wasm', 'rb').read())
instance = Instance(module)BASE_URL = 'https://spa14.scrape.center'
TOTAL_PAGE = 10runtime = pywasm.load('./Wasm.wasm')
for i in range(TOTAL_PAGE):offset = i * 10sign = 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())

运行结果一样


http://www.ppmy.cn/news/1509868.html

相关文章

偶然遇到了scanf输入字符时,前面与要加上空格

任务描述 本关任务&#xff1a;给定一个含有n个学生数据元素的数组a&#xff0c;用头插法来快速创建整个单链表。 相关知识 创建单链表有两种方法&#xff1a; 先初始化一个单链表&#xff0c;然后向其中一个一个地插入元素&#xff0c;通过调用基本运算算法来创建单链表。…

亚马逊测评的本质是什么?

在电商领域&#xff0c;无论是深耕国内市场还是拓展国际版图&#xff0c;精准选择并成功推广引流款产品至关重要&#xff0c;这一过程的核心环节之一便是对上架产品进行系统化测评&#xff0c;以确保其在市场中获得良好的曝光与转化&#xff0c;以下是对亚马逊平台测评流程的深…

https中的TLS协议

TLS简介 TLS是一种安全协议&#xff0c;用于在两个通信应用程序之间提供保密性和数据完整性。 它是SSL协议的继任者&#xff0c;由IETF&#xff08;互联网工程任务组&#xff09;在RFC 5246中标准化。 TLS协议位于TCP/IP协议栈的传输层之上&#xff0c;它使用加密技术来确保…

【密码学】密钥管理:③密钥的保护、存储、备份、撤销、过期、销毁

之前的文章把密钥管理技术的关键内容讲完了&#xff0c;重点是理解密钥管理系统的概念、密钥生成和密钥分配。剩下的有关密钥的保护、存储、备份、撤销、过期和销毁的内容&#xff0c;就用这篇文章简单的做个介绍即可。如果没有看前面重点内容的可以点击下面链接跳转&#xff1…

低代码开发平台通过钉钉API实现流程管理数据对接

实例背景&#xff1a; CRM项目虽然实现了报价转订单的功能&#xff0c;但是客户还是遇到使用不方便的问题&#xff0c;客户的业务流程中&#xff0c;审核报价的时候是需要提供销售人员与客户的聊天记录截图&#xff0c;这都是在手机上的&#xff0c;电脑操作不方便&#xff0c…

Node.js、npm和ng之间的关系

一、Node.js 定义&#xff1a;Node.js是一个开源的、跨平台的JavaScript运行环境&#xff0c;它允许开发者在服务器端运行JavaScript代码。Node.js基于Chrome V8引擎&#xff0c;提供高性能和非阻塞I/O&#xff08;输入输出&#xff09;操作。功能&#xff1a;Node.js主要用于…

idea付费插件,哪个比较好用?

以下idea付费插件你们都用过哪些呢&#xff1f; 获取链接&#xff1a;https://web.52shizhan.cn

Docker Swarm部署SpringCloud Alibaba微服务踩坑记录

为了方便部署和维护微服务项目&#xff0c;还是得上集群部署方案&#xff0c;决定采用Docker的swarm&#xff0c;为什么不是k8s&#xff0c;因为部署骑来又是个新的工具&#xff0c;之前就一直用的docker&#xff0c;自带了类k8s的工具&#xff0c;索性就直接使用swarm了&#…