For $ARGUMENTS , execute the environment patching plan.
Prerequisites: the encryption entry point is known (module ID, function name, script it resides in). If not yet located, first use /find-crypto-entry。
Core concepts
env_core.jsis the engine: provides utility functions (setFuncNative/setObjNative/getNativeProto/wrapFunc/monitor/createProxy) and diagnostic reports. The engine contains no environment stubs, and is not modified after being copied.run.jsis the strategy: all environment stubs, patches, and loading logic are written in run.js. After each diagnostic round, only run.js is modified.browser-stubs.mdis the reference directory: fetch stub code as needed according to the UNDEFINED/ERRORS that appear in the diagnostic report.
Iron rules
- Do not modify the original JS —
source/: files underenv/(main.js / sdk.js / bytecode.js) are generated, they are final and are no longer modified. All patches are written in run.js / sign.js. - Must require env_core.js — rewriting utility functions such as setFuncNative / setObjNative / createProxy in run.js is prohibited. All tools are imported via
const env = require('./env_core'), then callenv.setFuncNative()/env.createProxy(), etc. env_core.js is not modified after being copied. If additional helpers are needed, write them at the top of run.js, but do not reimplement functionality that env_core already has. - First analyze the VMP entry parameters — the dependency array of the VMP entry determines the boundaries of environment patching.
typeof chrome !== "undefined" ? chrome : undefinedchecksglobalThis, notwindow. You must useObject.defineProperty(global, name, ...)to synchronize. - The loading order is fatal — VMP initializes and reads the environment at the
require()ofinstant, and afterward it cannot be changed. The code in run.js must strictly follow this order:const env = require('./env_core')— get the tools (first line)- Build stub objects (document / navigator / location, etc.)
env.init({ window, document, navigator, location })— block Node leaks + mount globals- Additional environment completion — synchronize globals such as performance, chrome, etc.
require('./main.js')— load the target JS- Test the signature
- Format validation takes priority over request validation — the signature length/prefix being inconsistent with the browser = degraded; even an HTTP 200 is a false positive.
Template
| File | Purpose |
|---|---|
${CLAUDE_SKILL_DIR}/scripts/env_core.js |
Toolset + Proxy engine + diagnostic report |
${CLAUDE_SKILL_DIR}/scripts/webpack_runtime.js |
Minimal webpack runtime |
Copy to project env/ after,env_core.js is no longer modified。
Execution flow
Step 1: Set up the project structure
<project>/
├── source/ # 原始 JS(下载,不修改)
├── env/ # 运行环境
│ ├── env_core.js # 从模板复制,不改
│ ├── main.js # 目标 JS 副本(或 sdk.js + bytecode.js)
│ ├── run.js # 加载器 + 环境存根 + 测试
│ └── sign.js # 签名接口(最后封装)
├── python/ # 验证脚本
└── docs/progress.md
Determine the scenario(read reference as needed):
| Scenario | JS file | Reference document |
|---|---|---|
| Single-file SDK | Copy to env/main.js |
— |
| SDK + bytecode separation | env/sdk.js + env/bytecode.js |
references/multi-file.md |
| webpack bundle | Extract the module to env/main.js |
references/webpack.md |
| Runtime dynamic loading | curl download to source/, copy to env/ |
references/dynamic-loading.md |
Step 2: First run
Write a minimal run.js containing only the necessary stubs:
const env = require('./env_core');
const _process = process; // init() 会隐藏 process,提前保留引用
// ① 构建最小存根(从 browser-stubs.md 取用 document/navigator/location)
const fakeDocument = { /* ... */ };
const fakeNavigator = { /* ... */ };
const fakeLocation = { /* ... */ };
// ② 组装 window
const fakeWindow = {
document: fakeDocument,
navigator: fakeNavigator,
location: fakeLocation,
// ... 按需补充
};
fakeWindow.window = fakeWindow;
fakeWindow.self = fakeWindow;
fakeWindow.top = fakeWindow;
fakeWindow.parent = fakeWindow;
fakeWindow.globalThis = fakeWindow;
// ③ 初始化(Node 泄露阻断 + 全局挂载)
env.init({
window: env.createProxy(fakeWindow, 'window', 0),
document: env.createProxy(fakeDocument, 'document', 0),
navigator: env.createProxy(fakeNavigator, 'navigator', 0),
location: env.createProxy(fakeLocation, 'location', 0),
});
// ④ 额外全局同步
// global.chrome = window.chrome; // 如需
// ⑤ 加载目标 JS
require('./main.js');
// ⑥ 测试
console.log('签名函数:', typeof window.签名函数名);
Run node env/run.js, read the diagnostic report.
Step 3: Diagnostic loop (core)
After each round of running, read the diagnostic report and handle it according to the following decision tree:
诊断报告
├── [HANG] 进程卡死/无输出 → 参考 references/anti-debug.md
│ ├── setInterval debugger 死循环 → hook setInterval 过滤
│ ├── eval/Function 动态生成 debugger → hook eval + Function 剥离
│ └── 同步 while(true) → 定位源码,run.js 中 patch 全局
│
├── [ERRORS] → 必须立即修复
│ ├── TypeError: xxx is not a function → 补对应方法
│ ├── xxx is not defined → 补全局变量/构造函数
│ └── Cannot read property of undefined → 补中间对象
│
├── [UNDEFINED] → 逐项处理
│ ├── 先用 evaluate_script 从浏览器获取真实值
│ ├── 浏览器也是 undefined → 跳过(标记为已确认)
│ └── 浏览器有值 → 补到 run.js(从 browser-stubs.md 取用规范写法)
│
└── ERRORS = 0 && UNDEFINED 全部已确认 → 进入签名格式校验
├── 签名长度/前缀与浏览器一致 → Step 4
└── 签名不一致 → 深度排查
├── monitor() 包装关键对象,追踪属性访问
├── 全局错误拦截:process.on('uncaughtException')
├── 对照 references/node-detection.md 逐项检查
├── 怀疑指纹检测 → references/fingerprint-consistency.md(跨层指纹自测清单)
└── 所有外部 hook 均无异常 → references/limitations.md
Operations per round:
- Read the diagnostic report
- From
browser-stubs.mdtake the corresponding stub code, and useenv_core.js's tools to write - Only modify run.js
- Run again and return to Step 1
Step 4: Encapsulation verification
sign.js:
const env = require('./env_core');
// ... 环境构建(与 run.js 相同)...
require('./main.js');
module.exports = function sign(url, data) {
// 调用签名函数,返回结果
return window.签名函数名(url, data);
};
Verification order:
- Format verification — signature length and prefix match the browser
- Request verification — HTTP 200 + business data returned
Anti-patterns
- Do not modify the original JS —
source/files under it are read-only,env/the JS copy under it is final once generated. All patches are written in run.js. - Do not rewrite env_core's utility functions in run.js —
setFuncNative/createProxyetc. must be imported from env_core; do not reimplement them. - Do not patch the environment after requiring the target JS — the VMP is initialized at the moment of
require(), and patching the environment afterward is ineffective. - Do not treat HTTP 200 as a success indicator — if the signature format is wrong (length/prefix does not match the browser), even a 200 is a false positive.
- Do not fill in all stubs at once — add them gradually according to the diagnostic report, modifying only run.js each round, and verifying by running each round.
Completion criteria
补环境结果:
- 诊断报告:ERRORS = 0, UNDEFINED 全部已确认
- 签名格式:与浏览器输出一致(长度、前缀匹配)
- 签名接口:sign.js 导出函数可直接调用
- 请求验证:HTTP 200 + 业务数据返回
References
Read as needed when encountering specific scenarios:
| File | When to read |
|---|---|
references/anti-debug.md |
The process is stuck/extremely slow, or the target is known to have debugger anti-debugging |
references/browser-stubs.md |
When stubs need to be added, to find the canonical way to write them |
references/node-detection.md |
Signature degradation (length/prefix does not match the browser) |
references/fingerprint-consistency.md |
The signature is correct but the request is intercepted; fingerprint detection is suspected; or Canvas/WebGL/AudioContext stubs need to be added |
references/multi-file.md |
SDK + bytecode separation, multi-interpreter scenarios |
references/dynamic-loading.md |
Encrypted code is dynamically loaded from an API |
references/webpack.md |
Extracting webpack bundle modules |
references/storage-tracing.md |
Suspecting that localStorage has a control-flow switch |
references/limitations.md |
When all external hooks are ineffective, to understand the ceiling of the approach |