写在开头
- 这题也是,自己搞顶多追踪到wasm代码,然后就走不下去了。找了2个参考方案,自己做的过程中还又遇到些新的问题,下面做个记录。
- 解法1参考文章
- 解法2参考文章
解法1:追根溯源
-
在 JS 代码中追踪到 Payload 赋值位置
-
追踪 Sign 函数,调用的 wasm 代码。这里需要记录一下 var55 和 var56 的值
-
回到 JS 代码,选中 getStringFromWasm0(r0, r1) 部分,可以看到最终的 sign 已经生成。此时在 console 端口中输入 getStringFromWasm0(1114192, 31) —— 1114192和31 是 var55 和 var56 的值,1个代表内存地址,1个代表偏移量,表示从这个地址取这个长度的字符 —— 可以看到后面加了1串固定的字符串,也就是传说中的 md5 的盐?
- 到这里也就明白了,sign函数是以 页码,时间戳作为输入,然后加上了固定字符串(盐),拼接后做了 md5 加密。下面是Python 实现代码
# !/usr/bin/env python3
# _*_ coding:utf-8 _*_
"""
@File : match_20_normal.py
@Project : S044_YuanRenXue
@CreateTime : 2023/4/15 18:00
@Author : biaobro
@Software : PyCharm
@Last Modify Time : 2023/4/15 18:00
@Version : 1.0
@Description : None
"""
from urllib.parse import urlencode
import hashlib
import requests
import time
"""
关键的JS代码,得到Payload
t = Date.parse(new Date());
var list = {
"page": window.page,
"sign": window.sign(window.page + '|' + t.toString()),
"t": t,
};
"""
def get_sign(page, ts):
sign = hashlib.md5((str(page) + "|" + str(ts) + 'D#uqGdcw41pWeNXm').encode()).hexdigest()
return sign
all_total = 0
for page in range(1, 6):
ts = int(time.time()) * 1000
payload = {
'page': page,
'sign': get_sign(page, ts),
't': ts
}
url = "https://match.yuanrenxue.cn/api/match/20?" + urlencode(payload)
print(url)
headers = {
'user-agent': 'yuanrenxue.project',
'cookie': 'sessionid=ntkvbzoagc6tpauaugwk3b0jdrdbuba9'
}
resp = requests.get(url, headers=headers)
# print(resp.text)
datas = resp.json()['data']
print(datas)
p_total = 0
for data in datas:
p_total = p_total + data['value']
all_total = all_total + p_total
print(all_total)
解法2:举重若轻
- 这个解法不明白原理,但结合Git 作者给的 Demo 和 参考文章,还是照猫画虎搞出来了。Git 作者提供的代码,无法直接获取字典对象。参考文章提供的代码也需要稍微调整。
- 这个解法需要准备2个东西,1个是在浏览器 Console 跑的JS代码,1个是 Git 作者提供的 Win Exe 程序。
- 启动Exe 程序,建议在文件夹中右键选择“在终端中代开”,然后敲入 win64-localhost.exe 运行。
- 打开浏览器找到第20题页面打开,在Console 端口中输入以下 JS 代码,然后回车
function Hlclient(wsURL) {
this.wsURL = wsURL;
this.handlers = {};
this.socket = {};
if (!wsURL) {
throw new Error('wsURL can not be empty!!')
}
this.connect()
this.handlers["_execjs"]=function (resolve,param){
var res=eval(param)
if (!res){
resolve("没有返回值")
}else{
resolve(res)
}
}
}
Hlclient.prototype.connect = function () {
console.log('begin of connect to wsURL: ' + this.wsURL);
var _this = this;
try {
this.socket["ySocket"] = new WebSocket(this.wsURL);
this.socket["ySocket"].onmessage = function (e) {
try{
let blob=e.data
blob.text().then(data =>{
_this.handlerRequest(data);
})
}catch{
console.log("not blob")
_this.handlerRequest(blob)
}
}
} catch (e) {
console.log("connection failed,reconnect after 10s");
setTimeout(function () {
_this.connect()
}, 10000)
}
this.socket["ySocket"].onclose = function () {
console.log("connection failed,reconnect after 10s");
setTimeout(function () {
_this.connect()
}, 10000)
}
};
Hlclient.prototype.send = function (msg) {
this.socket["ySocket"].send(msg)
}
Hlclient.prototype.regAction = function (func_name, func) {
if (typeof func_name !== 'string') {
throw new Error("an func_name must be string");
}
if (typeof func !== 'function') {
throw new Error("must be function");
}
console.log("register func_name: " + func_name);
this.handlers[func_name] = func;
return true
}
//收到消息后这里处理,
Hlclient.prototype.handlerRequest = function (requestJson) {
var _this = this;
var result=JSON.parse(requestJson);
//console.log(result)
if (!result['action']) {
this.sendResult('','need request param {action}');
return
}
var action=result["action"]
var theHandler = this.handlers[action];
if (!theHandler){
this.sendResult(action,'action not found');
return
}
try {
if (!result["param"]){
theHandler(function (response) {
_this.sendResult(action, response);
})
}else{
var param=result["param"]
try {
param=JSON.parse(param)
}catch (e){
console.log("")
}
theHandler(function (response) {
_this.sendResult(action, response);
},param)
}
} catch (e) {
console.log("error: " + e);
_this.sendResult(action+e);
}
}
Hlclient.prototype.sendResult = function (action, response) {
var responseJson;
if (typeof response == 'string') {
try {
responseJson = JSON.parse(response);
} catch (e) {
responseJson = {};
responseJson['data'] = response;
}
} else if (typeof response == 'object') {
responseJson = response;
} else {
responseJson = {};
responseJson['data'] = response;
}
if (Array.isArray(responseJson)) {
responseJson = {
data: responseJson,
code: 0
}
}
if (responseJson['code']) {
responseJson['code'] = 0;
} else if (responseJson['status']) {
responseJson['status'] = 0;
} else {
responseJson['status'] = 0;
}
var responseText = JSON.stringify(responseJson);
this.send(action + atob("aGxeX14") + responseText);
}
// 上面是用于建立环境的源码
// 注入环境后连接通信
// var demo = new Hlclient("ws://127.0.0.1:12080/ws?group=zzz&name=hlg");
// 连接通信
var client = new Hlclient("ws://127.0.0.1:12080/ws?group=match20&name=yuanrenxue");
// demo_001
// 注册一个方法 第一个参数hello为方法名,
// 第二个参数为函数,resolve里面的值是想要的值(发送到服务器的)
client.regAction("hello1", function (resolve) {
//这样每次调用就会返回“好困啊+随机整数”
var Js_sjz = "好困啊"+parseInt(Math.random()*1000);
resolve(Js_sjz);
})
// 访问接口,获得js端的返回值
// http://localhost:12080/go?group=zzz&name=hlg&action=hello
// demo_002
//写一个传入字符串,返回base64值的接口(调用内置函数btoa)
client.regAction("hello2", function (resolve,param) {
//这样添加了一个param参数,http接口带上它,这里就能获得
var base666 = btoa(param)
resolve(base666);
})
// demo_003
//假设有一个函数 需要传递两个参数
function hlg(User,Status){
return User+"说:"+Status;
}
client.regAction("hello3", function (resolve,param) {
//这里还是param参数 param里面的key 是先这里写,但到时候传接口就必须对应的上
res=hlg(param["user"],param["status"])
resolve(res);
})
// url = "http://localhost:12080/go"
// 第二个参数为函数,resolve里面的值是想要的值(发送到服务器的)
// param是可传参参数,可以忽略
client.regAction("ShiGuang", function (resolve, param) {
t = Date.parse(new Date());
var list = {
"page": String(param),
"sign": window.sign(String(param) + '|' + t.toString()),
"t": t,
}
resolve(list)
})
- 准备好环境后,就可以写Python 代码进行请求了。以下是 Python 代码
# !/usr/bin/env python3
# _*_ coding:utf-8 _*_
"""
@File : match_20_jsrpc.py
@Project : S044_YuanRenXue
@CreateTime : 2023/4/15 23:06
@Author : biaobro
@Software : PyCharm
@Last Modify Time : 2023/4/15 23:06
@Version : 1.0
@Description : None
"""
import ast
import requests
def get_params(page):
params = {
"group": "match20",
"name": "yuanrenxue",
"action": "ShiGuang",
"param": str(page)
}
try:
res = requests.post("http://localhost:12080/go", params=params)
res = res.json()
print(res)
# 这个写法是把
# '{"page":"1","sign":"df47c82c991462661cfc3642369039a3","t":1681818656000,"status":0}'
# 转换成
# {'page': '1', 'sign': 'df47c82c991462661cfc3642369039a3', 't': 1681818656000, 'status': 0}
# 否则直接按json 或 字典方式是读不出来的
params = ast.literal_eval(res.get("data"))
print(params)
return params
except:
return False
get_params(1)
- 运行结果。现在拿到了 sign,再获取页面上的数据就不是问题了。