☰
某东联盟h5st逆向分析实战:用opencode+mcp+skills搭建可复现调试链路
2026/9/27 17:33:03 网站建设 项目流程

1. 某东联盟 h5st 逆向分析:从抓包定位到参数还原的完整链路

某东联盟的 h5st 参数是接口请求里绕不开的一道门槛,它由前端 JS 动态生成,长度会随版本变化,直接复制浏览器里的值往往几分钟就失效。这篇内容聚焦 h5st 逆向分析场景,把 opencode 当作编码环境、mcp 当作工具调用通道、skills 当作能力封装,串起从抓包定位到参数还原的完整调试链路。适合已经会看浏览器开发者工具、能读懂基础 JS、想把这套流程工程化复现的同学。我会交付可复制的 opencode 配置骨架、mcp 接入片段和 skills 目录结构,并给出逐步验证动作,让你在本地把 h5st 逆向分析流程跑通。整个过程不涉及任何网络访问工具,全部在本地编码环境里完成。

h5st 的本质是前端把 functionId、appid、body 等字段拼成一个签名输入,交给一个混淆过的构造函数生成签名串。逆向分析要解决三件事:找到生成入口、把混淆代码扣出来、在 Node 环境里补齐浏览器环境让它能跑。opencode 负责承载这些代码和调试脚本,mcp 负责把「执行 JS、读取文件、跑 Python」这些动作标准化,skills 则把重复出现的补环境套路固化下来。三者配合,能把原本靠手工复制粘贴的流程变成可复现的调试链路。

2. TaoToken 前置:模型与密钥准备

逆向分析过程中需要模型帮忙读混淆代码、解释调用栈、生成补环境骨架。我这边用的是 TaoToken 提供的模型对话能力,接入 DeepSeek 系列模型做代码理解。你可以先到模型对话页面体验一下对话效果,确认模型对 JS 混淆代码的理解程度符合预期。

模型对话入口:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model_chat

如果后续要把这套链路用于长期编码或 Agent 场景,可以了解 Coding Plan,它更适合持续性的代码生成与调试任务。

Coding Plan 入口:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding_plan

真正接入前需要在控制台创建 API Key,这是所有调用的凭证。

控制台入口:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=console

API Keys 管理入口:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api_keys

接入文档入口:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc

拿到 Key 之后,把它写进 opencode 的配置里,模型调用就走通了。这里要提醒一句,Key 只放在本地配置文件,不要提交到任何公开仓库。

3. 可复制配置:opencode + mcp + skills 骨架

3.1 opencode 配置骨架

opencode 的配置核心是声明模型提供方和 mcp 服务。下面是一个可复制的骨架,把 TaoToken 作为模型提供方,把 js_reverse_mcp 作为工具通道。

{ "provider": { "taotoken": { "type": "openai-compatible", "baseURL": "https://taotoken.net/api", "apiKey": "你的_API_KEY", "models": { "deepseek-v4-pro": { "name": "deepseek-v4-pro" } } } }, "mcp": { "js_reverse": { "command": "node", "args": ["./mcp/js_reverse_mcp/index.js"], "env": { "NODE_OPTIONS": "--max-old-space-size=4096" } } }, "skills": { "dir": "./skills" } }

这段配置做了三件事:把模型请求指向 TaoToken 的 API 地址,把 js_reverse_mcp 注册为可调用的工具服务,把 skills 目录挂载进来。baseURL 用 https://taotoken.net/api 即可,不需要额外参数。

3.2 mcp 接入片段

mcp 服务本身是一个 Node 进程,通过标准输入输出和 opencode 通信。js_reverse_mcp 的核心能力是执行 JS 片段、读取本地文件、返回执行结果。下面是一个最小可用的 mcp 服务片段,暴露一个run_js工具。

