with one click
js-env-patcher
// JS逆向沙箱补环境。给定一个混淆 JS 文件,自动完成"执行→诊断缺失环境→补环境→循环直到跑通"的完整流程。 当用户给出一个 JS 文件要求补环境、逆向分析、在沙箱中执行、或提到 a_bogus/JSVMP/指纹/反检测时触发此 skill。
// JS逆向沙箱补环境。给定一个混淆 JS 文件,自动完成"执行→诊断缺失环境→补环境→循环直到跑通"的完整流程。 当用户给出一个 JS 文件要求补环境、逆向分析、在沙箱中执行、或提到 a_bogus/JSVMP/指纹/反检测时触发此 skill。
| name | js-env-patcher |
| description | JS逆向沙箱补环境。给定一个混淆 JS 文件,自动完成"执行→诊断缺失环境→补环境→循环直到跑通"的完整流程。 当用户给出一个 JS 文件要求补环境、逆向分析、在沙箱中执行、或提到 a_bogus/JSVMP/指纹/反检测时触发此 skill。 |
你是 JS 逆向工程专家。你的任务是帮助用户在 Node.js VM 沙箱中成功执行混淆 JS 代码,通过迭代诊断和补全缺失的浏览器环境来实现。
Skill 目录: $SKILL_DIR/
诊断工具: $SKILL_DIR/scripts/diagnose.js
env 模块: $SKILL_DIR/env/(skill 自包含,不依赖外部框架)
参考文档: $SKILL_DIR/references/
┌─────────────────────────────────────────────────┐
│ 0. 检查 SDK init 参数(独立 SDK 必做) │
│ → 判断目标 JS 是否为独立加载的 SDK │
│ → 如果是,用 js-reverse-mcp 在浏览器中抓取 │
│ init/setup 函数的调用参数 │
├─────────────────────────────────────────────────┤
│ 1. 首次诊断(不加载任何 env 模块) │
│ → 获取 undefinedPaths 列表 │
├─────────────────────────────────────────────────┤
│ 2. 根据 undefinedPaths 选择 env 模块 │
│ → 查 references/env-modules.md 做前缀匹配 │
│ → 按 references/loading-order.md 排序 │
├─────────────────────────────────────────────────┤
│ 3. 带模块重新诊断 │
│ → 检查 undefinedPaths 是否减少 │
│ → 如果有新错误,分析原因 │
├─────────────────────────────────────────────────┤
│ 4. 处理剩余 undefinedPaths │
│ → 框架模块已覆盖 → 检查加载顺序问题 │
│ → 框架模块未覆盖 → 写 ai-generated 补丁 │
│ → 需要真实浏览器参数 → 用 js-reverse-mcp 采集 │
├─────────────────────────────────────────────────┤
│ 5. 循环 3-4 直到成功或稳定 │
└─────────────────────────────────────────────────┘
适用条件:目标 JS 是独立加载的安全 SDK(从独立 CDN 加载,不和业务代码打包在一起)。
判断标志(满足任一即需要检查):
sign() 函数,而是通过 XHR/fetch hook 拦截机制工作init/setup/config 等初始化方法操作方法:
SDK.init(params) 的位置(通常在胶水层/调度层)set_breakpoint_on_text("SDK.init(", urlFilter: "sdk-glue")
→ 刷新页面 → evaluate_script 读取参数
docs/progress.mdinit 参数的常见内容:
aid — 应用标识paths / urls / patterns — URL 匹配规则(决定哪些请求需要签名)boe / debug — 调试开关⚠️ 教训:不传正确的 init 参数,SDK 可能加载成功、hook 生效,但签名逻辑被静默跳过(无报错),导致补环境看起来一切正常却始终拿不到签名值,极难排查。
node $SKILL_DIR/scripts/diagnose.js <目标脚本.js>
解读输出:
success: true + 无 undefinedPaths → 脚本已可运行,可能不需要补环境success: false + error 包含 "is not defined" → 缺少全局对象undefinedPaths 列表 → 需要补全的属性$SKILL_DIR/references/env-modules.md. 前的部分)$SKILL_DIR/references/loading-order.md 中的标准顺序排列dom/elements.js 必须加 dom/document.js)node $SKILL_DIR/scripts/diagnose.js \
--env bom/navigator.js,bom/location.js,bom/crypto.js,dom/document.js \
<目标脚本.js>
比较前后结果:
当 undefinedPath 不在任何现有模块中时,创建补丁文件:
// 文件: env/ai-generated/<属性名>_<timestamp>.js
(() => {
'use strict';
// 补全 window.someProperty
Object.defineProperty(window, 'someProperty', {
value: /* 合理的默认值 */,
writable: false,
configurable: true,
enumerable: true
});
})();
补丁规范:
'use strict'Object.defineProperty 设置属性<对象>_<属性>_<Date.now()>.jsenv/ai-generated/ 目录当需要真实浏览器指纹值时(如特定网站的 navigator.userAgent、document.cookie 等),使用 js-reverse-mcp 的浏览器操作工具:
mcp__js-reverse__new_page 打开目标网站mcp__js-reverse__evaluate_script 采集需要的属性值:
() => ({
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
// ... 根据需要采集
})
对于指纹对抗场景,参考框架的 mock 预设:
预设配置参考框架的 config/mock-rules.json(如已安装外部框架)
继续循环的条件:
停止循环的条件:
success: true 且 undefinedPaths 为空或仅剩无关项Symbol(*) 开头)⚠️ success: true 不等于"能用":diagnose.js 的 success 只代表脚本加载没报错,不代表目标功能(签名、加密等)可用。必须进入步骤 6 做功能验证。
向用户报告的时机:
success: true 之后,必须验证目标功能是否真正可用。这一步在项目的 env/run.js 中完成,不是在 diagnose.js 中。
验证流程:
env/run.js,加载与 diagnose.js 相同的 env 模块 + 目标脚本针对 hook 型 SDK(如 bdms.js)的验证要点:
加载顺序(严格):
env 模块 → fake XMLHttpRequest → 目标 JS → hook 捕获代码 → init(配置) → 触发 XHR
↑ ↑
必须在目标 JS 之前 必须在目标 JS 之后
(目标 JS 要 hook 它) (目标 JS 可能重定义相关 API)
URLSearchParams.append hook):在目标 JS 之后注入(目标 JS 可能包含 polyfill 覆盖原生 API)open → setRequestHeader → send 流程常见的"加载成功但功能不工作"原因:
| 现象 | 原因 | 排查方法 |
|---|---|---|
| 签名值为 undefined | 缺少 crypto.js / performance.js | 检查 env 模块列表 |
| hook 执行了但不签名 | 缺少 init 参数(尤其是 paths) | 浏览器断点抓 init 参数(步骤 0) |
| 捕获 hook 没命中 | hook 被目标 JS 的 polyfill 覆盖 | 将 hook 移到目标 JS 加载之后 |
| JSVMP 静默失败 | 内部 try-catch 吞错误 | 在 JSVMP 错误出口 f=3,void(l= 注入 console.warn |
| 签名长度不对 | 环境指纹与真实浏览器有差异 | 用 js-reverse-mcp 采集真实浏览器参数覆盖默认值 |
步骤 6 确认签名值能生成后,需要封装为可调用的服务,并用实际请求验证签名是否被服务器接受。
7a. sign.js — 签名接口模块
从 env/run.js 提取签名逻辑,封装为可导出的函数:
// env/sign.js
// ... 与 run.js 相同的环境构建 + 目标 JS 加载 ...
function sign(url) {
window.__captured_a_bogus = null;
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.setRequestHeader('Accept', 'application/json, text/plain, */*');
xhr.send(null);
return window.__captured_a_bogus;
}
module.exports = sign;
// 命令行模式
if (require.main === module) {
const url = process.argv[2] || process.env.SIGN_URL;
if (!url) { console.error('Usage: node sign.js <url>'); process.exit(1); }
process.stdout.write(sign(url) || '');
}
7b. server.js — HTTP 中间层
通过 HTTP 服务调用签名,避免 subprocess 的环境差异问题:
// env/server.js
const http = require('http');
const sign = require('./sign');
const PORT = 3456;
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/sign') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
try {
const { url } = JSON.parse(body);
const result = sign(url);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ result: result || '', length: (result || '').length }));
} catch (e) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: e.message }));
}
});
} else { res.writeHead(404); res.end('Not Found'); }
});
server.listen(PORT, () => console.log(`sign server on http://localhost:${PORT}/sign`));
7c. Python 验证脚本
# python/verify.py
from curl_cffi import requests
from urllib.parse import urlencode
SIGN_SERVER = "http://localhost:3456/sign"
def get_sign(url):
resp = requests.post(SIGN_SERVER, json={"url": url}, impersonate="chrome136")
return resp.json().get("result") or None
# headers / cookies / params 从浏览器抓包填入
headers = { "user-agent": "...", "referer": "..." }
cookies = { "ttwid": "...", "odin_tt": "..." } # 必须用 dict
params = { "aid": "6383", "sec_user_id": "...", "msToken": "..." }
base_url = "https://目标域名/api/path/"
full_url = base_url + "?" + urlencode(params)
sign_value = get_sign(full_url)
params["a_bogus"] = sign_value
# ⚠️ params 用 dict 传 params=,cookies 用 dict 传 cookies=
response = requests.get(base_url, headers=headers, cookies=cookies, params=params, impersonate="chrome136")
print(response.status_code, response.json())
验证顺序:
node env/run.js,签名长度与浏览器一致node env/server.js,curl 测试:
curl -X POST localhost:3456/sign -H 'Content-Type: application/json' -d '{"url":"https://..."}'
python python/verify.py,确认服务器返回正常数据⚠️ Python 请求注意事项:
requests.get(base_url, params=params, cookies=cookies) 标准写法urllib.parse.quote(a_bogus) 拼 URL — 编码不一致导致签名校验失败headers["cookie"] — 必须用 dict 传 cookies=watch() 函数);ProxyEnv.js 仅在未指定 --env 时加载(避免 configurable:false 冲突)env/ 目录,如 bom/navigator.js 而非 env/bom/navigator.jsdom/elements.js 必须在 dom/document.js 之后webapi/network.js 必须在 webapi/xhr.js 和 webapi/fetch.js 之后crypto.js 和 performance.js 是关键依赖[ProxyMonitor] 前缀日志是框架监控输出,不是目标脚本的输出success: true ≠ 功能可用:diagnose.js 只验证脚本能加载,不验证签名/加密等功能是否正常工作。必须在 env/run.js 中做功能验证(步骤 6)env 模块 → fake 全局对象 → 目标 JS → hook 捕获代码 → init → 触发undefinedPath 是简单属性(如 navigator.userAgent)→ 对应模块直接覆盖
undefinedPath 是方法(如 document.createElement)→ 需要加载完整的 DOM 模块
undefinedPath 很深(如 window.crypto.subtle.digest)→ 需要加载提供整个对象树的模块
error 包含 "X is not a constructor" → 需要加载定义该类的模块(如 TextEncoder → encoding/textencoder.js)
目标脚本检测 navigator.webdriver、Canvas 指纹等 → 加载模块 + 应用 anti-detect mock 预设