Files
hello_js_reverse_skill/references/jsvmp-analysis.md
T
白夜 8cbaa8b704 feat(v2.6.0): signature-based anti-bot compatibility + MCP v0.5.0 alignment
- Add top-level chapter: anti-bot type classification (signature/behavioral/obfuscation)
- Add observer effect warning for pre_inject_hooks on signature-based anti-bot
- Add hook_jsvmp_interpreter mode parameter (proxy/transparent)
- Update instrument_jsvmp_source AST mode: MCP-side esprima (zero CDN dependency)
- Add fallback_on_error parameter and last_mode_used status field
- Add four-axe applicability labels per anti-bot type
- Rewrite scenario 9 (anti-detection sites) by anti-bot type
- Add scenario 11 signature-type pre-check
- Add observer effect troubleshooting section
- Add experience rules 28-32
- Add AST health diagnostics to jsvmp-source-instrumentation.md
- Add transparent mode examples to mcp-cookbook.md
- Add anti-bot type column to cases/README.md
- Create CHANGELOG_v2.6.0.md
2026-04-18 01:26:56 +08:00

640 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# JSVMP 虚拟机保护分析指南
## 什么是 JSVMP
JSVMP(JavaScript Virtual Machine Protection)是一种高级 JS 代码保护技术,将原始 JS 源码编译为自定义字节码,运行时由内嵌的虚拟机解释器逐条执行。这意味着:
- 原始代码逻辑不再以 JS AST 形式存在,无法通过传统反混淆手段还原
- 代码以字节码数组 + 解释器循环的形式运行
- 常见于瑞数、极验、某数等商业级反爬方案
**核心原则:不反编译字节码,用行为追踪法(Hook / 插桩 / 日志分析 / 源码级插桩四板斧)从 I/O 两端夹逼 + 中间层观察定位签名逻辑。**
> **v2.5.0 更新**:第四板斧「源码级插桩」由 camoufox-reverse MCP v0.4.0 的 `instrument_jsvmp_source` 提供支持,在 HTTP 层改写 VMP 源码,对每个 `obj[key]` 和 `fn(args)` 插入 tap,捕获 VM 内部 switch/case 调度——是瑞数 5/6、Akamai sensor_data、webmssdk、obfuscator.io 这类"VM 自包含"场景的通用武器。详见 [`jsvmp-source-instrumentation.md`](./jsvmp-source-instrumentation.md)。
---
## 识别 JSVMP
### 文件特征
| 特征 | 描述 |
|------|------|
| 文件大小 | 200KB+ 的单文件,通常 500KB~2MB |
| 变量命名 | 完全无意义:单字母(`a`, `b`, `c`)或 `_0x` 前缀 |
| 超大数组 | 包含数千个数字元素的数组(字节码) |
| 解释器循环 | `while(true) { switch(opcode) { case 0: ... case 1: ... } }` |
| 栈操作 | 频繁出现 `push`、`pop`、`shift` 操作 |
| API 劫持 | 重写 `XMLHttpRequest`、`fetch`、`document.cookie` 等原生 API |
### 代码模式示例
```javascript
// 典型的 JSVMP 解释器结构
var _0x1234 = [3, 15, 7, 22, ...]; // 字节码数组
var _0x5678 = []; // 操作栈
var _0xabcd = 0; // 指令指针
function _0xefgh() {
while (true) {
var _0x9999 = _0x1234[_0xabcd++];
switch (_0x9999) {
case 0: _0x5678.push(_0x1234[_0xabcd++]); break; // PUSH
case 1: var a = _0x5678.pop(), b = _0x5678.pop(); _0x5678.push(a + b); break; // ADD
case 2: /* ... */ break;
// 数十到数百个 case
}
}
}
```
### 如何确认是 JSVMP
```
MCP 操作(推荐顺序):
- list_scripts → 找到异常大的 JS 文件(100KB+)
- find_dispatch_loops(script_url=<大 JS 的 url>, min_case_count=20) ← v0.4.0 推荐首选
→ 返回 candidates: [{fn_name, case_count, char_range, preview}]
→ case_count > 50 的基本确认是 VMP 解释器
- dump_jsvmp_strings(script_url=<大 JS 的 url>) → 提取字符串数组
→ 看 suspicious_patterns 是否包含 "JSVMP interpreter loop (while+switch, large source)"
→ 看 api_names 是否包含 navigator/screen/encrypt/md5/hmac 等
兜底手动方式(大文件 find_dispatch_loops 超时时):
- search_code_in_script(script_id=N, keyword="switch") → 前后 3 行上下文
- search_code_in_script(script_id=N, keyword="case 0:|case 1:|case 2:")
- get_script_source(script_id=N, start_line=..., end_line=...) → 按 char_range 读取分发体
```
---
## 四板斧方法论(v2.5.0 更新)
### 四板斧关系图
```
┌── 第一板斧 Hook 出入口 ──────────┐
│ inject_hook_preset(xhr|fetch │
│ |crypto|cookie|websocket │
│ |debugger_bypass|runtime_probe)│
│ freeze_prototype │
│ analyze_cookie_sources │
└────────┬─────────────────────────┘
│
夹逼 I/O ──→ ┌─┴─┐ ──→ 推断签名公式
│VMP│
中间层 ──→ ┌─┘ └─┐ ──→ 追踪执行链路
│
┌── 第二板斧 插桩解释器 ────────────┐ ┌── 第四板斧 源码级插桩 ──────────┐
│ find_dispatch_loops │ │ instrument_jsvmp_source (新) │
│ trace_function(分发函数/子函数) │ │ get_instrumentation_log │
│ trace_property_access │ │ → summary.hot_keys │
│ hook_jsvmp_interpreter (多路径) │ │ → summary.hot_methods │
└────────┬─────────────────────────┘ │ → summary.hot_functions │
└────────┬────────────────────────┘
│
↓ ↓
┌── 第三板斧 日志分析 ──────────────────┐
│ get_trace_data + get_jsvmp_log │
│ + get_runtime_probe_log │
│ + get_instrumentation_log │
│ 反向追踪法(从签名值→明文) │
│ 多次请求对比法(找变化因子) │
└───────────────────────────────────────┘
```
**板斧选择矩阵**:
| VMP 特征 | 推荐板斧组合 |
|---------|-------------|
| 签名通过 CryptoJS / atob / MD5 等可 hook API 走 | 第一 + 第三(hot API hook + 日志反推) |
| VMP 通过 `Function.apply/call` 调子函数 | 第二 + 第三(`hook_jsvmp_interpreter` 多路径 + 日志) |
| VMP 自包含,算法全在 switch/case 内(瑞数 5/6、Akamai、webmssdk、obfuscator.io) | **第四板斧首选**,配合第一板斧做 I/O 边界 |
| VMP 深度绑定浏览器环境(compare_env 差异大 + 签名随环境变) | 路径 B 环境伪装(见 SKILL.md 场景 10),配第四板斧 `hot_keys` 定位指纹集 |
---
### 快速路径:v0.4.0 黄金 8 步流程(推荐先试,70%+ 瑞数/Akamai/webmssdk 场景直接搞定)
```
Step 1 — find_dispatch_loops 确认 VMP + 记下 fn_name / char_range
Step 2 — hook_jsvmp_interpreter(script_url=<VMP basename>) 装多路径探针
Step 3 — inject_hook_preset("cookie", persistent=True) + inject_hook_preset("xhr", persistent=True)
Step 4 — instrument_jsvmp_source(url_pattern="**/<VMP 文件>", mode="ast", tag="vmp1") ← 核心新步
Step 5 — reload_with_hooks() 让所有探针先于 VMP 生效 + 清日志
Step 6 — 触发业务操作
Step 7 — get_instrumentation_log(tag_filter="vmp1", limit=300) 看 hot_keys / hot_methods / hot_functions
Step 8 — analyze_cookie_sources() 归因最终 cookie 来源
```
详细步骤见 [`jsvmp-source-instrumentation.md`](./jsvmp-source-instrumentation.md)「黄金 8 步流程」节。
---
### 三板斧旧流程(保留以应对第四板斧无法使用的场景)
对于典型 JSVMP,也可以继续用以下一键工具:
```
MCP 操作:
- hook_jsvmp_interpreter → 自动 Hook Function.prototype.apply + 追踪 30+ 敏感属性读取
- dump_jsvmp_strings → 提取字符串数组,识别 API 名称,检测混淆模式
- 触发目标操作(翻页、提交等)
- get_jsvmp_log → 获取结构化分析结果:
· API 调用统计(哪些原生函数被 VM 调用)
· 属性读取摘要(哪些环境属性被访问)
· 调用时序(帮助定位签名生成节点)
- compare_env → 收集完整浏览器环境(navigator/screen/canvas/WebGL/Audio/timing)
· 用于后续 Node.js/Python 补环境时对照
快速路径无法解决时,再进入手动三板斧流程 ↓
```
### 第一板斧:Hook 出入口(确定 I/O 边界)
**目标**:不看 VM 内部实现,只关心"什么进去了"和"什么出来了"。
#### 1.1 Hook 出口 — 签名值去了哪里
```
MCP 操作:
- inject_hook_preset(preset="xhr", persistent=True) → 持久化拦截 XHR(跨导航不丢失)
- inject_hook_preset(preset="fetch", persistent=True) → 持久化拦截 Fetch
- hook_function(
function_path="Document.prototype.cookie",
hook_code="console.log('[COOKIE_SET]', arguments[0], new Error().stack)",
position="before", non_overridable=True
) → 拦截 Cookie 写入(防覆盖)
- freeze_prototype(className="XMLHttpRequest", methodName="send")
→ 冻结 XHR.send 防止 VM 覆盖 Hook
- reload() → 刷新页面触发 Hook
- get_console_logs → 查看捕获的签名值和调用栈
```
**关键信息提取**:
- 签名参数名(如 `sign`、`m`、`_signature`)
- 签名值的格式(长度、字符集、编码方式)
- 签名值出现在请求的哪个位置(URL params / Body / Header / Cookie)
#### 1.2 Hook 入口 — 明文从哪里来
```
MCP 操作:
- inject_hook_preset(preset="crypto", persistent=True)
→ 持久化 Hook btoa/atob/JSON.stringify/JSON.parse
- hook_function(function_path="String.fromCharCode",
hook_code="if(arguments.length > 1) console.log('[fromCharCode]', Array.from(arguments).map(Number), String.fromCharCode(...arguments))",
position="before")
→ JSVMP 高频使用 fromCharCode 构造字符串
- add_init_script(script=`
// Hook 常见加密库入口
const _origMD5 = window.CryptoJS?.MD5;
if (_origMD5) {
window.CryptoJS.MD5 = function() {
console.log('[CryptoJS.MD5] input:', arguments[0]?.toString());
const result = _origMD5.apply(this, arguments);
console.log('[CryptoJS.MD5] output:', result.toString());
return result;
};
}
`)
```
#### 1.3 I/O 关联分析
拿到出口和入口的数据后,进行关联:
```
已知信息:
出口:sign=a1b2c3d4e5f6... (32位hex → 疑似MD5)
入口:CryptoJS.MD5 输入 = "page=1&ts=1680000000&key=secret123"
关联结论:
sign = MD5("page=" + page + "&ts=" + timestamp + "&key=" + secret_key)
```
---
### 第二板斧:插桩解释器(追踪执行链路)
当出入口 Hook 无法直接关联(例如 VM 使用自实现的 MD5 而非 CryptoJS)时,需要插桩解释器。
#### 2.1 定位解释器核心函数
```
MCP 操作:
- search_code(keyword="while.*true") → 找到解释器主循环
- search_code_in_script(script_id=N, keyword="case 0:|case 1:|case 2:")
→ 在指定大文件中搜索 opcode 分发表(前后 3 行上下文,更精确)
- get_script_source → 读取解释器函数体
判断标准:
- 函数体超过 500 行
- 包含 20+ 个 case 分支
- 有数组索引递增操作(指令指针移动)
```
#### 2.2 分层插桩策略
**原则:由粗到细,逐步缩小范围。**
**第一轮 — 分发器级别(粗粒度)**
```
MCP 操作:
- trace_function(
function_path="解释器分发函数名",
log_args=true, log_return=true, max_captures=500,
persistent=True → 持久化追踪,跨导航不丢失
)
- 触发一次包含签名参数的请求
- get_trace_data → 获取 500 条执行记录(自动合并页面数据 + Python 端持久化数据)
分析要点:
- 观察每次调用的参数模式
- 找到签名值首次出现的调用序号
- 缩小时间窗口
```
**第二轮 — 子函数级别(中粒度)**
```
从第一轮日志中识别被频繁调用的子函数(通常 3-5 个核心函数):
MCP 操作:
- trace_function(function_path="子函数A", log_args=true, log_return=true, log_stack=true)
- trace_function(function_path="子函数B", log_args=true, log_return=true, log_stack=true)
- 再次触发请求
- get_trace_data → 分析子函数的输入输出
```
**第三轮 — 字符串操作级别(细粒度)**
```
JSVMP 中字符串操作是签名生成的关键信号:
MCP 操作:
- hook_function(function_path="String.prototype.charAt",
hook_code="console.log('[charAt]', this.substring(0,50), arguments[0])",
position="before")
- hook_function(function_path="String.prototype.charCodeAt", ...)
- hook_function(function_path="String.prototype.substring", ...)
- hook_function(function_path="String.prototype.concat", ...)
- hook_function(function_path="Array.prototype.join", ...)
分析要点:
- 追踪字符串如何被逐步拼接
- 观察 charCodeAt 的输入字符和输出编码
- 关注 join('') 调用 —— 通常是最终拼接点
```
#### 2.3 关键变量监控
**方法 1:使用 MCP 专用工具(推荐)**
```
MCP 操作:
- trace_property_access(target_expression="疑似签名容器对象")
→ Proxy 级别属性访问追踪,自动记录读写操作
- get_property_access_log → 获取属性访问记录
- compare_env → 收集完整浏览器环境信息
→ 用于后续补环境时精确对照 Node.js/Python 差异
```
**方法 2:手动注入监控脚本**
```javascript
// 通过 add_init_script(persistent=True) 注入
(function() {
// 方法1:监控全局变量写入
const watched = ['sign', 'm', '_signature', 'token'];
const origDefineProperty = Object.defineProperty;
watched.forEach(key => {
let val;
try {
origDefineProperty(window, key, {
get() { return val; },
set(v) {
console.log(`[GLOBAL_SET] window.${key} =`, v);
console.trace();
val = v;
},
configurable: true
});
} catch(e) {}
});
// 方法2:监控对象属性赋值
const _origAssign = Object.assign;
Object.assign = function() {
const result = _origAssign.apply(this, arguments);
for (let i = 1; i < arguments.length; i++) {
const src = arguments[i];
if (src && typeof src === 'object') {
Object.keys(src).forEach(k => {
if (watched.includes(k.toLowerCase())) {
console.log(`[ASSIGN] ${k} =`, src[k]);
console.trace();
}
});
}
}
return result;
};
})();
```
---
### 第三板斧:日志分析(从海量数据中提取签名链路)
JSVMP 的 trace 日志通常有数百甚至数千条,需要系统化的分析方法。
#### 3.1 日志采集
```
MCP 操作:
- get_trace_data → 获取函数追踪数据(自动合并页面数据 + Python 端持久化数据)
- get_jsvmp_log → 获取 JSVMP 专项日志(含 API 调用统计 + 属性读取摘要)
- get_console_logs → 获取 Hook 输出
- get_breakpoint_data → 获取伪断点捕获数据
- get_property_access_log → 获取属性访问记录
```
#### 3.2 过滤策略
| 过滤维度 | 方法 | 说明 |
|---------|------|------|
| 关键词过滤 | 搜索签名值片段 | 在日志中搜索已知签名值的前8位 |
| 时间窗口过滤 | 只看请求前 1-2 秒 | 签名通常在请求发送前即时生成 |
| 类型过滤 | 只看字符串参数 | 数值型操作大多是 VM 内部调度 |
| 长度过滤 | 关注长度 > 8 的字符串 | 排除短字符串噪声 |
| 变化过滤 | 对比多次请求日志 | 每次都变的是动态参数,不变的是密钥 |
#### 3.3 反向追踪法(核心技巧)
从已知的签名值反向追踪到原始明文:
```
步骤 1:在日志中搜索签名值
→ 搜索 "a1b2c3d4" (签名值的前几位)
→ 找到:[trace#247] return "a1b2c3d4e5f6..."
步骤 2:查看该次调用的输入
→ [trace#247] args: ["page=1&ts=1680000000&key=secret123"]
→ 这是签名函数的输入明文
步骤 3:继续追踪输入来源
→ 搜索 "page=1&ts=1680000000"
→ [trace#245] return "page=1&ts=1680000000&key=secret123"
→ [trace#245] args: ["page=1", "ts=1680000000", "key=secret123"]
→ 这是参数拼接函数
步骤 4:确认各参数来源
→ "page=1" → 用户输入
→ "ts=1680000000" → Date.now() / 1000
→ "key=secret123" → 硬编码密钥
结论:sign = MD5("page=" + page + "&ts=" + Math.floor(Date.now()/1000) + "&key=secret123")
```
#### 3.4 多次请求对比法
```
请求 1: sign=aaa111, ts=1680000000, page=1
请求 2: sign=bbb222, ts=1680000005, page=2
请求 3: sign=ccc333, ts=1680000010, page=1
对比分析:
- page 相同但 sign 不同(请求1 vs 请求3)→ ts 参与签名
- ts 不同且 sign 不同 → ts 是变化因子之一
- 请求1 和 请求3 page 相同但 sign 不同 → 不仅仅是 page 参与
结论:sign 至少由 page + ts 共同决定
```
#### 3.5 算法指纹识别
根据 I/O 特征反推算法类型:
| 输出特征 | 可能算法 |
|---------|---------|
| 32 位 hex | MD5 |
| 40 位 hex | SHA-1 |
| 64 位 hex | SHA-256 |
| 44 位 Base64(含 `=` 填充) | HMAC-SHA256 + Base64 |
| 24/32/44 位 Base64 | AES 加密(CBC/ECB) |
| 输出长度随输入变化 | 非固定哈希,可能是加密或自定义编码 |
| 输出包含特殊分隔符 | 自定义拼接格式 |
---
## 常见 JSVMP 变体及应对
### 变体 1:VM 劫持 XHR/Fetch
**特征**:VM 重写了 `XMLHttpRequest` 或 `fetch`,请求在 VM 内部完成,外部 Hook 抓不到。
**应对**:
```
- Hook 更底层的接口:
· hook_function(function_path="XMLHttpRequest.prototype.send", ...)
· 在 add_init_script 中保存原始引用,在 VM 重写之前
- 或 Hook 网络层:
· start_network_capture → 不依赖 JS 层 Hook,直接在协议层捕获
· list_network_requests → get_network_request → 获取完整请求详情
```
### 变体 2:VM 动态生成加密函数
**特征**:加密算法不是硬编码在字节码中,而是 VM 运行时通过 `eval` 或 `new Function` 动态构造。
**应对**:
```
- inject_hook_preset(preset="crypto") → 包含 eval/Function Hook
- hook_function(function_path="Function",
hook_code="console.log('[new Function]', arguments[arguments.length-1].substring(0,200))",
position="before")
- 从 Hook 日志中提取动态生成的函数源码
```
### 变体 3:多层 VM 嵌套
**特征**:外层 VM 解密出内层 VM 的字节码,再由内层 VM 执行签名逻辑。
**应对**:
```
- 不要试图理解嵌套关系
- 依然从 I/O 两端入手,Hook 最终的出口和最初的入口
- 增加 trace_function 的 max_captures 到 1000+
- 用时间戳过滤法缩小日志范围
```
### 变体 4:VM + WASM 混合
**特征**:VM 负责流程控制,核心加密算法调用 WASM 导出函数。
**应对**:
```
- search_code(keyword="WebAssembly|wasm|instantiate")
- Hook WASM 导出函数:
evaluate_js(expression=`
// 等 WASM 实例化后 Hook 导出函数
const origInstantiate = WebAssembly.instantiate;
WebAssembly.instantiate = async function() {
const result = await origInstantiate.apply(this, arguments);
const exports = result.instance?.exports || result.exports;
Object.keys(exports).forEach(name => {
if (typeof exports[name] === 'function') {
const orig = exports[name];
exports[name] = function() {
console.log('[WASM]', name, 'args:', Array.from(arguments));
const ret = orig.apply(this, arguments);
console.log('[WASM]', name, 'return:', ret);
return ret;
};
}
});
return result;
};
`)
- 确认 WASM 函数的 I/O 后,下载 .wasm 文件用 Node.js 直接加载调用
```
---
## 前置判断(v2.6.0 新增):先识别 JSVMP 所在反爬类型
在选路径 A / B 之前,先判断 JSVMP 的反爬类型:
```
navigate(url) 不加任何 hook 观察 redirect_chain
│
├─ 反复 412 后才到 200 → 签名型 JSVMP(瑞数/Akamai)
│ └─ 走"签名型专属路径"(见下文)
│
├─ 直接 200 但主请求链带签名参数 → 行为型 JSVMP(TikTok/极验)
│ └─ 按原路径 A / 路径 B 决策树选择
│
└─ 直接 200 无签名参数 → 非 JSVMP 或 VMP 已被跳过
└─ 回 Phase 2.2 常规混淆分析
```
### 签名型 JSVMP 专属路径(v2.6.0 新增)
签名型 JSVMP 与原路径 A / B 的决策树**不适用**,因为:
- 路径 A 的前三板斧(Hook/插桩/日志)会破坏签名
- 路径 B 的 jsdom 环境伪装成本高(58+ 项修复)
签名型 JSVMP 推荐走的路:
```
1. instrument_jsvmp_source(mode="ast", tag="...") (第四板斧)
2. reload_with_hooks() 让插桩后的 VMP 跑完挑战
3. get_instrumentation_log(tag_filter, type_filter="tap_get")
4. 基于 hot_keys 补最小环境,在 jsdom 或纯 Python 侧独立跑
(此时 jsdom 补环境的粒度比路径 B 小得多,只补 hot_keys 显示的属性)
```
源码级插桩失败才回落:
```
A. hook_jsvmp_interpreter(mode="transparent") 仅 prototype getter 替换
- 对签名型大多数情况安全
- 极严格反爬仍能识别 getter identity 变化,此时真的只能走路径 B 全量 jsdom 伪装
B. 路径 B 完整 jsdom 环境伪装(六步法)
- 最重但最稳,是最后的兜底
```
---
## 还原策略决策(v2.5.0 扩充源码级插桩分支)
```
JSVMP 签名还原决策树:
第 0 步:先看 hot_methods(第四板斧 summary)
├─ hot_methods 包含 CryptoJS.MD5 / SubtleCrypto.digest / HMAC / btoa
│ → 算法是标准加密,继续看第 1 步分支
│
├─ hot_methods 全是自定义 fn 名,几乎没有 CryptoJS/Subtle
│ → VMP 自实现加密,直接跳到第 3 步(沙箱运行完整 VM)
第 1 步:标准算法分支
├─ 能从 hot_methods + 调用栈反推出明文拼接顺序?
│ ├─ YES → 纯算法还原(Node.js crypto / Python hashlib+pycryptodome)
│ └─ NO(明文来自 VM 内部)→ 进第 2 步
第 2 步:能提取独立的签名函数?
├─ YES → 沙箱执行(Node.js vm / Python execjs)
│ · 提取函数及其依赖
│ · 构造最小执行环境(根据 hot_keys 对齐环境变量)
└─ NO → 第 3 步
第 3 步:VMP 劫持了整个请求链路 + hot_keys 显示大量环境读取?
├─ YES → 走路径 B(jsdom 环境伪装)
│ · hot_keys 直接告诉你要对齐哪些环境属性(省去 58 项人工 diff)
│ · analyze_cookie_sources 确认 cookie 是否来自 HTTP Set-Cookie
│ · 参考 references/jsdom-env-patches.md
└─ NO(jsdom 加载失败 / VMP 有 CDN 反射检测)→ 第 4 步
第 4 步:加载完整 VM(仅调用签名入口)
· 下载完整 JS 文件
· 在 Node.js vm 中加载
· 补最小浏览器环境(仍然根据 hot_keys 指导)
· 调用签名函数获取结果
第 5 步:实在无法脱离浏览器
→ 使用 Camoufox 浏览器自动化(最后手段,不是协议优先方案)
```
**关键:第四板斧的 `hot_keys` / `hot_methods` 是所有决策的共同输入**——不要在没看 hot_keys 之前就选策略。即使最后选了路径 B,`hot_keys` 也能把"要对齐什么环境"从 58 项人工 diff 降为 20 项精确对齐。
---
## 实战检查清单(v2.5.0 扩充四板斧项)
在 JSVMP 分析过程中,确保以下每项都已完成:
**第一板斧 — Hook 出入口**:
- [ ] `find_dispatch_loops` 确认是 VMP(case_count > 50)
- [ ] `inject_hook_preset("xhr", persistent=True)` + `("fetch", persistent=True)` 捕获请求出口
- [ ] `inject_hook_preset("cookie", persistent=True)` **原型链级** cookie hook(替代手写 Document.prototype.cookie 的做法)
- [ ] `analyze_cookie_sources` 归因最终 cookie(HTTP vs JS)
- [ ] `inject_hook_preset("crypto", persistent=True)` 捕获加密原语入口(btoa/atob/CryptoJS/MD5/SHA)
- [ ] Hook String.fromCharCode(JSVMP 高频信号)
- [ ] `freeze_prototype` 防止 VMP 覆盖 hook
**第二板斧 — 插桩解释器**:
- [ ] `hook_jsvmp_interpreter(script_url=<VMP basename>)` 多路径探针(apply/call/bind + Reflect.*/Proxy)
- [ ] 分层 `trace_function`(粗 → 中 → 细,用 `max_captures` 限制日志量)
- [ ] `trace_property_access(["navigator.*", "screen.*", ...])` 监控签名容器
**第三板斧 — 日志分析**:
- [ ] `get_jsvmp_log` + `get_trace_data` + `get_runtime_probe_log`(如果装了 runtime_probe)+ `get_console_logs`
- [ ] 反向追踪法,找到签名值首次出现的位置
- [ ] 多次请求对比,确认变化因子和固定因子
**第四板斧 — 源码级插桩 [v2.5.0 新增]**:
- [ ] `instrument_jsvmp_source(url_pattern=..., mode="ast"|"regex", tag=...)` 装好
- [ ] `reload_with_hooks()` 让插桩先于 VMP 生效
- [ ] 读 `get_instrumentation_log` 三个 summary:`hot_keys` / `hot_methods` / `hot_functions`
- [ ] `hot_keys` 给出 VMP 读取的环境指纹集(top 30)
- [ ] `hot_methods` 给出 VMP 调用的方法(ObjectType.methodName 格式)
- [ ] `hot_functions` 给出 VMP 调用的函数名
- [ ] `stop_instrumentation` 完工清理
**环境/时序**:
- [ ] 若目标是首屏挑战页,用 `navigate(pre_inject_hooks=[...])` 或 `via_blank=True`
- [ ] `compare_env` 采集环境基准
- [ ] `bypass_debugger_trap` 绕过反调试
**还原与验证**:
- [ ] 根据 hot_methods 判断算法类型
- [ ] 选择还原策略(纯算法 / 沙箱 / 完整 VM / jsdom 环境伪装 / 浏览器)
- [ ] 实现并验证签名计算结果
- [ ] 端到端验证(连续 5+ 次请求稳定)