// mcp/js_reverse_mcp/index.js const readline = require("readline"); const vm = require("vm"); const fs = require("fs"); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false }); function handleRequest(req) { if (req.method === "tools/call" && req.params.name === "run_js") { const code = req.params.arguments.code; const sandbox = { window: {}, document: {}, console }; try { vm.createContext(sandbox); const result = vm.runInContext(code, sandbox, { timeout: 5000 }); return { content: [{ type: "text", text: String(result) }] }; } catch (e) { return { content: [{ type: "text", text: "ERROR: " + e.message }] }; } } if (req.method === "tools/list") { return { tools: [{ name: "run_js", description: "在沙箱中执行 JS 代码片段", inputSchema: { type: "object", properties: { code: { type: "string" } }, required: ["code"] } }] }; } return { error: "unknown method" }; } rl.on("line", (line) => { if (!line.trim()) return; const req = JSON.parse(line); const resp = handleRequest(req); process.stdout.write(JSON.stringify(resp) + "\n"); });

这个片段把 JS 执行能力封装成工具,opencode 在调试时可以直接调用run_js来验证补环境是否成功,不用反复切终端。

3.3 skills 目录结构

skills 是把重复流程固化的地方。针对 h5st 逆向分析,我整理成下面这个目录结构。

skills/ h5st_reverse/ SKILL.md env_patch.js sign_input.js verify.py

SKILL.md描述这个 skill 的用途和调用方式,env_patch.js放补环境逻辑,sign_input.js放签名输入构造逻辑,verify.py放最终验证脚本。这样每次遇到新的 h5st 版本,只需要替换env_patch.js里的环境补丁,其余流程不变。

4. 逐步验证:从抓包到参数还原

4.1 抓包定位生成入口

打开某东联盟页面,在开发者工具里对目标接口下断点。关键字搜索h5st,刷新页面后命中断点。观察调用栈,找到x.sign(w)这一行,它位于case 0的最后。继续跟进会发现程序走到case 12,说明x.sign(w)的返回值里包含 h5st。通过跟栈可以定位到this._$sdnmd(_$fQ)这一块,_$fQ是签名输入对象。

这里有个关键点:_$fP是原型对象上的方法,不能直接调用,需要通过构造器创建实例。搜索window.ParamsSign可以找到赋值位置,再搜索new window.ParamsSign能找到实例化代码,形如x = new window.ParamsSign({ appId: "586ae" })。这一步确认了签名入口和 appId。

4.2 扣代码与补环境

把js_security_v3_0.1.5.js里的代码整体扣下来。分析后发现 functionId 和 appid 可以写死,body 对应的是 SHA256 加密,用crypto-js就能解决。签名输入构造如下:

const CryptoJs = require("crypto-js"); function buildSignInput(page) { const body = { funName: "getSkuByMaterialId", page: { pageNo: page, pageSize: 60 }, param: { materialId: 13884, requestScene: 0, requestExtFields: ["shopInfo", "orientations"] }, clientPageId: "jingfen_pc" }; return { functionId: "unionSearchRecommend", appid: "unionpc", body: CryptoJs.SHA256(JSON.stringify(body)).toString() }; }

补环境用 Proxy 拦截全局对象,把window、document、location、navigator、history、screen等挂上代理,避免混淆代码访问时直接报错。

function get_enviroment(proxy_array) { for (let i = 0; i < proxy_array.length; i++) { const handler = { get: function (target, property) { return target[property]; }, set: function (target, property, value) { target[property] = value; return true; } }; try { eval(proxy_array[i] + " = new Proxy(" + proxy_array[i] + ", handler)"); } catch (e) { eval(proxy_array[i] + " = {}; " + proxy_array[i] + " = new Proxy(" + proxy_array[i] + ", handler)"); } } } const proxy_array = ["window", "document", "location", "navigator", "history", "screen"]; get_enviroment(proxy_array);

补环境时要重点关注undefined,因为前面的环境为空可能导致后面的环境报错。先加载01_jd.js,再补环境,最后加载02_jdments.js,顺序不能乱。

