ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

某东联盟h5st逆向分析实战:用opencode+mcp+skills搭建可复现调试链路

某东联盟h5st逆向分析实战:用opencode+mcp+skills搭建可复现调试链路 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_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodel_chat如果后续要把这套链路用于长期编码或 Agent 场景可以了解 Coding Plan它更适合持续性的代码生成与调试任务。Coding Plan 入口https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding_plan真正接入前需要在控制台创建 API Key这是所有调用的凭证。控制台入口https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentconsoleAPI Keys 管理入口https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi_keys接入文档入口https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc拿到 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-size4096 } } }, 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.pySKILL.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 并验证补环境完成后调用签名方法生成 h5stfunction 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, encodingutf-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, headersheaders, paramsparams, timeout20, impersonatechrome142) 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_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodel_chat需要重新生成 Key 或查看接入文档时走下面两个入口。API Keys 管理入口https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi_keys接入文档入口https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc6. 把调试链路固化下来这套链路跑通之后最有价值的动作是把补环境逻辑和签名输入构造沉淀到 skills 目录里。下次遇到 h5st 版本更新只需要替换env_patch.js重新跑verify.py验证不用从头跟栈。opencode 负责承载代码mcp 负责执行动作skills 负责固化经验三者配合能把逆向分析的效率提上来。长期做这类编码和 Agent 任务的话Coding Plan 会比按次调用更合适。Coding Plan 入口https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding_plan最后提醒一句本文内容仅用于本地学习和技术研究请勿用于其他用途。逆向分析的价值在于理解前端签名机制和工程化调试方法把流程跑通、把经验固化比拿到一个能用的参数更有意义。
返回列表