某deepseek提问answer逆向分析,wasm + worker
本文分析了某网站的接口请求参数生成过程。重点研究了请求头中的'x-ds-pow-response'和请求载荷中的'client_stream_id'两个参数。'x-ds-pow-response'通过WebAssembly调用wasm_solve函数生成answer值;'client_stream_id'由当前日期拼接16位随机字符串组成。其他参数如'x-hif-leim'和'chat_sessi
仅供学习使用,如有侵权,联系删除!!!
仅供学习使用,如有侵权,联系删除!!!
url = 'aHR0cHM6Ly9jaGF0LmRlZXBzZWVrLmNvbS8='
来到这个网站随便问点东西,找到需要的接口信息
观察发现请求头里面有一些参数需要分析
请求载荷里面也有两个参数,chat_session_id和client_stream_id需要分析一下。
首先请求头里面的'x-hif-leim'是前面接口返回的,'x-hif-dliq'是localStorage里面的,经过测试发现可以不携带,请求载荷里面的chat_session_id也是前面接口返回的。
那么需要分析的只有'x-ds-pow-response'和'client_stream_id'
首先看看'x-ds-pow-response',直接进行base64解码
可以看到,只有这个answer需要分析,其它的都是前面接口返回的。
搜索定位,打上断点
这里返回的是一个异步,进入this.doSolveChallenge方法
这里有点看不懂,问一下ai
let solveChallenge = async (challengeData, tracker) => {
tracker.info({
name: "powSolveChallengeStart",
message: "Start solving challenge",
payload: {}
})
// 第一套方案:normal(Worker A)
try {
const startTime = performance.now()
const worker = (() => {
if (!window.Worker) {
throw new Error("Worker is not supported")
}
return new Worker(
new URL(n.p + n.u("33614"), n.b),
{ type: "module" }
)
})()
const result = await o(challengeData, worker)
tracker.info({
name: "powSolveChallengeSuccess",
message: "Solved challenge",
payload: {
duration: performance.now() - startTime,
from: "normal"
}
})
return result
} catch (err) {
tracker.error({
name: "powSolveChallengeFailed",
message: "Failed to solve challenge",
payload: tracker.withError(err, {
from: "normal",
info: JSON.stringify(challengeData)
})
})
}
// 第二套方案:fallback(Worker B / js)
try {
const startTime = performance.now()
const worker = (() => {
if (!window.Worker) {
throw new Error("Worker is not supported")
}
return new Worker(
new URL(n.p + n.u("38401"), n.b),
{ type: "module" }
)
})()
const result = await o(challengeData, worker)
tracker.info({
name: "powSolveChallengeSuccess",
message: "Solved challenge",
payload: {
duration: performance.now() - startTime,
from: "js"
}
})
return result
} catch (err) {
tracker.error({
name: "powSolveChallengeFailed",
message: "Failed to solve challenge",
payload: tracker.withError(err, {
from: "js",
info: JSON.stringify(challengeData)
})
})
throw err
}
}
那么我们只需要在第一个await o(e, s)打上断点,进入o方法
又有点看不懂了,再问下ai
const solveWithWorker = async (challenge, worker) => {
// 把 challenge 发给 worker
worker.postMessage({
type: "pow-challenge",
challenge
})
const startTime = performance.now()
// 等 worker 回消息
const workerResult = await new Promise((resolve, reject) => {
worker.onmessage = event => {
// worker 主动返回 error
if (isWorkerError(event)) {
reject(
supportsErrorCause
? new Error("Worker error", { cause: event.data.error })
: event.data.error
)
worker.terminate()
return
}
// worker 返回成功结果
if (isWorkerSuccess(event)) {
resolve(event.data)
worker.terminate()
return
}
}
worker.onerror = err => {
reject(
new Error(
`Worker error: ${err.message} ${err.filename}:${err.lineno}:${err.colno}`
)
)
worker.terminate()
}
})
const endTime = performance.now()
return {
challengeResponse: workerResult.answer,
duration: endTime - startTime
}
}
那么这里就清楚了,t是worker,通过t.postMessage发送数据,这里就单步进入t.postMessage,来到这里了
这里就是这个answer生成的位置了
let o = n.__wbindgen_add_to_stack_pointer(-16)
, h = u(e, n.__wbindgen_export_0, n.__wbindgen_export_1)
, f = i
, l = u(t, n.__wbindgen_export_0, n.__wbindgen_export_1)
, p = i;
n.wasm_solve(o, h, f, l, p, r);
var s = c().getInt32(o + 0, !0)
, a = c().getFloat64(o + 8, !0);
return 0 === s ? void 0 : a
n.wasm_solve里面就是一个wasm文件,全部拿下来,用Js里面的WebAssembly做导出,调用就可以了。
加密的参数也是前面接口返回的,最后返回的a就是我们需要的answer。
接下来还有一个client_stream_id,这个就简单了,20260211代表当前日期,后面拼接了一个随机字符串
import uuid
import datetime
def generate_date_uuid():
"""生成日期前缀的UUID"""
date_prefix = datetime.datetime.now().strftime("%Y%m%d")
random_part = uuid.uuid4().hex[:16] # 直接获取十六进制表示
return f"{date_prefix}-{random_part}"
print(generate_date_uuid())
最后看一下实现吧 
更多推荐



所有评论(0)