4.3 生成 h5st 并验证

补环境完成后,调用签名方法生成 h5st:

function get_h5st(page) { const signInput = buildSignInput(page); const info = new window.ParamsSign({ appId: "586ae" }); const result = info._$sdnmd(signInput); console.log("h5st length:", result.h5st.length); return result.h5st; } console.log(get_h5st(1));

代入数值测试后能拿到结果,但长度是 831 位,而网页上看到的是 1103 位。长度不一致说明补环境还有缺失字段,需要继续排查。不过用这个 831 位的 h5st 去请求,数据居然能获取到,说明服务端对长度有一定容忍度,但为了稳定还是要把缺失字段补齐。

4.4 Python 侧请求验证

用execjs调用 Node 生成的 h5st,再用curl_cffi发请求:

import execjs import time from curl_cffi import requests headers = { "accept": "application/json, text/plain, */*", "origin": "https://union.jd.com", "referer": "https://union.jd.com/", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36", "x-referer-page": "https://union.jd.com/proManager/index", "x-rp-client": "h5_1.0.0" } h5st = execjs.compile(open("03.jdh5st.js", encoding="utf-8").read()).call("get_h5st", 1) params = { "functionId": "unionSearchRecommend", "appid": "unionpc", "_": int(time.time() * 1000), "loginType": "3", "h5st": h5st, "body": '{"funName":"getSkuByMaterialId","page":{"pageNo":1,"pageSize":60},"param":{"materialId":13884,"requestScene":0,"requestExtFields":["shopInfo","orientations"]},"clientPageId":"jingfen_pc"}' } response = requests.get("https://api.m.jd.com/api", headers=headers, params=params, timeout=20, impersonate="chrome142") print(response.text)

运行后数据可以正常获取,说明整条链路是通的。长度差异的问题可以在后续迭代中通过对比浏览器生成结果逐步补齐。

5. 本篇常见错排查

补环境阶段最容易遇到的是ReferenceError: xxx is not defined。这类报错通常是因为某个全局对象没挂上代理,或者挂载顺序不对。排查方法是把报错的对象名加到proxy_array里,重新跑一遍。

第二个常见问题是 h5st 长度不一致。831 位和 1103 位的差异,往往来自环境字段缺失,比如navigator.userAgent、screen.width这类值没补全。可以对比浏览器里window.ParamsSign实例化时的入参,看是否有遗漏字段。

第三个问题是execjs调用超时。这通常是因为补环境代码里有死循环,或者vm沙箱的 timeout 设置太短。把 timeout 调到 10000 毫秒,同时检查混淆代码里是否有while(true)之类的逻辑。

第四个问题是请求返回签名错误。这时候先确认_时间戳和 h5st 是同一批次生成的,再确认body字段和签名输入里的 body 完全一致。任何一处不一致都会导致签名校验失败。

如果排查过程中需要模型帮忙读混淆代码,可以回到模型对话页面把代码片段贴进去问。

模型对话入口:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model_chat

需要重新生成 Key 或查看接入文档时,走下面两个入口。

API Keys 管理入口:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api_keys

接入文档入口:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc

6. 把调试链路固化下来

这套链路跑通之后,最有价值的动作是把补环境逻辑和签名输入构造沉淀到 skills 目录里。下次遇到 h5st 版本更新,只需要替换env_patch.js,重新跑verify.py验证,不用从头跟栈。opencode 负责承载代码,mcp 负责执行动作,skills 负责固化经验,三者配合能把逆向分析的效率提上来。

长期做这类编码和 Agent 任务的话,Coding Plan 会比按次调用更合适。

Coding Plan 入口:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding_plan

最后提醒一句,本文内容仅用于本地学习和技术研究,请勿用于其他用途。逆向分析的价值在于理解前端签名机制和工程化调试方法,把流程跑通、把经验固化,比拿到一个能用的参数更有意义。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询