diff --git a/SKILL.md b/SKILL.md
index 32cbbca..489b293 100644
--- a/SKILL.md
+++ b/SKILL.md
@@ -4,7 +4,7 @@ description: >
Node.js / Python 接口自动化与签名还原工程技能:对自有平台或已授权平台的 Web API 进行签名分析与接口对接,
通过 Camoufox 反检测浏览器动态调试与静态源码分析,定位并还原前端加密/签名逻辑,
使用 Node.js 或 Python 实现算法复现与自动化接口调用。
- 深度集成 camoufox-reverse MCP v0.5.0(C++ 引擎级指纹伪装,65 个逆向分析工具,签名型反爬兼容改造)。
+ 深度集成 camoufox-reverse MCP v0.6.0(C++ 引擎级指纹伪装,68 个逆向分析工具,签名型反爬兼容改造,新增 CSP 预检与双签名场景支持)。
擅长 JSVMP 虚拟机保护的双路径攻克:路径 A 算法追踪(Hook / 插桩 / 日志分析 / 源码级插桩四板斧,
通用对瑞数 5/6、Akamai sensor_data、webmssdk、obfuscator.io)、
路径 B 环境伪装(jsdom/vm 沙箱 + 浏览器环境采集对比 + 全量补丁)。
@@ -114,7 +114,7 @@ argument-hint: "<目标URL> [需要分析的加密参数名, 如 sign, m, token]
| 类型 | 代表 | 特征 | ✅ 工具路径 | ❌ 禁用 |
|---|---|---|---|---|
| **签名型**
(环境即签名) | 瑞数 5/6、Akamai sensor_data v3+、Shape Security | 挑战时读 navigator/screen/document.cookie 等,hash 成 cookie;**任何主动观察都破坏签名** | `instrument_jsvmp_source(mode="ast")` +
`analyze_cookie_sources()` +
`navigate` 的 `redirect_chain`/`final_status` +
`reload_with_hooks()` +
(备选)`hook_jsvmp_interpreter(mode="transparent")` | `pre_inject_hooks=["jsvmp_probe"]`
`hook_jsvmp_interpreter(mode="proxy")`
`trace_property_access(["navigator.*"])` |
-| **行为型**
(参数签名) | TikTok webmssdk、极验 gt4、头条 a_bogus | 签名依赖参数/时间戳/用户行为,**不校验浏览器原生性** | `hook_jsvmp_interpreter(mode="proxy")` 全量开 +
`pre_inject_hooks=["jsvmp_probe"]` +
`inject_hook_preset("xhr"/"fetch"/"crypto")` +
(必要时)`instrument_jsvmp_source` 源码级补刀 | — |
+| **行为型**
(参数签名) | 海外短视频平台(国际版) webmssdk、极验 gt4、头条 a_bogus | 签名依赖参数/时间戳/用户行为,**不校验浏览器原生性** | `hook_jsvmp_interpreter(mode="proxy")` 全量开 +
`pre_inject_hooks=["jsvmp_probe"]` +
`inject_hook_preset("xhr"/"fetch"/"crypto")` +
(必要时)`instrument_jsvmp_source` 源码级补刀 | — |
| **纯混淆** | obfuscator.io、自研 VMP 无反指纹 | 只是代码难读,不检测观察者 | 任意组合,优先 `instrument_jsvmp_source(mode="regex")` | — |
### 识别反爬类型的标准动作(放在 Phase 0.5 之前)
@@ -138,7 +138,7 @@ const r = await navigate(url) // 不传 pre_inject_hooks,不加任何 hook
| `redirect_chain` 里有**重复同 URL 的 412/302** 然后才 200 | 签名型(十有八九是瑞数/Akamai) |
| `initial_status == 200` 直接返回,但 Network 里加载了 `sdenv*.js` / `FuckCookie*.js` | 签名型(瑞数) |
| 加载 `acmescripts*.js` / `/akam/{version}/*` | 签名型(Akamai) |
-| 加载 `webmssdk.es5*.js` / `_sdkGlueInit` | 行为型(TikTok 系) |
+| 加载 `webmssdk.es5*.js` / `_sdkGlueInit` | 行为型(短视频平台系) |
| 加载 `gt4*.js` + 滑块/点击验证 | 行为型(极验) |
| `initial_status == 200` + JS 大文件含 `_0x` 前缀密集的变量 | 纯混淆(obfuscator.io) |
| `initial_status == 200` + JS 大文件含 `while(true)`+`switch` 分发表 | JSVMP(需进一步判断是签名型还是行为型) |
@@ -168,7 +168,7 @@ const r2 = await navigate(url, { pre_inject_hooks: ["jsvmp_probe"] })
7. 在 Node/jsdom/Python 侧复刻最小环境,独立跑签名
```
-**行为型工作流**(TikTok / 极验):
+**行为型工作流**(短视频平台 / 极验):
```
1. launch_browser(headless=false) + start_network_capture(capture_body=True)
2. navigate(url, pre_inject_hooks=["jsvmp_probe","xhr","fetch","crypto"])
@@ -194,7 +194,7 @@ const r2 = await navigate(url, { pre_inject_hooks: ["jsvmp_probe"] })
## 核心武器:camoufox-reverse MCP
-Camoufox 反检测浏览器 + Playwright 协议,C++ 引擎级指纹伪装,65 个工具覆盖逆向分析全链路(v0.4.0+)。
+Camoufox 反检测浏览器 + Playwright 协议,C++ 引擎级指纹伪装,68 个工具覆盖逆向分析全链路(v0.6.0)。
**核心优势:**
- Camoufox 在 **C++ 引擎层** 修改指纹信息,而非 JS 补丁
@@ -203,6 +203,7 @@ Camoufox 反检测浏览器 + Playwright 协议,C++ 引擎级指纹伪装,65
- 适用于有强反爬检测的站点:瑞数、极验、Cloudflare 等
- Hook 持久化 + 防覆盖:跨导航自动重注入,`Object.defineProperty` 冻结防止页面 JS 覆盖
- v0.5.0 签名型反爬兼容:hook_jsvmp_interpreter 新增 mode="transparent" 模式(仅 prototype getter 替换,不装 Proxy、不改 Function.prototype,对签名型反爬透明);instrument_jsvmp_source 的 AST 模式改为 MCP 侧 esprima-python 实现,挑战页也能跑(不再依赖外部 CDN 加载 acorn)
+- v0.6.0 新增:`instrument_jsvmp_source` 支持 `csp_bypass=True` 自动绕过 Content-Security-Policy 限制(对严格 CSP 站点的 AST 模式不再静默失败);`navigate` 新增 `collect_response_chain=True`(默认)精确记录 JS 驱动的重定向链;新增 3 个工具(`check_csp_policy` / `get_response_chain` / `get_dual_sign_data`)
### 浏览器与页面控制
@@ -264,7 +265,7 @@ Camoufox 反检测浏览器 + Playwright 协议,C++ 引擎级指纹伪装,65
- `track_reflect=True`:Hook `Reflect.apply/get/set/construct`(ES6 VMP 常用)
- `track_props=True`:在全局对象上装 Proxy——默认 `["navigator","screen","history","localStorage","sessionStorage","performance"]`(`document` **默认不装** 因为容易破坏页面;document.cookie 用 `cookie` 预设)
- 参数 `script_url` 做栈过滤(传 VMP 文件 basename),`max_entries=10000`
- - **对瑞数 5/6、Akamai sensor_data v2/v3、TikTok webmssdk、obfuscator.io 通用有效**
+ - **对瑞数 5/6、Akamai sensor_data v2/v3、短视频平台 webmssdk、obfuscator.io 通用有效**
- `hook_jsvmp_interpreter` 新增 `mode` 参数(v0.5.0):
- `mode="proxy"`(默认,向后兼容)— v0.4.x 的原行为,装 Proxy + 改 Function.prototype。**对签名型反爬会挂**(Proxy 和 Function.prototype.apply.toString 都可被检测)
- `mode="transparent"`(v0.5.0 新增,**签名安全备选**)— 只在 Navigator/Screen/Document/History/Performance/Location 原型上替换 getter,不装 Proxy、不改 Function.prototype。fake getter 的 `toString()` 伪装为原 getter 的源码串,瞒过 `Function.prototype.toString.call(getter)` 检测
@@ -751,6 +752,28 @@ Actions:
**环境伪装六步法**:
```
+步骤 0.5:确认签名函数入口(v2.7.0 新增)
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ 在开始环境采集之前,先确认 JSVMP 的签名入口类型:
+
+ MCP 操作:
+ - search_code(keyword="frontierSign") → 是否有导出签名函数
+ - search_code(keyword="XMLHttpRequest.prototype.open") → 是否劫持 XHR
+ - search_code(keyword="window.fetch") → 是否劫持 fetch
+ - search_code(keyword="cacheOpts") → 是否使用新版初始化
+
+ 判定结果:
+ a. 仅 XHR 拦截器 → 单通道截获(Hook XHR.open 即可)
+ b. XHR + fetch 双通道拦截 → 需同时 Hook 两个通道
+ c. 导出函数 + 拦截器 → 优先调用导出函数,拦截器作为备选
+ d. 含 cacheOpts 初始化 → 必须传入 cacheOpts 配置才能注册业务路径
+
+ ⚠️ 跳过此步骤可能导致:
+ - 只 Hook 了 XHR 漏掉 fetch 通道的签名(如 X-Gnarly)
+ - 未传 cacheOpts 导致拦截器不触发
+ - 误以为签名函数不存在而走了更复杂的路径
+
步骤 1:用 Camoufox 采集真实浏览器完整环境指纹
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -1109,6 +1132,42 @@ Actions:
2. 用对比测试确认:改变该值后是否导致请求失败
3. 只补真正参与校验的最小环境项
+### 双签名截获不完整排查(v2.7.0 新增)
+
+**症状**:截获到 X-Bogus(URL 参数签名)但缺少 X-Gnarly(header 签名),或反之。请求被服务端拒绝。
+
+**排查步骤**:
+
+| 步骤 | 检查项 | 动作 |
+|---|---|---|
+| ① | 是否只 Hook 了 XHR 没 Hook fetch | `inject_hook_preset("fetch", persistent=True)` 补上 |
+| ② | 是否只 Hook 了 fetch 没 Hook XHR | `inject_hook_preset("xhr", persistent=True)` 补上 |
+| ③ | cacheOpts 是否正确传入 | 检查 `_SdkGlueInit` 调用是否包含 `cacheOpts` 字段 |
+| ④ | 业务路径是否在 enablePathList 中 | 检查 `cacheOpts.paths` 是否覆盖目标 API 路径 |
+
+### CSP 策略阻断插桩排查(v2.7.0 新增)
+
+**症状**:`instrument_jsvmp_source(mode="ast")` 返回成功,但 `get_instrumentation_log` 始终为空,VMP 脚本未被改写。
+
+**根因**:目标页面的 Content-Security-Policy 阻止了改写后的脚本执行。
+
+**排查步骤**:
+
+| 步骤 | 检查项 | 动作 |
+|---|---|---|
+| ① | 检查 CSP 策略 | `evaluate_js("document.querySelector('meta[http-equiv=Content-Security-Policy]')?.content")` |
+| ② | 检查响应头 CSP | 在 `list_network_requests` 中找主文档,`get_network_request` 看 `content-security-policy` header |
+| ③ | CSP 含 `script-src 'strict-dynamic'` 或 nonce-only | 加 `csp_bypass=True` 参数重试 |
+| ④ | 仍然失败 | 退回 `mode="regex"` 或 `hook_jsvmp_interpreter(mode="transparent")` |
+
+### Firefox native code 格式不匹配排查(v2.7.0 新增)
+
+**症状**:jsdom 环境补丁已打全,但签名仍被服务端拒绝。对比发现 `Function.prototype.toString` 输出格式与 Camoufox 采集的基准不一致。
+
+**根因**:Camoufox 基于 Firefox 内核,native code 格式含换行缩进;jsdom 的 markNative 输出了 Chrome 单行格式。
+
+**修复**:将 markNative 的返回值从 `"function name() { [native code] }"` 改为 `"function name() {\n [native code]\n}"`(Firefox 格式)。
+
---
## 常见签名分析场景速查
@@ -1247,7 +1306,7 @@ MCP 操作:
⚠️ 禁用: pre_inject_hooks / hook_jsvmp_interpreter(mode="proxy") / trace_property_access
```
-**🟡 行为型(TikTok / 极验)**:
+**🟡 行为型(短视频平台 / 极验)**:
```
1. start_network_capture(capture_body=True)
2. navigate(url, pre_inject_hooks=["jsvmp_probe","xhr","fetch","crypto"])
@@ -1288,12 +1347,12 @@ MCP 操作:
- document.hasFocus() → true
- DOM offsetHeight/Width → 非零值
MCP 操作:
- - launch_browser → navigate → 搭建采集环境
+ - launch_browser → navigate(url, wait_until="domcontentloaded", collect_response_chain=True) → 搭建采集环境
- compare_env → 采集浏览器基准数据
- evaluate_js → 分批采集细粒度环境值(4-5 批次)
- 本地运行 jsdom 对比脚本 → 生成差异报告
- 迭代修复 → 再次对比 → 直到差异归零 → 端到端验证
-参考:references/jsdom-env-patches.md(补丁模板)、cases/ 经验案例
+参考:references/jsdom-env-patches.md(补丁模板)、cases/ 经验案例(含双签名变体)
```
### 场景 11:通用 JSVMP 源码级插桩(瑞数 5/6 / Akamai sensor_data / webmssdk / obfuscator.io)
@@ -1338,13 +1397,18 @@ MCP 操作:
find_dispatch_loops(script_url="https://target.com/xxx/sdenv-xxx.js", min_case_count=20)
→ case_count > 50 基本确认;记下 fn_name / char_range
- Step 3 — 装源码级插桩(核心)
+ Step 3 — CSP 预检 + 装源码级插桩(核心)
+ # v2.7.0 新增:先检查目标页面的 CSP 策略
+ # 严格 CSP 可能阻断 AST 模式注入的改写代码执行
+ # check_csp_policy() → 如果 script-src 含 'strict-dynamic' 或 nonce-only
+ # → 加 csp_bypass=True 或退回 regex 模式
instrument_jsvmp_source(
url_pattern="**/sdenv-*.js", # glob 模式,能覆盖 CDN 不同 hash 版本
mode="ast", # 页面能联网时优先 ast(99% 覆盖);否则 regex(80%,无 CDN)
tag="vmp1", # 一次插桩多个 VMP 时用来过滤
rewrite_member_access=True, # 每个 obj[key] 都 tap
- rewrite_calls=True # 每个 fn(args) / obj.method(args) 都 tap
+ rewrite_calls=True, # 每个 fn(args) / obj.method(args) 都 tap
+ csp_bypass=True # v0.6.0: 自动绕过 CSP 限制
)
Step 4 — 兜底 hook(与源码插桩互补)
@@ -1418,11 +1482,16 @@ MCP 操作:
### MCP 工具配合模式
```
-启动浏览器 → Cookie写入 → 网络捕获 → 静态定位 → 动态验证 → 补充分析 → 再次验证
- ↓ ↓ ↓ ↓ ↓ ↓ ↓
-launch_browser set_cookies start_ search_ set_break hook_ trace_
-+ navigate + reload network_ code point_via function function
- capture hook
+反爬类型识别 → 启动浏览器 → Cookie写入 → 网络捕获 → 静态定位 → 动态验证 → 补充分析 → 再次验证
+ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
+navigate launch_browser set_cookies start_ search_ set_break hook_ trace_
+(无hook, + navigate + reload network_ code point_via function function
+ 看redirect capture hook
+ _chain)
+
+双签名场景额外步骤:
+ → 同时 Hook XHR + fetch → 截获 URL 参数签名 + header 签名
+ → search_code(keyword="cacheOpts") → 确认初始化方式
所有操作均在独立的 Camoufox 浏览器中执行,不影响用户日常浏览器
```
@@ -1556,9 +1625,17 @@ launch_browser set_cookies start_ search_ set_break hook_ trace_
27. **`reload_with_hooks` 取代裸 `reload`**:装完 hook 想让它先于页面 JS 跑,裸 `reload()` 不能保证顺序;用 `reload_with_hooks()` 一步到位——它不仅按 context-level init_script 的方式重载,还默认 `clear_log=True` 清空 `__mcp_jsvmp_log`/`__mcp_vmp_log`/`__mcp_prop_access_log`/`__mcp_cookie_log`,拿到的是这次重载的干净快照
28. **反爬类型识别是 Phase 0 的 Phase 0**:先判断签名型/行为型/纯混淆,再选工具。用错档的工具不是效率差,是**根本跑不通**。
29. **签名型反爬只有一条路**:`instrument_jsvmp_source(mode="ast")`。源码级插桩不动环境,是唯一能同时"观察 VMP"和"让挑战通过"的手段。
-30. **`pre_inject_hooks` 的正确定位是"观察 ≠ 污染"场景的便利工具**:TikTok 可用,瑞数不可用。永远不要对签名型反爬用它。
+30. **`pre_inject_hooks` 的正确定位是"观察 ≠ 污染"场景的便利工具**:行为型反爬可用,瑞数不可用。永远不要对签名型反爬用它。
31. **`mode="transparent"` 是 `mode="proxy"` 的签名安全备选**:只替换 prototype getter,不装 Proxy 不改 Function.prototype。比 proxy 安全一个数量级,但极严格的反爬(对 getter 函数做跨加载 identity 对比)仍能感知。源码级插桩失败再退到这里,不要一上来就用。
32. **MCP 侧 AST 让插桩在挑战页可用**:v0.5.0 把 AST 从页面内 Acorn 改为 MCP 侧 esprima,瑞数 412 挑战页(CDN 被拦)也能跑。`last_mode_used` 长期是 `"regex (fallback)"` 说明目标有太新语法,才考虑手动换 regex 或 transparent。
+33. **双签名场景必须同时 Hook XHR 和 fetch**:某些平台的 JSVMP 同时修改 `XMLHttpRequest.prototype.open`(追加 URL 参数签名)和 `window.fetch`(注入 header 签名),只 Hook 一个通道会丢失另一半签名。确认方法:`search_code(keyword="XMLHttpRequest.prototype.open")` + `search_code(keyword="window.fetch")`,两者都命中就是双通道。
+34. **cacheOpts 是新版 SDK 初始化的必传项**:旧版只需 `bdms.paths`,新版必须同时传入 `cacheOpts: { paths: [...], ttl: N }`。缺少 cacheOpts 会导致业务路径未注册,拦截器不触发,签名不生成。检测方法:`search_code(keyword="cacheOpts")`。
+35. **Firefox 格式 native code 与 Chrome 不同**:Camoufox 基于 Firefox 内核,`Function.prototype.toString` 对原生函数返回 `"function name() {\n [native code]\n}"`(含换行和 4 空格缩进),而 Chrome 返回单行格式。jsdom 的 markNative 必须匹配采集基准浏览器的格式,否则 JSVMP 的浏览器类型判断会不一致。
+36. **got-scraping 解决 Node.js TLS 指纹问题**:当签名正确但请求仍被拒时,可能是 TLS Client Hello 指纹暴露了 Node.js 运行时。`got-scraping` 可模拟 Firefox/Chrome 的 TLS 指纹,与 Camoufox 采集环境保持一致。Python 侧用 `curl_cffi`。
+37. **CSP 策略可能阻断 AST 模式插桩**:严格的 Content-Security-Policy 会阻止 `instrument_jsvmp_source(mode="ast")` 注入的改写代码执行。v0.6.0 新增 `csp_bypass=True` 参数自动绕过;旧版本需手动 `check_csp_policy` 后决定是否退回 regex 模式。
+38. **环境伪装前先确认签名函数入口**:在开始 6 步法的环境采集之前,先用 `search_code` 确认 JSVMP 的签名入口类型(单通道 XHR / 双通道 XHR+fetch / 导出函数 / cacheOpts 初始化),避免后续截获不完整。
+39. **`navigate` 的 `collect_response_chain` 默认开启**:v0.6.0 起 `navigate` 默认记录完整响应链(含 JS 驱动的重定向),`redirect_chain` 字段比旧版更精确。对签名型反爬的 412→200 判断更可靠。
+40. **经验案例优先匹配 cacheOpts 区分变体**:同一 SDK 体系(webmssdk/byted_acrawler)存在多个变体(单签名 vs 双签名、bdms.paths vs cacheOpts),Phase 0.5 指纹匹配时应优先检测 `cacheOpts` 和 `X-Gnarly` 来区分具体变体,避免套用错误的还原路径。
---
@@ -1574,4 +1651,5 @@ launch_browser set_cookies start_ search_ set_break hook_ trace_
| v2.3.0 | 2026-04-01 | 新增经验库系统(`cases/` 目录):Phase 0.5 指纹快速匹配阶段(30 秒内完成技术指纹采集 → 经验库匹配 → 命中走快速路径 / 未命中走标准流程);案例模板 `_template.md` + 知识提取提示词;首个案例:JSVMP 字节码虚拟机 + XHR 拦截器 + jsdom 环境伪装(含 58 项环境检测差异全表 + markNative 三层防御方案 + 完整浏览器指纹采集流程) |
| v2.4.0 | 2026-04-02 | **JSVMP 双路径攻克体系**:基于实战经验重构 JSVMP 分析框架,新增路径选择决策树(路径 A 算法追踪 vs 路径 B 环境伪装);路径 B「环境伪装六步法」完整方法论(采集→对比→分级→补丁→验证→端到端);新增场景 10「JSVMP + 环境伪装」;标注三板斧适用边界与局限(apply 限制 / 高频日志爆炸 / 字符串表加密);`compare_env` 覆盖范围说明及细粒度采集建议;新增 `references/jsdom-env-patches.md` 环境补丁知识库(Function.toString 三层防御 / plugins 完整模拟 / DOM 布局属性 / 30+ API 存根 / Symbol.toStringTag);经验法则扩充至 22 条(+环境伪装优先 / Function.toString 第一杀手 / 分批采集 / Hook 顺序 / 静默拒绝信号);解法模式新增 E: jsdom 环境伪装 |
| v2.5.0 | 2026-04-17 | **JSVMP 路径 A 升级为四板斧(新增源码级插桩)+ 对齐 MCP v0.4.0**:适配 camoufox-reverse MCP v0.4.0(52→65 个工具);新增第四板斧「源码级插桩」——在 HTTP 层对 VMP 脚本改写,每个 `obj[key]` / `fn(args)` 都插入 tap,对瑞数 5/6、Akamai sensor_data v2/v3、webmssdk、obfuscator.io 通用有效;新增 8 个 MCP 工具清单(`instrument_jsvmp_source`/`get_instrumentation_log`/`get_instrumentation_status`/`stop_instrumentation`/`find_dispatch_loops`/`reload_with_hooks`/`analyze_cookie_sources`/`get_runtime_probe_log`);`hook_jsvmp_interpreter` 从单路径 apply 升级为多路径(apply/call/bind + Reflect.*/Proxy 全局对象 + timing/random);`inject_hook_preset` 新增 `cookie`(原型链级 cookie hook)和 `runtime_probe`(低开销广谱运行时探针)两个预设;`navigate` 支持 `pre_inject_hooks` + `via_blank` + 返回 `initial_status`/`final_status`/`redirect_chain`,解决首屏挑战页 hook 失效;新增场景 11「通用 JSVMP 源码级插桩」(黄金 8 步流程);场景 2「动态 Cookie」引入 `analyze_cookie_sources` 作为第一步归因;新增 `references/jsvmp-source-instrumentation.md` 源码级插桩专项指南(regex vs ast 模式选择、hot_keys 指纹学习法、与 hook 探针互补);经验法则扩充至 27 条(+源码级插桩优先 / hot_keys 指纹学习法 / Cookie 归因优先于 setter hook / 首屏 pre_inject_hooks / reload_with_hooks 取代裸 reload);新增骨架案例 `cases/universal-vmp-source-instrumentation.md` |
-| v2.6.0 | 2026-04-18 | **签名型反爬兼容对齐 MCP v0.5.0**:新增顶层决策框架「反爬类型三分法」(签名型/行为型/纯混淆),放在 Phase 0.5 之前作为必做识别步骤;四板斧新增"适用反爬类型"标签,明确前三板斧对签名型不可用,源码级插桩是签名型的唯一通用解;`hook_jsvmp_interpreter` 新增 `mode` 参数("proxy"/"transparent"),transparent 模式仅替换 prototype getter 不装 Proxy,作为签名安全备选;`instrument_jsvmp_source(mode="ast")` 从页面内 Acorn 迁到 MCP 侧 esprima(零 CDN 依赖,挑战页可用),新增 `fallback_on_error` 参数和 `last_mode_used` 状态字段;`pre_inject_hooks` 参数明确标注对签名型反爬不可用(症状:redirect_chain 反复 412);场景 9(反检测站点)按反爬类型分档重写;错误处理新增"观察者效应"专项排查;新增经验法则 28-32 条;场景 11 加签名型前置检查;引用文档 `references/jsvmp-source-instrumentation.md` 需同步更新 AST 迁移与 transparent 模式 |
\ No newline at end of file
+| v2.6.0 | 2026-04-18 | **签名型反爬兼容对齐 MCP v0.5.0**:新增顶层决策框架「反爬类型三分法」(签名型/行为型/纯混淆),放在 Phase 0.5 之前作为必做识别步骤;四板斧新增"适用反爬类型"标签,明确前三板斧对签名型不可用,源码级插桩是签名型的唯一通用解;`hook_jsvmp_interpreter` 新增 `mode` 参数("proxy"/"transparent"),transparent 模式仅替换 prototype getter 不装 Proxy,作为签名安全备选;`instrument_jsvmp_source(mode="ast")` 从页面内 Acorn 迁到 MCP 侧 esprima(零 CDN 依赖,挑战页可用),新增 `fallback_on_error` 参数和 `last_mode_used` 状态字段;`pre_inject_hooks` 参数明确标注对签名型反爬不可用(症状:redirect_chain 反复 412);场景 9(反检测站点)按反爬类型分档重写;错误处理新增"观察者效应"专项排查;新增经验法则 28-32 条;场景 11 加签名型前置检查;引用文档 `references/jsvmp-source-instrumentation.md` 需同步更新 AST 迁移与 transparent 模式 |
+| v2.7.0 | 2026-04-20 | **双签名场景支持 + 对齐 MCP v0.6.0(65→68 个工具)**:新增案例 `jsvmp-dual-sign-xhr-intercept-cacheOpts-jsdom-firefox.md`(JSVMP 双签名 X-Bogus + X-Gnarly + XHR/fetch 双通道拦截 + cacheOpts 初始化 + jsdom Firefox 环境伪装);环境伪装六步法新增「步骤 0.5:确认签名函数入口」(区分单通道/双通道/导出函数/cacheOpts 初始化);MCP v0.6.0 新增 3 个工具(`check_csp_policy`/`get_response_chain`/`get_dual_sign_data`),`instrument_jsvmp_source` 支持 `csp_bypass=True` 自动绕过 CSP 限制,`navigate` 的 `collect_response_chain` 默认开启;场景 10 的 navigate 补充 `wait_until` 和 `collect_response_chain` 参数;场景 11 的 Step 3 新增 CSP 预检流程;工具配合模式更新(新增反爬类型识别前置步骤 + 双签名额外步骤);错误处理新增三类排查(双签名截获不完整 / CSP 策略阻断插桩 / Firefox native code 格式不匹配);经验法则扩充至 40 条(+双签名双通道 / cacheOpts 必传 / Firefox 格式 native code / got-scraping TLS / CSP 预检 / 签名入口确认 / collect_response_chain / 变体区分);案例模板 `_template.md` 新增「反爬类型判定」和「关键经验总结」段;`cases/README.md` 新增指纹匹配快速参考表和变体关系图 |
diff --git a/cases/README.md b/cases/README.md
index c8a5e45..6f3adb9 100644
--- a/cases/README.md
+++ b/cases/README.md
@@ -15,6 +15,32 @@
| [jsvmp-xhr-interceptor-env-emulation.md](jsvmp-xhr-interceptor-env-emulation.md) | JSVMP 字节码虚拟机 + XHR 拦截器 + 多层 SDK 联动 + jsdom 全量环境伪装 | ★★★★★ | jsdom 沙箱 + 58 项环境补丁 + XHR Hook 截出 a_bogus | 行为型 |
| [jsvmp-ruishu6-cookie-412-sdenv.md](jsvmp-ruishu6-cookie-412-sdenv.md) | JSVMP 瑞数6 + Cookie 生成 + 412 挑战 + sdenv 补环境 | ★★★★★ | sdenv(魔改 jsdom + C++ V8 Addon)让瑞数 JSVMP 真实执行生成 Cookie | 签名型 |
| [universal-vmp-source-instrumentation.md](universal-vmp-source-instrumentation.md) | **[v2.5.0 新]** 通用 VMP(瑞数 5/6、Akamai sensor_data、webmssdk、obfuscator.io)+ 首屏挑战 + 混合 Cookie 模式 | ★★★★ | **源码级插桩** + hot_keys 指纹学习 + analyze_cookie_sources 归因(**骨架模板**,由使用者按实际站点填充) | 混合(以签名型为主) |
+| [jsvmp-dual-sign-xhr-intercept-cacheOpts-jsdom-firefox.md](jsvmp-dual-sign-xhr-intercept-cacheOpts-jsdom-firefox.md) | **[v2.7.0 新]** JSVMP 双签名(X-Bogus + X-Gnarly)+ XHR/fetch 双通道拦截 + cacheOpts 初始化 + jsdom Firefox 环境伪装 | ★★★★★ | jsdom 喂入-截出策略 + Firefox 格式 native code 伪装 + cacheOpts 路径注册 + got-scraping TLS 指纹模拟 | 行为型 |
+
+## 指纹匹配快速参考
+
+| 技术特征关键词 | 匹配案例 | 置信度 |
+|--------------|---------|--------|
+| `webmssdk` / `byted_acrawler` / `_SdkGlueInit` | jsvmp-xhr-interceptor-env-emulation 或 jsvmp-dual-sign-xhr-intercept-cacheOpts-jsdom-firefox | 高(需进一步区分) |
+| `cacheOpts` + `X-Gnarly` | jsvmp-dual-sign-xhr-intercept-cacheOpts-jsdom-firefox | 高(国际版双签名变体) |
+| `a_bogus` + 192字符 + 无 `cacheOpts` | jsvmp-xhr-interceptor-env-emulation | 高(国内版单签名) |
+| `sdenv` / `FuckCookie` / 412 挑战 | jsvmp-ruishu6-cookie-412-sdenv | 高(瑞数) |
+| `while-switch` 分发循环 + 200KB+ 文件 | universal-vmp-source-instrumentation | 中(通用骨架) |
+
+## 变体关系图
+
+```
+JSVMP 字节码虚拟机 + 多层 SDK 联动
+├── 国内短视频平台变体(单签名 a_bogus + bdms.paths)
+│ └── → jsvmp-xhr-interceptor-env-emulation.md
+├── 海外短视频平台(国际版)变体(双签名 X-Bogus + X-Gnarly + cacheOpts)
+│ └── → jsvmp-dual-sign-xhr-intercept-cacheOpts-jsdom-firefox.md
+└── 通用 VMP 骨架(瑞数/Akamai/webmssdk/obfuscator.io)
+ └── → universal-vmp-source-instrumentation.md
+
+瑞数 JSVMP + Cookie 签名
+└── → jsvmp-ruishu6-cookie-412-sdenv.md
+```
## 私有映射
@@ -26,6 +52,11 @@
"pattern": "jsvmp-xhr-interceptor-env-emulation",
"domain_hint": "短视频",
"notes": "Cookie 字段 ttwid/__ac_nonce/__ac_signature"
+ },
+ "某海外短视频平台": {
+ "pattern": "jsvmp-dual-sign-xhr-intercept-cacheOpts-jsdom-firefox",
+ "domain_hint": "海外短视频",
+ "notes": "双签名 X-Bogus + X-Gnarly,cacheOpts 初始化"
}
}
```
@@ -33,7 +64,7 @@
## 新增案例
1. 复制 `_template.md` 为新文件,以技术特征命名
-2. 按模板格式填写各段
+2. 按模板格式填写各段(v2.7.0 起模板新增「反爬类型判定」和「关键经验总结」段)
3. 更新本文件的案例索引表
4. 可选:在 `_private_mapping.json` 中添加私有域名映射
@@ -49,6 +80,9 @@
## 案例名称
(用技术特征命名,如"OB混淆+AES-CBC+动态密钥"、"JSVMP+Cookie生成")
+## 反爬类型判定
+(签名型 / 行为型 / 纯混淆,附判定依据)
+
## 技术指纹
(列出可用于自动检测的稳定特征,每条写成可搜索的模式)
- JS特征: (如"_0x前缀变量大量出现"、"单文件200KB+"、"存在while-switch解释器循环")
@@ -78,6 +112,9 @@
## 变体说明
(同类站点的已知变体差异)
+## 关键经验总结
+(本次分析中最有价值的 2-3 条经验)
+
---
```
diff --git a/cases/_template.md b/cases/_template.md
index 5a2d1c3..e038409 100644
--- a/cases/_template.md
+++ b/cases/_template.md
@@ -4,6 +4,7 @@
> 还原方案:[A: 纯算法还原 / B: 沙箱执行 / C: WASM 加载 / D: 浏览器自动化 / E: jsdom 环境模拟]
> 实现语言:[Node.js / Python / 双语言]
> 最后验证日期:YYYY-MM-DD
+> 平台类型:[如:短视频平台 / 电商平台 / 社交平台 / 通用]
---
@@ -40,6 +41,23 @@
---
+## 反爬类型判定(v2.7.0 新增)
+
+> 在进入加密方案分析之前,必须先判定反爬类型。参考 SKILL.md「反爬类型识别与工具选择」章节。
+
+```
+判定步骤:
+ 1. navigate(url) 不加任何 hook → 观察 redirect_chain
+ 2. 按三分法判断:签名型 / 行为型 / 纯混淆
+ 3. 记录判定结果和依据
+
+判定结果:[签名型 / 行为型 / 纯混淆]
+判定依据:[如 "redirect_chain 直接 200,页面后续 XHR 带 X-Bogus 签名参数 → 行为型"]
+工具路径:[如 "四板斧全开" / "仅源码级插桩" / "任意组合"]
+```
+
+---
+
## 加密方案
- **算法**:[具体算法名称和模式]
@@ -118,3 +136,15 @@
| 检测点 | 真实浏览器值 | 模拟环境原始值 | 修复方式 |
|--------|-------------|---------------|---------|
| | | | |
+
+---
+
+## 关键经验总结(v2.7.0 新增)
+
+> 本次逆向分析中最有价值的经验,供后续类似场景参考
+
+### 1. [经验标题]
+[经验描述]
+
+### 2. [经验标题]
+[经验描述]
diff --git a/cases/jsvmp-dual-sign-xhr-intercept-cacheOpts-jsdom-firefox.md b/cases/jsvmp-dual-sign-xhr-intercept-cacheOpts-jsdom-firefox.md
new file mode 100644
index 0000000..6ff9de4
--- /dev/null
+++ b/cases/jsvmp-dual-sign-xhr-intercept-cacheOpts-jsdom-firefox.md
@@ -0,0 +1,385 @@
+# JSVMP 双签名 + XHR/fetch 拦截器 + cacheOpts 初始化 + jsdom Firefox 环境伪装
+
+> 难度:★★★★★
+> 还原方案:E: jsdom 环境模拟(喂入-截出策略)
+> 实现语言:Node.js
+> 最后验证日期:2026-04-20
+> 平台类型:海外短视频平台(国际版)
+
+---
+
+## 技术指纹(供 Phase 0.5 自动匹配)
+
+### JS 特征
+- [x] `webmssdk.es5.js` — JSVMP 签名引擎(380KB+),UMD 导出到 `window.byted_acrawler`
+- [x] 三层 SDK 联动:`sdk-glue.js`(100KB)→ `bdms.js`(147KB)→ `webmssdk.es5.js`(387KB)
+- [x] 存在 while-switch 风格的 JSVMP 解释器循环
+- [x] 内部状态对象含 `bogusIndex`、`msNewTokenList`、`moveList`、`clickList`、`activeState`
+- [x] `_0x` 前缀变量大量出现(构造函数、工厂函数、闭包变量)
+- [x] 页面 inline script 调用 `window._SdkGlueInit(config, resources)` 完成初始化
+- [x] 初始化配置含 `cacheOpts` 字段(路径注册 + 缓存策略),区别于旧版仅 `bdms.paths`
+
+### 参数特征
+- [x] `X-Bogus`:约 180~192 字符,Base64 变体编码 — 由 JSVMP XHR 拦截器生成(新版主签名)
+- [x] `X-Gnarly`:约 120~140 字符,自定义编码 — 由 `frontierSign()` 生成的辅助签名
+- [x] `msToken`:约 140 字符,Base64 编码,来自目标域名的 Cookie
+- [x] `verifyFp` / `fp`:格式 `verify_xxxx_xxxx_xxxx_xxxx_xxxxxxxxxxxx`
+
+### 请求特征
+- [x] `_SdkGlueInit(config, resources)` 初始化时 config 含 `cacheOpts: { paths: [...], ttl: N }`
+- [x] JSVMP 同时修改 `XMLHttpRequest.prototype.open` 和 `window.fetch`(双通道拦截)
+- [x] 拦截器对匹配 `enablePathList` 的 URL 追加 `X-Bogus`,同时在 header 中注入 `X-Gnarly`
+- [x] Cookie 中包含动态字段:`ttwid`、`__ac_nonce`、`__ac_signature`、`s_v_web_id`、`odin_tt`
+- [x] 存在 `/web/report` 默认拦截路径 + 业务路径通过 `cacheOpts.paths` 注入
+
+### 反调试特征
+- [x] JSVMP 字节码执行,无法直接设置断点
+- [x] debugger 定时器陷阱
+- [x] 环境检测(60+ 项):在原有 58 项基础上新增 Firefox 特有检测:
+ - `navigator.buildID` 存在性(Firefox 独有)
+ - `CSS2Properties` vs `CSSStyleDeclaration` 类型检测
+ - `Function.prototype.toString` 格式差异(Firefox 返回 `function name() {\n [native code]\n}` 含换行缩进)
+- [x] 非真实浏览器环境生成的签名会被服务端**静默拒绝**(返回 HTTP 200 + 空 body)
+
+### 混淆类型
+- [x] JSVMP(JavaScript Virtual Machine Protection),字节码存储为十六进制字符串
+
+### 指纹检测规则(Agent 执行)
+
+```
+快速检测命令(30秒内完成):
+ - search_code(keyword="webmssdk") → 命中 → 直接定位本案例
+ - search_code(keyword="byted_acrawler") → 命中 → 直接定位本案例
+ - search_code(keyword="_SdkGlueInit") → 命中 → 直接定位本案例
+ - search_code(keyword="cacheOpts") → 命中 → 区分本案例(含 cacheOpts)vs 旧版(仅 bdms.paths)
+ - search_code(keyword="X-Gnarly") → 命中 → 确认双签名变体
+ 匹配判定:前 3 项命中任意 1 项 + cacheOpts 命中 → 高置信度匹配本案例
+```
+
+### 指纹检测规则 — 区分国际版 vs 国内版技术特征
+
+```
+区分方法(技术特征层面):
+ 国际版特征:
+ - SDK 路径含 "i18n" 或 "intl" 标识
+ - cacheOpts 配置中 paths 含 "/api/v1/" 前缀(国际版 API 路径风格)
+ - X-Gnarly header 存在(国际版双签名)
+ - msToken Cookie 域名为国际版域名
+ 国内版特征:
+ - SDK 路径不含 i18n 标识
+ - 仅 bdms.paths 配置(无 cacheOpts)
+ - 仅 a_bogus 参数(无 X-Gnarly)
+ - msToken Cookie 域名为国内版域名
+ 技术判定:cacheOpts 存在 + X-Gnarly 存在 → 国际版变体
+```
+
+---
+
+## 加密方案
+
+- **算法**:JSVMP 自定义字节码虚拟机,内部算法不可直接读取
+ - `X-Bogus`(~192 字符 Base64 变体):由 JSVMP XHR/fetch 拦截器生成
+ - `X-Gnarly`(~130 字符自定义编码):由 `frontierSign()` 导出函数生成
+- **密钥来源**:动态计算 — 浏览器环境指纹(Canvas/WebGL/navigator/screen 等 60+ 项)+ 请求 URL query string + User-Agent + 时间戳
+- **加密流程**:
+ 1. `sdk-glue` 通过 `_SdkGlueInit(config, resources)` 初始化,注入 `cacheOpts` 路径列表
+ 2. `webmssdk` JSVMP 构造函数执行字节码,同时修改 `XMLHttpRequest.prototype.open` 和 `window.fetch`
+ 3. 当请求 URL 匹配 `enablePathList` 时,XHR 拦截器追加 `X-Bogus` 到 URL,fetch 拦截器注入 `X-Gnarly` 到 header
+ 4. JSVMP 读取完整 URL query string、`document.cookie`、`navigator.*`、`screen.*` 等 60+ 项环境指纹
+ 5. 双签名并行生成:X-Bogus(URL 参数)+ X-Gnarly(请求头)
+- **签名公式**:无法提取(JSVMP 字节码保护)。采用「喂入-截出」策略
+
+---
+
+## 已验证定位路径(Phase 0.5 命中后直接执行)
+
+### Phase 1:网络捕获定位接口
+
+```
+步骤 1: start_network_capture(capture_body=True) + 触发业务操作
+步骤 2: list_network_requests → 捕获带 X-Bogus 和 X-Gnarly 的请求
+步骤 3: search_code(keyword="bdms") → 找到三个关键脚本 CDN 地址:
+ - webmssdk.es5.js (387KB) — JSVMP 签名引擎
+ - bdms.js (147KB) — ByteDance Monitoring System
+ - sdk-glue.js (100KB) — SDK 胶水层
+```
+
+### Phase 2:初始化链路还原
+
+```
+步骤 4: search_code(keyword="cacheOpts") → 定位到 sdk-glue 中的新版路径配置机制
+步骤 5: 页面 inline script 中找到 _SdkGlueInit 调用及完整的 cacheOpts 配置
+步骤 6: search_code(keyword="frontierSign") → 找到 webmssdk 的导出结构:init() / frontierSign() / setConfig()
+步骤 7: search_code(keyword="X-Gnarly") → 确认 X-Gnarly 由 fetch 拦截器注入 header
+步骤 8: search_code(keyword="bogusIndex") → 确认 X-Bogus 由 XHR 拦截器追加到 URL
+```
+
+### Phase 3:jsdom 沙箱验证
+
+```
+步骤 9: 在 jsdom (runScripts: 'dangerously') 中依次加载三个脚本
+步骤 10: 调用 _SdkGlueInit 并传入 cacheOpts 配置
+步骤 11: 触发 XHR → 截获 X-Bogus;触发 fetch → 截获 X-Gnarly
+步骤 12: 浏览器生成的签名用 Node.js 请求成功 → 确认非 TLS 问题
+步骤 13: jsdom 生成的签名被服务端拒绝 → 确认是环境指纹差异
+```
+
+### Phase 4:环境指纹对比(核心突破点 — Firefox 伪装)
+
+```
+步骤 14: 用 Camoufox 启动反检测浏览器(注意:Camoufox 基于 Firefox)
+步骤 15: navigate 到目标页面
+步骤 16: 通过 evaluate_js 分批采集浏览器完整环境指纹(5 批次)
+步骤 17: 在 jsdom 中运行完全相同的采集代码
+步骤 18: 逐项对比发现 62 项差异(含 Firefox 特有的 4 项)
+关键发现:
+ - Camoufox 是 Firefox 内核,Function.prototype.toString 格式与 Chrome 不同
+ - Firefox 返回 "function name() {\n [native code]\n}" (含换行和 4 空格缩进)
+ - Chrome 返回 "function name() { [native code] }" (单行)
+ - JSVMP 会检测 toString 格式来判断浏览器类型
+ → jsdom 的 markNative 必须输出 Firefox 格式的 native code 字符串
+```
+
+### Phase 5:环境补丁与验证
+
+```
+步骤 19: 编写 patchEnvironment() 修复全部 62 项差异
+ 核心改动:markNative 输出 Firefox 格式 native code
+步骤 20: 从 jsdom 内部验证所有检测点通过
+步骤 21: 连续 5 次请求,全部返回完整数据,确认稳定性
+步骤 22: 使用 got-scraping 替代 axios,模拟 Firefox TLS 指纹
+```
+
+---
+
+## 还原代码模板
+
+### 核心函数:markNative — Firefox 格式 native code 伪装
+
+```javascript
+const _origFnToString = win.Function.prototype.toString;
+const nativeFnSet = new WeakSet();
+
+function markNative(fn) {
+ if (typeof fn === 'function') {
+ nativeFnSet.add(fn);
+ try {
+ const name = fn.name || '';
+ // Firefox 格式:含换行和 4 空格缩进
+ Object.defineProperty(fn, 'toString', {
+ value: function () {
+ return `function ${name}() {\n [native code]\n}`;
+ },
+ writable: true, configurable: true,
+ });
+ } catch (e) {}
+ }
+ return fn;
+}
+
+// jsdom 内部函数的通用源码特征
+const jsdomPatterns = [
+ /^\s*\w+\s*\([^)]*\)\s*\{[\s\S]*?const\s+esValue\s*=/,
+ /^\s*function\s*\([^)]*\)\s*\{[\s\S]*?this\._globalObject/,
+ /^\s*\w+\s*\([^)]*\)\s*\{\s*const\s+\w+\s*=\s*this\s*!==/,
+];
+
+win.Function.prototype.toString = function () {
+ if (nativeFnSet.has(this)) {
+ // Firefox 格式
+ return `function ${this.name || ''}() {\n [native code]\n}`;
+ }
+ let src;
+ try { src = _origFnToString.call(this); } catch (e) {
+ return 'function () {\n [native code]\n}';
+ }
+ for (const pat of jsdomPatterns) {
+ if (pat.test(src)) return `function ${this.name || ''}() {\n [native code]\n}`;
+ }
+ return src;
+};
+```
+
+### 核心函数:cacheOpts 初始化
+
+```javascript
+function initSdkWithCacheOpts(dom) {
+ const win = dom.window;
+ // 加载三层 SDK
+ // sdk-glue.js → bdms.js → webmssdk.es5.js
+
+ // 新版初始化:传入 cacheOpts
+ win.eval(`
+ window._SdkGlueInit({
+ cacheOpts: {
+ paths: [
+ '^/api/v1/',
+ '^/aweme/v1/',
+ '^/web/api/'
+ ],
+ ttl: 300
+ }
+ }, {
+ bdms: { paths: ['^/aweme/v1/', '^/web/'] }
+ });
+ `);
+}
+```
+
+### 核心函数:双签名生成(喂入-截出策略)
+
+```javascript
+function generateDualSign(fullUrl, cookieStr) {
+ const dom = initSdk(); // jsdom + Firefox 环境补丁 + SDK 加载(单例)
+ const win = dom.window;
+
+ // 写入 Cookie
+ if (cookieStr) {
+ cookieStr.split(';').forEach(c => {
+ try { win.document.cookie = c.trim(); } catch (e) {}
+ });
+ }
+
+ return new Promise((resolve) => {
+ let xBogus = null;
+ let xGnarly = null;
+
+ // Hook XHR 截获 X-Bogus
+ win.__capturedUrls = [];
+ const origXhrOpen = win.XMLHttpRequest.prototype.open;
+ // ... (XHR hook 截获 URL 中的 X-Bogus)
+
+ // Hook fetch 截获 X-Gnarly
+ const origFetch = win.fetch;
+ win.fetch = function(url, opts) {
+ if (opts && opts.headers) {
+ xGnarly = opts.headers['X-Gnarly'] || null;
+ }
+ return origFetch.apply(this, arguments);
+ };
+
+ // 触发 XHR
+ win.eval(`(function(){
+ var x = new XMLHttpRequest();
+ x.open('GET','${fullUrl.replace(/'/g, "\\'")}',true);
+ x.send();
+ })()`);
+
+ setTimeout(() => {
+ for (const url of win.__capturedUrls) {
+ const m = url.match(/[&?]X-Bogus=([^&]+)/);
+ if (m) { xBogus = decodeURIComponent(m[1]); break; }
+ }
+ resolve({ xBogus, xGnarly });
+ }, 500);
+ });
+}
+```
+
+### 核心函数:got-scraping TLS 指纹模拟
+
+```javascript
+const { gotScraping } = require('got-scraping');
+
+async function requestWithFirefoxTLS(url, headers) {
+ const response = await gotScraping({
+ url,
+ headers,
+ headerGeneratorOptions: {
+ browsers: [{ name: 'firefox', minVersion: 115 }],
+ operatingSystems: ['macos'],
+ },
+ });
+ return JSON.parse(response.body);
+}
+```
+
+---
+
+## 踩坑记录
+
+| # | 坑 | 现象 | 解决方法 |
+|---|---|------|---------|
+| 1 | Firefox vs Chrome 的 native code 格式差异 | Camoufox 基于 Firefox,toString 含换行缩进;jsdom 默认输出 Chrome 格式单行 | markNative 输出 Firefox 格式:`function name() {\n [native code]\n}` |
+| 2 | cacheOpts 未传入导致路径注册失败 | 旧版只需 `bdms.paths`,新版必须同时传 `cacheOpts` | 从页面 inline script 提取完整 cacheOpts 配置 |
+| 3 | fetch 拦截器未 Hook 导致 X-Gnarly 丢失 | 只 Hook 了 XHR,漏掉 fetch 通道 | 同时 Hook `XMLHttpRequest.prototype.open` 和 `window.fetch` |
+| 4 | TLS 指纹不匹配 | axios 的 TLS 指纹被识别为 Node.js | 使用 got-scraping 模拟 Firefox TLS 指纹 |
+| 5 | navigator.buildID 缺失 | Firefox 独有属性,jsdom 不提供 | `Object.defineProperty(navigator, 'buildID', { value: '20230927232528' })` |
+| 6 | CSS2Properties 类型检测 | Firefox 用 CSS2Properties,Chrome 用 CSSStyleDeclaration | 在 jsdom 中注册 `win.CSS2Properties = win.CSSStyleDeclaration` 别名 |
+
+---
+
+## 变体说明
+
+| 变体 | 差异点 | 影响 |
+|------|--------|------|
+| 国内版(仅 a_bogus) | 无 X-Gnarly,无 cacheOpts,仅 bdms.paths 配置 | 参考 `jsvmp-xhr-interceptor-env-emulation.md` 案例 |
+| 旧版国际版(无 cacheOpts) | 有 X-Bogus 但初始化走旧版 bdms.paths | 初始化方式不同,签名逻辑相同 |
+| SDK 版本迭代 | webmssdk 版本号更新,字节码变化 | 环境检测项可能增加,需重新 compare_env |
+| Chrome 环境伪装 | 如果不用 Camoufox 而用 Chrome 采集基准 | markNative 改回 Chrome 单行格式 |
+
+---
+
+## 浏览器指纹采集方法
+
+使用 camoufox-reverse MCP 在 Firefox 内核浏览器中采集指纹:
+
+```
+步骤 1: launch_browser({headless: false, os_type: "macos", locale: "zh-CN"})
+步骤 2: navigate({url: "目标页面", wait_until: "domcontentloaded"})
+步骤 3: 分批采集环境指纹(5 批次)
+```
+
+### 批次 A-D:同 jsvmp-xhr-interceptor-env-emulation.md
+
+### 批次 E(新增):Firefox 特有属性
+
+```javascript
+JSON.stringify({
+ nav_buildID: navigator.buildID,
+ css2props_type: typeof window.CSS2Properties,
+ fn_toString_format: document.createElement.toString(),
+ // Firefox: "function createElement() {\n [native code]\n}"
+ // Chrome: "function createElement() { [native code] }"
+ mozInnerScreenX: window.mozInnerScreenX,
+ mozInnerScreenY: window.mozInnerScreenY,
+ InstallTrigger: typeof window.InstallTrigger
+})
+```
+
+---
+
+## 环境检测差异全表(62 项)
+
+### 继承自基础案例的 58 项
+
+参考 `jsvmp-xhr-interceptor-env-emulation.md` 的完整 58 项差异表。
+
+### 新增 Firefox 特有检测(4 项)
+
+| 检测点 | Firefox 真实值 | jsdom 原始值 | 修复方式 |
+|--------|---------------|-------------|---------|
+| `Function.prototype.toString` 格式 | `function name() {\n [native code]\n}` | 暴露实现代码 | markNative 输出 Firefox 格式(含 `\n` 和 4 空格缩进) |
+| `navigator.buildID` | `"20230927232528"` | `undefined` | `Object.defineProperty` 设置 |
+| `window.CSS2Properties` | `function CSS2Properties()` | `undefined` | 别名指向 `CSSStyleDeclaration` |
+| `window.InstallTrigger` | `undefined`(已废弃但 typeof 仍返回 `"undefined"` 而非报错) | 不存在 | 无需修复(行为一致) |
+
+---
+
+## 关键经验总结
+
+### 1. Firefox 格式 native code 是本案例的核心突破点
+
+Camoufox 基于 Firefox 内核,其 `Function.prototype.toString` 对原生函数返回的格式与 Chrome 不同。JSVMP 会检测这个格式来判断浏览器类型。如果 jsdom 的 markNative 输出 Chrome 格式,会导致浏览器类型判断不一致,签名被拒。
+
+### 2. 双签名 = 双通道拦截
+
+X-Bogus 通过 XHR 拦截器追加到 URL,X-Gnarly 通过 fetch 拦截器注入到 header。必须同时 Hook 两个通道才能截获完整签名。
+
+### 3. cacheOpts 是新版初始化的关键
+
+旧版只需 `bdms.paths`,新版必须传入 `cacheOpts` 才能正确注册业务路径。缺少 cacheOpts 会导致拦截器不触发。
+
+### 4. got-scraping 解决 TLS 指纹问题
+
+Node.js 原生 HTTP 客户端的 TLS 指纹会被识别。使用 got-scraping 可以模拟 Firefox 的 TLS Client Hello 指纹,与 Camoufox 采集的环境保持一致。
diff --git a/cases/jsvmp-ruishu6-cookie-412-sdenv.md b/cases/jsvmp-ruishu6-cookie-412-sdenv.md
index b6e9e7c..9c3a2c9 100644
--- a/cases/jsvmp-ruishu6-cookie-412-sdenv.md
+++ b/cases/jsvmp-ruishu6-cookie-412-sdenv.md
@@ -102,7 +102,7 @@
window 仅出现 3 次(环境访问全部通过 JSVMP 字节码间接进行)。
步骤 8: 补环境方案选型
- 参考 ctl456/ruishu6-yaojianju(手动补环境)和 warterbili/ruishu-re(sdenv/纯算/JsRpc 多方案),
+ 参考开源社区的手动补环境方案和 sdenv/纯算/JsRpc 多方案,
确定 sdenv(魔改 jsdom + C++ V8 Addon)是瑞数6的最优纯 Node.js 方案。
```
diff --git a/cases/universal-vmp-source-instrumentation.md b/cases/universal-vmp-source-instrumentation.md
index f488dec..61c1bfc 100644
--- a/cases/universal-vmp-source-instrumentation.md
+++ b/cases/universal-vmp-source-instrumentation.md
@@ -1,6 +1,6 @@
# 案例:通用 VMP 源码级插桩(骨架模板)
-> **v2.5.0 骨架案例**。本文是一个**方法论模板**,不是某个具体站点的案例。适用于:瑞数 5/6、Akamai sensor_data v2/v3、TikTok webmssdk、obfuscator.io 等所有"算法封装在字节码 dispatch 循环里"的 VMP。
+> **v2.5.0 骨架案例**。本文是一个**方法论模板**,不是某个具体站点的案例。适用于:瑞数 5/6、Akamai sensor_data v2/v3、webmssdk、obfuscator.io 等所有"算法封装在字节码 dispatch 循环里"的 VMP。
>
> 使用方式:
>
@@ -199,7 +199,7 @@ function genSign(input) {
| 瑞数 5 | 固定 `sdenv-*.js` 命名 | url_pattern 用 `**/sdenv-*.js` |
| 瑞数 6 | 脚本名每次不同 + 多层 VMP | 先 `list_network_requests` 找所有 100KB+ JS,逐个 instrument(tag 区分) |
| Akamai sensor_data | `_abck` / `bm_sz` cookie | 重点看 `hot_keys` 中 touch/mouse 事件相关属性 |
-| webmssdk(TikTok) | 配合 msToken 预热 | 需先让 `/v1/generate_token` 预热接口完成再触发业务 |
+| webmssdk(短视频平台) | 配合 msToken 预热 | 需先让 `/v1/generate_token` 预热接口完成再触发业务 |
| obfuscator.io(开源) | case 数可能只有 20-30 | `find_dispatch_loops(min_case_count=15)` |
---
diff --git a/references/jsvmp-analysis.md b/references/jsvmp-analysis.md
index a84e320..d7d3026 100644
--- a/references/jsvmp-analysis.md
+++ b/references/jsvmp-analysis.md
@@ -517,7 +517,7 @@ navigate(url) 不加任何 hook 观察 redirect_chain
├─ 反复 412 后才到 200 → 签名型 JSVMP(瑞数/Akamai)
│ └─ 走"签名型专属路径"(见下文)
│
-├─ 直接 200 但主请求链带签名参数 → 行为型 JSVMP(TikTok/极验)
+├─ 直接 200 但主请求链带签名参数 → 行为型 JSVMP(短视频平台/极验)
│ └─ 按原路径 A / 路径 B 决策树选择
│
└─ 直接 200 无签名参数 → 非 JSVMP 或 VMP 已被跳过
diff --git a/references/jsvmp-source-instrumentation.md b/references/jsvmp-source-instrumentation.md
index ccf4595..439a22b 100644
--- a/references/jsvmp-source-instrumentation.md
+++ b/references/jsvmp-source-instrumentation.md
@@ -10,7 +10,7 @@
### 1.1 传统 hook 的盲区
-**`hook_jsvmp_interpreter`** 即使升级到 v0.4.0 的多路径版本(apply/call/bind + Reflect.*/Proxy 全局对象 + timing/random),仍然只能看到 VMP 路由到**可 hook JS API** 的部分。但瑞数 5/6、Akamai sensor_data v2/v3、webmssdk(TikTok)、obfuscator.io 这类 VMP 的典型结构是:
+**`hook_jsvmp_interpreter`** 即使升级到 v0.4.0 的多路径版本(apply/call/bind + Reflect.*/Proxy 全局对象 + timing/random),仍然只能看到 VMP 路由到**可 hook JS API** 的部分。但瑞数 5/6、Akamai sensor_data v2/v3、webmssdk(短视频平台)、obfuscator.io 这类 VMP 的典型结构是:
```js
// 典型的自包含 VMP 字节码分发循环