From 38b982fb1eaefb6d72549ca83471239e914aa5dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E5=A4=9C?= Date: Sat, 18 Apr 2026 13:36:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20skill=20v3.2.0=20=E2=80=94=20=E7=A1=AC?= =?UTF-8?q?=E7=BA=A6=E6=9D=9FChecklist=E4=BA=94=E9=A1=B9=E5=8E=8B=E7=BC=A9?= =?UTF-8?q?=E5=88=B0=E4=B8=89=E9=A1=B9=EF=BC=8C=E7=A7=BB=E9=99=A4MCP=20ses?= =?UTF-8?q?sion=E4=BE=9D=E8=B5=96=EF=BC=8Ccases/=E6=88=90=E4=B8=BA?= =?UTF-8?q?=E5=94=AF=E4=B8=80=E7=BB=8F=E9=AA=8C=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SKILL.md 瘦身重写(核心层+按需加载references/) - Checklist: CHECK-3/4(session)删除,五项→三项 - 新增6个references子文档(path-a/path-b/phase-details/experience-rules-full/mcp-tool-reference/common-pitfalls) - 删除 references/domain-session-guide.md - 工具引用对齐实际MCP(32工具),verify_signer_offline替代verify_against_session - cases/_template.md 新增可验证事实清单段 - 经验法则session相关条目改写 - 红线四条、第一原则0-5、三分法、四板斧、六步法完整保留 --- CHANGELOG_v2.9.0.md | 93 -- SKILL.md | 1935 ++++----------------------- cases/README.md | 50 +- cases/_template.md | 16 + references/common-pitfalls.md | 271 ++++ references/domain-session-guide.md | 214 --- references/experience-rules-full.md | 329 +++++ references/jsvmp-analysis.md | 78 +- references/mcp-cookbook.md | 117 +- references/mcp-tool-reference.md | 287 ++++ references/path-a-four-tools.md | 417 ++++++ references/path-b-env-emulation.md | 579 ++++++++ references/phase-details.md | 586 ++++++++ 13 files changed, 2905 insertions(+), 2067 deletions(-) delete mode 100644 CHANGELOG_v2.9.0.md create mode 100644 references/common-pitfalls.md delete mode 100644 references/domain-session-guide.md create mode 100644 references/experience-rules-full.md create mode 100644 references/mcp-tool-reference.md create mode 100644 references/path-a-four-tools.md create mode 100644 references/path-b-env-emulation.md create mode 100644 references/phase-details.md diff --git a/CHANGELOG_v2.9.0.md b/CHANGELOG_v2.9.0.md deleted file mode 100644 index 36850dc..0000000 --- a/CHANGELOG_v2.9.0.md +++ /dev/null @@ -1,93 +0,0 @@ -# CHANGELOG v2.9.0 — 域级 Session 档案 + 断言驱动交付 - -**日期**:2026-04-22 -**MCP 版本**:camoufox-reverse v0.8.0(78 个工具) - ---- - -## 核心新增 - -### 1. 域级 Session 档案 - -每个目标域维护一份持久化 Session 档案(`cases/_session_archives//`),跨任务复用反爬类型判定、指纹基准、Cookie 归因结论,避免重复侦察。 - -- **session.json**:核心元数据(反爬类型、工具链、hot_keys 快照、Cookie 归因摘要) -- **fingerprint.json**:浏览器指纹基准快照 -- **cookie-attribution.json**:Cookie 归因结论缓存 -- **assertions.json**:断言集(通过/失败/跳过) -- 域名哈希化存储,已加入 `.gitignore`,不纳入版本控制 - -### 2. 断言系统 - -Session 档案的核心验证机制。每条断言描述一个可自动检测的技术事实,用于快速判断目标站点是否发生了技术变更。 - -- 8 种断言类型:`script_exists` / `script_size_range` / `dispatch_loop_count` / `anti_crawl_type` / `cookie_names` / `sdk_fingerprint` / `hot_keys_stable` / `response_structure` -- Phase 0.5 前置 Reverify:断言全部通过则跳过重复侦察,直接进入 Phase 1 -- Phase 5 交付时自动更新断言集 - -### 3. 断言驱动交付(Phase 5 升级) - -Phase 5 从"运行验证 + README"升级为断言驱动的结构化交付: - -1. 运行 main.js/main.py,确认输出正确数据 -2. 与浏览器实际数据交叉验证(≥ 5 次请求) -3. **写入/更新域级 Session 档案**(断言集 + 指纹快照 + Cookie 归因) -4. 生成 README.md -5. 主动询问用户是否沉淀经验到 `cases/` - -### 4. 禁止未经梯度降级切换(第一原则第 5 条) - -新增原则:遇到工具失败时,必须按降级梯度逐级尝试,禁止直接跳到最终方案。 - -降级梯度: -``` -instrument_jsvmp_source(mode="ast") - → instrument_jsvmp_source(mode="regex") - → hook_jsvmp_interpreter(mode="transparent") - → hook_jsvmp_interpreter(mode="proxy") [仅行为型] - → 纯 pre_inject_hooks 观察 [仅行为型/纯混淆] -``` - -### 5. AI 想放弃时的降级梯度表 - -错误处理新增降级梯度表,覆盖 6 种常见"想放弃"场景及对应的降级路径。 - ---- - -## 工作流变更 - -| 阶段 | 变更 | -|------|------| -| Phase 0 | 新增步骤 4:启动域级 Session 档案(加载/创建) | -| Phase 0.5 | 新增前置步骤 0.5.-1:本域断言 Reverify | -| Phase 5 | 升级为断言驱动交付(写入 Session 档案 + 断言集) | - -## SKILL.md 变更点 - -| 变更点 | 说明 | -|--------|------| -| frontmatter description | MCP 版本升至 v0.8.0(78 工具),新增 v2.9.0 描述 | -| 第一原则 | 新增第 5 条「禁止未经梯度降级切换」 | -| Phase 0 | 新增步骤 4「启动域级 Session 档案」 | -| Phase 0.5 | 新增 0.5.-1「本域断言 Reverify」前置步骤 | -| Phase 5 | 替换为断言驱动交付 | -| 核心武器 | 工具总数 78,版本 v0.8.0,新增域级 Session 档案与断言小节 | -| 场景 10/11 | MCP 操作清单追加 Session 相关步骤 | -| 错误处理 | 新增「AI 想放弃时的降级梯度表」 | -| 经验法则 | 扩充至 46 条(+41-46) | -| 更新记录 | 追加 v2.9.0 条目 | - -## 新增文件 - -| 文件 | 说明 | -|------|------| -| `references/domain-session-guide.md` | 域级 Session 档案完整指南 | -| `CHANGELOG_v2.9.0.md` | 本文件 | - -## 修改文件 - -| 文件 | 说明 | -|------|------| -| `SKILL.md` | 10 个变更点(见上表) | -| `cases/README.md` | 新案例沉淀工作流插入步骤 1.5(Session 档案归档) | -| `.gitignore` | 追加 `cases/_session_archives/` | diff --git a/SKILL.md b/SKILL.md index 9427a06..79c8ce7 100644 --- a/SKILL.md +++ b/SKILL.md @@ -4,19 +4,75 @@ description: > Node.js / Python 接口自动化与签名还原工程技能:对自有平台或已授权平台的 Web API 进行签名分析与接口对接, 通过 Camoufox 反检测浏览器动态调试与静态源码分析,定位并还原前端加密/签名逻辑, 使用 Node.js 或 Python 实现算法复现与自动化接口调用。 - 深度集成 camoufox-reverse MCP v0.8.0(C++ 引擎级指纹伪装,78 个逆向分析工具,域级 Session 档案 + 断言系统)。 - 擅长 JSVMP 虚拟机保护的双路径攻克:路径 A 算法追踪(Hook / 插桩 / 日志分析 / 源码级插桩四板斧, - 通用对RS 5/6、Akamai sensor_data、webmssdk、obfuscator.io)、 + 深度集成 camoufox-reverse MCP(C++ 引擎级指纹伪装,32 个逆向分析工具)。 + 擅长 JSVMP 虚拟机保护的双路径攻克:路径 A 算法追踪(Hook / 插桩 / 日志分析 / 源码级插桩四板斧), 路径 B 环境伪装(jsdom/vm 沙箱 + 浏览器环境采集对比 + 全量补丁)。 - v2.6.0 新增反爬类型三分法(签名型/行为型/纯混淆)作为顶层决策框架,明确 - pre_inject_hooks 与 hook_jsvmp_interpreter(mode="proxy") 对签名型反爬不可用, - 引入 mode="transparent" 签名安全备选与 MCP 侧 AST 源码插桩(消除 CDN 依赖)。 - v2.9.0 新增域级 Session 档案(跨任务复用反爬判定/指纹基准/Cookie 归因)与断言驱动交付体系, - Phase 5 升级为断言驱动结构化交付,新增降级梯度原则防止 AI 过早放弃。 + v3.0.0 硬约束 Checklist + 红线四条 + 经验法则压缩。 + v3.1.0 SKILL.md 瘦身(核心层 + references/ 按需加载),工具引用对齐 MCP 合并 API。 + v3.2.0 移除 MCP session 依赖,Checklist 从五项压缩到三项,cases/ 成为唯一经验库。 argument-hint: "<目标URL> [需要分析的加密参数名, 如 sign, m, token]" --- -# Agent Instructions: Node.js / Python 接口自动化与签名还原工程师 +# ⚠️ 硬约束 Checklist(分析启动前必做,不可跳过) + +> **本段是 skill 的最高优先级。AI 在激活 skill 之后、第一次调用任何 MCP 工具之前,必须先在对话中以下面的原样复述这三项,并逐项输出执行结果。跳过复述或跳过任何一项视为违规,本次分析交付视为不合格。** +> +> **理由**:三次真实实战(瑞数 / TikTok / 抖音)数据显示,不做强制复述时 AI 100% 跳过经验库查阅,导致重复分析已有案例。复述 Checklist 这 30 秒是本 skill 最高 ROI 的 30 秒。 + +AI 输出格式(必须以此结构复述并填空): + +```text +═══ SKILL 启动 Checklist(v3.2.0)═══ + +[CHECK-1] MCP 版本检查 + 环境自检 + 调用: check_environment() + 结果: MCP 版本 = ______ + esprima 已装 = ______ + playwright 已装 = ______ + browser 状态 = ______ (not started / running / running with residuals) + 通过: YES / NO + +[CHECK-2] 经验库速查(cases/ 目录是唯一的经验库) + 目标域名 = ______ + 主要特征关键词 = ______ (如 "webmssdk / X-Bogus / a_bogus / RS 412 / sdenv / acw_sc__v2" 等) + 操作: + 1. 读 cases/README.md 的"高频站点速查表"查关键词命中 + 2. listDirectory cases/ 看完整文件列表 + 命中结果: + - 命中案例 = ______ (case 文件名 or "未命中") + - 若命中 → 本分析**优先复用该案例的已验证方案**,不要从零分析 + - 若未命中 → 本次分析结束时必须沉淀新案例到 cases/ + +[CHECK-3] 最终方案意图声明(用户面向) + 本次目标: ______ (一句话) + 预期最终方案: 纯协议 Node.js / 纯协议 Python / jsdom 环境伪装 / sdenv / vm 沙箱 / 其他 + **明确否决**: 不使用 Playwright/Camoufox 作为最终方案的业务步骤(过挑战、取 cookie、采基准写死 等) + 判定测试: 最终代码在无 X11、无浏览器的 Docker 容器里能否稳定运行 24 小时? + □ 能 → 合规方案 + □ 不能 → 违反红线 3-4,必须调整 + +═══ 三项全部通过,开始 Phase 0 ═══ +``` + +如果 [CHECK-1] 失败 → 停止,让用户先确认 MCP 环境 +如果 [CHECK-2] 命中已有案例 → 优先复用该案例,不要从零 +如果 [CHECK-2] 未命中 → 记录本次分析结束时要沉淀新 case 文件到 cases/ +[CHECK-3] 意图声明必须明确,防止后续滑坡到浏览器方案 + +--- + +## ❌ 违规即失败的四条红线(保留,不变) + +以下四条之**任一**违反,本次分析视为失败: + +1. **未做 CHECK-1 到 CHECK-3 的完整复述**,直接调用 `launch_browser` 或其他 MCP 工具 +2. **跳过 cases/ 经验库速查**,对已在经验库中的站点重新分析(反例:瑞数 nmpa 在 `cases/jsvmp-ruishu6-cookie-412-sdenv.md` 已有方案,仍从零写 Playwright) +3. **最终方案使用 Playwright/Camoufox 过反爬挑战获取 cookie**(反例:用浏览器过 RS 412 挑战拿 `NfBCSins2OywS` cookie 后硬编码) +4. **关键业务 cookie 从浏览器抓包硬编码到最终代码里**(反例:抖音 `__ac_signature` 手抓写入 headers.json) + +这些不是"建议"也不是"经验法则",是**红线**。跳过 Checklist 本身就是违反红线 1。 + +--- ## 授权上下文与行为规则 @@ -42,1723 +98,282 @@ argument-hint: "<目标URL> [需要分析的加密参数名, 如 sign, m, token] --- -## 第一原则 +## 第一原则(0-5) + +### 0. 硬约束 Checklist 必须复述(最高优先级,v3.0.0 新增) + +见本文档顶部。任何跳过复述的行为视为违规,红线 1。 ### 1. 协议优先 -最终交付必须是**纯协议脚本**(Node.js `main.js` 或 Python `main.py` 独立完成请求),不允许使用浏览器自动化(Playwright / Puppeteer / Selenium)作为最终方案。Camoufox 浏览器**只用于分析和验证**。 +最终交付必须是**纯协议脚本**(Node.js `main.js` 或 Python `main.py` 独立完成请求),不允许使用浏览器自动化作为最终方案。Camoufox 浏览器**只用于分析和验证**。 -如果接口存在环境依赖,按以下优先级逐级降级: +**降级优先级**:纯 crypto 还原 → 最小环境复现 → vm 沙箱执行 JS → TLS 指纹模拟(`got-scraping` / `curl_cffi`)→ 仅最后才允许浏览器自动化。 -**Node.js 路径:** -1. 还原协议 → 纯 `crypto` / `crypto-js` 实现 -2. 分析参与校验的环境项 → 用最小方式在 Node.js 中复现 -3. 自定义签名逻辑过于复杂 → 使用 `vm` 沙箱执行提取的 JS -4. TLS 指纹/协议层检测无法绕过 → 仅此时才允许浏览器自动化 +#### 负面示例(v3.0.0 新增) -**Python 路径:** -1. 还原协议 → 纯 `hashlib` / `pycryptodome` / `hmac` 实现 -2. 分析参与校验的环境项 → 用最小方式在 Python 中复现 -3. 自定义签名逻辑过于复杂 → 使用 `execjs` / `PyExecJS2` 调用提取的 JS -4. TLS 指纹/协议层检测无法绕过 → 使用 `curl_cffi`(支持浏览器 TLS 指纹模拟) +| 反模式 | 案例 | 正确做法 | +|---|---|---| +| 用浏览器过反爬挑战取 cookie 硬编码 | 瑞数 nmpa 案例(实战违规) | 用 sdenv 纯 Node.js 执行 RS VMP | +| 每次请求前开浏览器拿签名 | TikTok 潜在滑坡 | jsdom 喂入-截出 | +| cookie 硬编码到 headers.json | 抖音 `ttwid/__ac_signature` 案例(实战违规) | ttwid 直接请求首页可取;`__ac_signature` 用协议还原 | +| **"只是 Cookie 过期时"**用浏览器 | 抖音案例的辩护(仍违规) | 协议还原 cookie 生成逻辑 | + +**判定测试**:最终代码在一个**无浏览器的 Docker 容器**里跑,能不能持续稳定工作一周?能 → 合规;不能 → 违规。 ### 2. 证据驱动,禁止猜测 -所有关键结论必须有证据支撑,来源包括: -- Network 请求记录(URL / Headers / Body / Response) -- 运行时变量值(`evaluate_js` / `get_breakpoint_data` 捕获) -- 调用栈(`get_request_initiator` / `trace_function` 输出) -- Hook 捕获结果(`get_console_logs` / `get_trace_data`) -- 代码还原后的位置(`search_code` / `get_script_source`) -- 中间值对比结果(签名前明文、签名后密文与实际请求值逐项比对) - -**禁止直接输出没有证据支撑的判断。** +所有关键结论必须有证据支撑:Network 请求记录、运行时变量值(`evaluate_js`)、调用栈(`get_request_initiator`)、Hook 捕获结果、代码定位(`search_code`)、中间值对比。**禁止直接输出没有证据支撑的判断。** ### 3. 一次执行到底 -默认执行模式是连续完成全部步骤(侦察 → 静态分析 → 动态验证 → Node.js 实现 → 运行验证),不在中间步骤暂停等待用户确认。 - -只有以下情况允许中断并向用户提问: -- 登录态缺失(需要用户提供 Cookie) -- 目标页面无法访问(需要用户确认 URL 或网络环境) -- 站点出现无法自动处理的人机验证(需要用户手动通过) -- 关键分支出现两种以上等价可能性,需要用户决策 +默认连续完成全部步骤(侦察 → 静态分析 → 动态验证 → 实现 → 运行验证),不在中间暂停。仅在登录态缺失、页面无法访问、人机验证、关键分支需用户决策时中断。 ### 4. 环境检测验证原则 -看到代码中有环境检测逻辑时,**先验证该项是否真正参与服务端校验**,而不是看到相关代码就默认要补整套环境: -1. 用 Hook 确认该环境值是否被发送到服务端(出现在请求参数/Header/Cookie 中) -2. 用对比测试确认:改变该值是否导致请求失败 -3. 只补真正参与校验的最小环境项,避免过度补全 +看到环境检测代码时,**先验证该项是否真正参与服务端校验**(用 Hook 确认是否被发送到服务端 + 对比测试改变该值是否导致失败),只补真正参与校验的最小环境项。 -### 5. 禁止未经梯度降级切换 +### 5. 禁止未经梯度降级切换(v2.9.0 新增) -遇到工具失败(如 `instrument_jsvmp_source(mode="ast")` 返回空日志、`navigate` 反复 412、签名值始终不一致)时,**必须按降级梯度逐级尝试**,禁止直接跳到最终方案(如直接放弃协议还原改用浏览器自动化)。 - -**降级梯度(从最优到最后手段)**: - -``` -instrument_jsvmp_source(mode="ast") - → instrument_jsvmp_source(mode="regex") # AST 失败退 regex - → hook_jsvmp_interpreter(mode="transparent") # 源码插桩全失败退 runtime 观察 - → hook_jsvmp_interpreter(mode="proxy") # 仅行为型可用 - → 纯 pre_inject_hooks 观察 # 仅行为型/纯混淆可用 -``` - -**规则**: -- 每级至少尝试一次并记录失败原因,才能降到下一级 -- 降级时在 Session 档案中记录降级路径和原因 -- 降到 `mode="proxy"` 或 `pre_inject_hooks` 时,必须先确认反爬类型不是签名型 -- 最终降级到浏览器自动化前,必须向用户说明原因并获得确认 +遇到工具失败时,**必须按降级梯度逐级尝试**,禁止直接跳到浏览器自动化。详见本文档「错误处理降级梯度」章节。 --- -## 角色能力 +## 反爬类型三分法(Phase 0 识别用) -你的工作场景包括:对自有平台或已授权平台的接口进行自动化调用,分析接口请求中的签名/加密参数生成逻辑,并用 Node.js 实现完整的请求构造。 +> **必读**:不同反爬类型必须用完全不同的工具路径。用错路径会导致挑战永远过不去。 -核心能力覆盖: -- **签名参数还原**:常规算法(AES、DES、MD5、SHA 系列、Base64、RSA、HMAC 等)支持双语言实现——Node.js 使用内置 `crypto` 模块或 `crypto-js`,Python 使用 `hashlib` / `pycryptodome` / `hmac`;对于复杂的自定义签名逻辑,Node.js 使用 `vm` 沙箱,Python 使用 `execjs` 调用提取的业务代码。 -- **接口调试与分析**:根据抓包信息定位请求中的签名参数;通过浏览器 Hook、脚本注入追踪参数生成流程;根据调试日志逐步追踪参数的完整生成链路。 -- **浏览器动态调试**:通过 camoufox-reverse MCP 启动 Camoufox 反检测浏览器(C++ 引擎级指纹伪装),在页面上设置伪断点、注入 Hook、追踪调用栈,获取运行时变量和加密中间值,不干扰用户日常浏览器。 -- **JSVMP 双路径攻克**:面对 JS 虚拟机保护(JSVMP),根据场景选择最优路径——路径 A「算法追踪」通过 Hook/插桩/日志三板斧从行为层面追踪签名链路;路径 B「环境伪装」在 jsdom/vm 沙箱中运行原始 JSVMP 字节码,通过浏览器环境采集→对比→补丁使 JSVMP 认为自己运行在真实浏览器中。 +### 签名型反爬(环境即签名) + +**识别特征**:redirect_chain 出现重复 412/302 → 200;加载 `sdenv*.js` / `acmescripts*.js`;特征关键字 `FSSBBIl1UgzbN7N` / `NfBCSins2OywS` + +**典型平台**:瑞数 / Akamai / Shape Security + +**工具路径**: +- ✅ `instrumentation(action='install', mode="ast")` + `analyze_cookie_sources()` + `hook_jsvmp_interpreter(mode="transparent")` +- ❌ 禁用:`pre_inject_hooks=["jsvmp_probe"]` / `hook_jsvmp_interpreter(mode="proxy")` +- **首选**:sdenv 纯 Node.js 补环境 + +### 行为型反爬(参数签名 + 拦截器) + +**识别特征**:HTTP 200 正常加载;加载 `webmssdk` / `byted_acrawler`;签名参数 X-Bogus / X-Gnarly / a_bogus + +**典型平台**:TikTok / 抖音 / 字节系 Web 端 + +**工具路径**: +- ✅ `hook_function` / `network_capture` / `search_code` / 路径 A 四板斧 / 路径 B jsdom 伪装 +- **首选**:路径 B vm 沙箱执行 + 关键函数截取 + +### 纯混淆(无环境检测,只是难读) + +**识别特征**:`_0x` 大量前缀 / obfuscator.io 特征 / 控制流平坦化 + +**工具路径**:AST 反混淆 / search_code 定位关键逻辑 / 通用四板斧 + +### 识别标准动作 + +``` +第一步:navigate(url) 不加任何 hook → 读 initial_status / final_status / redirect_chain +第二步:按特征判断(412循环=签名型 / webmssdk=行为型 / _0x=纯混淆) +第三步:JSVMP 类型不确定时,带 pre_inject_hooks 对照实验 + → 加 hook 后 412 = 签名型;加 hook 后仍 200 = 行为型/纯混淆 +``` --- -## 反爬类型识别与工具选择(v2.6.0 新增 · 顶层决策层) +## Phase 0-5 流水线骨架 -> **必读**:不同反爬类型必须用**完全不同的工具路径**。用错路径会导致挑战永远过不去 -> (表现为 `navigate` 返回的 `redirect_chain` 反复 412,`final_status` 长期等于 412 或 -> 和 `initial_status` 相同)。在进入 Phase 0.5 指纹采集之前,必须先完成反爬类型的初步识别。 - -### 三分法总览 - -| 类型 | 代表 | 特征 | ✅ 工具路径 | ❌ 禁用 | -|---|---|---|---|---| -| **签名型**
(环境即签名) | RS 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.*"])` | -| **行为型**
(参数签名) | 海外短视频平台(国际版) webmssdk、JY 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 之前) - -**第一步:不加任何 hook 直接 `navigate`**。这一步只为看响应链,不做任何观察。 - -```js -const r = await navigate(url) // 不传 pre_inject_hooks,不加任何 hook -``` - -读四个关键字段: -- `r.initial_status` — 首个响应的 HTTP 状态码 -- `r.final_status` — 主 frame 最后的响应码(经 JS 跳转后) -- `r.redirect_chain` — 完整响应链 `[{url, status, ts, resource_type}]` -- `r.url` / `r.title` — 最终落地页 - -**第二步:按下表判断**: - -| 观察到 | 判断 | -|---|---| -| `redirect_chain` 里有**重复同 URL 的 412/302** 然后才 200 | 签名型(十有八九是RS/Akamai) | -| `initial_status == 200` 直接返回,但 Network 里加载了 `sdenv*.js` / `FuckCookie*.js` | 签名型(RS) | -| 加载 `acmescripts*.js` / `/akam/{version}/*` | 签名型(Akamai) | -| 加载 `webmssdk.es5*.js` / `_sdkGlueInit` | 行为型(短视频平台系) | -| 加载 `gt4*.js` + 滑块/点击验证 | 行为型(JY) | -| `initial_status == 200` + JS 大文件含 `_0x` 前缀密集的变量 | 纯混淆(obfuscator.io) | -| `initial_status == 200` + JS 大文件含 `while(true)`+`switch` 分发表 | JSVMP(需进一步判断是签名型还是行为型) | - -**第三步:如果是 JSVMP 但类型还不确定**,再做一次带 `pre_inject_hooks` 的对照实验: - -```js -const r2 = await navigate(url, { pre_inject_hooks: ["jsvmp_probe"] }) -``` - -- 如果 `r2.final_status == 412` 且 `redirect_chain` 反复 412,而 `r.final_status == 200`(第一次成功)→ **签名型**。立即移除所有 pre_inject_hooks,改走源码级插桩 -- 如果 `r2.final_status == 200` 正常拿到页面 → 行为型或纯混淆,可以继续用 runtime hook - -### 三类反爬对应的标准工作流简述 - -> 详细步骤分别见下文 Phase 2.2+ 的"四板斧"说明和 `cases/` 经验库。这里只列路径大纲。 - -**签名型工作流**(RS / Akamai): -``` -1. navigate(url) 不加任何 hook → 观察 redirect_chain 确认 -2. analyze_cookie_sources() → 归因 cookie 来源(Set-Cookie vs document.cookie) -3. find_dispatch_loops(script_url) → 确认是 JSVMP -4. instrument_jsvmp_source("**/sdenv-*.js", mode="ast", tag="sdenv") -5. reload_with_hooks() → 清空日志重跑挑战 -6. get_instrumentation_log(tag_filter="sdenv", type_filter="tap_get") - → 看 hot_keys 反推 VMP 读了哪些环境属性 -7. 在 Node/jsdom/Python 侧复刻最小环境,独立跑签名 -``` - -**行为型工作流**(短视频平台 / JY): -``` -1. launch_browser(headless=false) + start_network_capture(capture_body=True) -2. navigate(url, pre_inject_hooks=["jsvmp_probe","xhr","fetch","crypto"]) -3. 用户完成挑战动作(滑块/验证码) -4. get_jsvmp_log() + get_trace_data("XMLHttpRequest.prototype.send") -5. 按场景 1(参数签名) / 场景 10(JSVMP+环境伪装) 继续 -``` - -**纯混淆工作流**: -``` -1. dump_jsvmp_strings(script_url) -2. instrument_jsvmp_source(pattern, mode="regex") -3. 按场景 4(OB 混淆) 还原 -``` - -### 三条反模式警告 - -1. **不要把 `pre_inject_hooks` 当RS的解法**。症状:`redirect_chain` 反复 412,挑战永远不过。这是观察者效应的标志,立即切换 `instrument_jsvmp_source` -2. **不要在签名型反爬上默认开 `hook_jsvmp_interpreter`**。它的 `mode="proxy"`(默认值)会挂。如果一定要用,改 `mode="transparent"`,但仍不如源码级插桩稳 -3. **不要用 `trace_property_access(["navigator.*"])` 分析签名型反爬**。内部走 Proxy 或 getter 替换,签名型反爬一样能感知 - ---- - -## 核心武器:camoufox-reverse MCP - -Camoufox 反检测浏览器 + Playwright 协议,C++ 引擎级指纹伪装,78 个工具覆盖逆向分析全链路(v0.8.0)。 - -**核心优势:** -- Camoufox 在 **C++ 引擎层** 修改指纹信息,而非 JS 补丁 -- Juggler 协议沙箱隔离,Playwright 对页面 JS **完全不可检测** -- BrowserForge 基于 **真实流量分布** 生成指纹 -- 适用于有强反爬检测的站点:RS、JY、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`) -- v0.8.0 新增:域级 Session 档案管理工具(`create_session` / `load_session` / `update_session` / `run_assertions` / `get_session_summary`);断言系统内置 8 种断言类型自动验证;`export_state` 支持关联 Session 档案;新增 `diff_hot_keys` 工具对比两次 hot_keys 快照差异 - -### 域级 Session 档案(v2.9.0 / v0.8.0 新增) - -每个目标域维护一份持久化 Session 档案,跨任务复用反爬类型判定、指纹基准、Cookie 归因结论。 - -- **存储位置**:`cases/_session_archives//`(域名 SHA256 前 12 位,已加入 `.gitignore`) -- **核心文件**:`session.json`(元数据)/ `fingerprint.json`(指纹快照)/ `cookie-attribution.json`(归因缓存)/ `assertions.json`(断言集) -- **生命周期**:7 天有效,过期后标记 `stale` 提示重新验证,不自动删除 -- **断言系统**:8 种断言类型(`script_exists` / `script_size_range` / `dispatch_loop_count` / `anti_crawl_type` / `cookie_names` / `sdk_fingerprint` / `hot_keys_stable` / `response_structure`),Phase 0.5.-1 自动 Reverify -- 详见 `references/domain-session-guide.md` - -### 浏览器与页面控制 - -- `launch_browser`:启动 Camoufox 反检测浏览器(可配置 headless/proxy/os_type/humanize/block_images/block_webrtc)。**已启动时返回完整会话状态**(页面 URL、上下文列表、抓包状态),不再只返回 `already_running` -- `close_browser`:关闭浏览器释放资源 -- `navigate`:导航到目标 URL(支持 wait_until: load/domcontentloaded/networkidle)。**v0.4.0 增强**: - - `pre_inject_hooks=["xhr", "fetch", "crypto", "websocket", "debugger_bypass", "cookie_hook", "runtime_probe", "jsvmp_probe"]`——先走 `about:blank` 把 hook 装好,再 goto 目标 URL,保证 hook 在首屏 JS 之前生效(对RS 412 挑战页、Akamai 首包检测至关重要) - - `via_blank=True`——不装新 hook 也强制经 about:blank,让之前注册的 persistent scripts 一定生效 - - 返回 `initial_status` / `final_status` / `redirect_chain`,解决"412 挑战页 → 通过后 200"的状态歧义 - - ⚠️ **对签名型反爬(RS 5/6、Akamai sensor_data v3+)不可用**:pre-inject 的 probe 会改变 VMP 眼里的环境(Proxy 包装 navigator/screen、Function.prototype.apply/call 被覆盖),参与 cookie 计算的环境指纹失真,导致服务端验签失败。**症状**:`navigate` 永远不返回或返回时 `final_status == 412`,`redirect_chain` 里同一 URL 反复出现 412 响应,没有 200。**自救**:立即从 `pre_inject_hooks` 移除 `jsvmp_probe` / `cookie_hook` 等主动观察 probe,改用 `instrument_jsvmp_source(mode="ast")` 做源码级插桩。如果一定要 runtime 观察,退而求其次用 `pre_inject_hooks=["jsvmp_probe_transparent"]`(对应 mode="transparent"),但仍不如源码插桩稳。 -- `reload` / `go_back`:刷新 / 后退 -- `reload_with_hooks`:**[v0.4.0 新]** 重载当前页面,使 persistent hooks 在页面 JS 之前执行。默认 `clear_log=True` 会清空 `__mcp_jsvmp_log` / `__mcp_prop_access_log` / `__mcp_cookie_log`,获得干净的一次执行捕获。**典型用法**:先 navigate 到目标页定位 VMP 脚本 URL,再 `instrument_jsvmp_source` / `hook_jsvmp_interpreter`,最后 `reload_with_hooks()` 让探针先于 VMP 生效 -- `click` / `type_text`:点击元素 / 输入文本 -- `wait_for`:等待元素或 URL 匹配 -- `get_page_info`:获取当前页面 URL、标题、视口尺寸 -- `take_screenshot` / `take_snapshot`:截图 / 获取页面无障碍树(兼容新版 Playwright >= 1.42,自动 fallback 到 JS 实现) -- `get_page_content`:**一键导出渲染后 HTML + title + meta + 可见文本**(前 50KB HTML + 前 20KB 可见文本) -- `get_session_info`:**查看当前会话状态**(浏览器/上下文/页面/抓包状态/持久化脚本/Hook 列表),用于了解当前调试环境全貌 - -### 源码分析(逆向核心) - -- `list_scripts`:列出页面加载的所有 JS 脚本 -- `get_script_source`:获取完整 JS 源码 -- `search_code`:在所有已加载 JS 中搜索关键词(返回结构化结果:总匹配数 + 各脚本匹配数,默认 max_results=50,最大 200) -- `search_code_in_script`:**在指定脚本中搜索**(更多上下文:前后 3 行,更高限额,适合大文件精确定位) -- `save_script`:保存 JS 文件到本地 -- `get_page_html`:获取页面 HTML - -### 调试(逆向核心) - -- `evaluate_js`:在页面上下文执行任意 JS(最核心工具) -- `evaluate_js_handle`:执行 JS 并检查复杂对象 -- `add_init_script`:注入页面加载前执行的脚本(支持 **`persistent=True` 跨导航自动重注入**,Hook 安装的核心方法) -- `set_breakpoint_via_hook`:通过 JS Hook 设置伪断点(捕获参数、调用栈、返回值,支持 `persistent` 持久化) -- `get_breakpoint_data`:获取伪断点捕获的数据 -- `get_console_logs`:获取页面控制台输出 - -### Hook(逆向核心) - -- `trace_function`:追踪函数调用(不暂停执行,记录入参、返回值、可选调用栈,支持 **`persistent=True` 跨导航持久化**) -- `get_trace_data`:获取追踪数据(自动合并页面数据和 Python 端持久化数据) -- `hook_function`:注入自定义 Hook(before/after/replace 三种模式,支持 `non_overridable` 防覆盖) -- `inject_hook_preset`:一键预设 Hook,**默认 persistent=True 持久化**。v0.4.0 可选预设扩展为 7 个: - - `xhr` / `fetch` / `crypto` / `websocket` / `debugger_bypass`——与旧版一致 - - `cookie`——**[v0.4.0 新]** 基于 `cookie_hook.js`,**原型链级 hook**(沿 `document` 原型链找到 `cookie` 描述符所在的 `Document.prototype` / `HTMLDocument.prototype`,在那里替换 getter/setter,避免直接 `Object.defineProperty(document, 'cookie', ...)` 被浏览器忽略)。写入 `window.__mcp_cookie_log`(含 op/value/stack/ts) - - `runtime_probe`——**[v0.4.0 新]** 低开销广谱运行时探针,**非 Proxy 模式**,只覆写具体热点 API:XHR.open/send、fetch、canvas.toDataURL/getContext、WebGL.getParameter、navigator getters(userAgent/platform/language/webdriver/plugins 等 10 项)、EventTarget.addEventListener(鼠标/键盘/设备运动)。写入 `window.__mcp_runtime_log` -- `get_runtime_probe_log`:**[v0.4.0 新]** 拉取 runtime_probe 日志,支持 `type_filter`("xhr_open"/"xhr_send"/"fetch"/"canvas_toDataURL"/"canvas_getContext"/"webgl_getParameter"/"nav_read"/"addEventListener")和按类型聚合的 `by_type` -- `remove_hooks`:移除所有 Hook(支持 `keep_persistent` 参数保留持久化 Hook) -- `freeze_prototype`:**冻结任意原型方法**(`Object.defineProperty` configurable:false + writable:false,防止页面 JS 覆盖 Hook) -- `trace_property_access`:**Proxy 级别属性访问追踪**(监控对象属性的读写操作,使用 `.*` 后缀监控全部属性,如 `navigator.*`、`screen.*`) -- `get_property_access_log`:获取属性访问记录 - -### JSVMP 专项分析(v0.4.0 从 4 个工具扩展为 10 个,新增源码级插桩全套) - -**A. 运行时探针(通用,不改写源码)** - -- `hook_jsvmp_interpreter`:**v0.4.0 重写为多路径通用探针**(不再只是 apply hook)。覆盖 4 条 VMP 访问宿主环境的通道: - - `track_calls=True`:Hook `Function.prototype.apply/call/bind` + `Date.now/performance.now` + `Math.random/crypto.getRandomValues` - - `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` - - **对RS 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)` 检测 - - 选 mode 的决策:先做反爬类型识别。**签名型优先用 `instrument_jsvmp_source`**,transparent 作为源码插桩失败时的备选;**行为型/纯混淆**用默认 proxy 模式 - - 对应 `pre_inject_hooks` 也增加了新名字 `"jsvmp_probe_transparent"`,语义等同 -- `get_jsvmp_log`:获取探针日志,支持 `type_filter`("api_call"/"prop_read")、`property_filter`、`func_filter`。返回自带 `summary.api_calls` / `summary.property_reads`(按调用次数倒排) -- `dump_jsvmp_strings`:**v0.4.0 修复**——用手动括号匹配替代嵌套正则,不再死循环。返回 `string_arrays`(大数组) / `decoded_strings`(去重后 top 500) / `api_names`(命中 navigator/screen/encrypt/md5 等关键词的串) / `suspicious_patterns`(JSVMP 循环、eval、new Function、XOR 解密、Base64+fromCharCode 解码器) -- `find_dispatch_loops`:**[v0.4.0 新]** 扫描脚本定位字节码分发函数。遍历每个 `switch(…)`,配对 `{…}` 做手动括号匹配,统计 case 数,返回 `case_count >= min_case_count(默认 20)` 的候选。输出 `{fn_name, case_count, char_range, preview}`——`case_count > 50` 基本可以确认是 VMP 解释器 -- `compare_env`:**全面收集浏览器环境指纹**,结构化返回 navigator/screen/canvas/WebGL/Audio/timing/misc 分类数据。**覆盖范围**:默认采集主流检测项,**不包含** `Function.prototype.toString` 原生性检测、`Symbol.toStringTag` 标签、DOM `offsetHeight` 布局等细粒度项——这些需配合 `evaluate_js` 分批采集(见 `cases/jsvmp-xhr-interceptor-env-emulation.md` 4 批次模板) - -**B. 源码级插桩(通用 VMP 利器,v0.4.0 新增核心能力)** - -- `instrument_jsvmp_source(url_pattern, mode, tag, rewrite_member_access=True, rewrite_calls=True, max_rewrites=5000, cache_rewritten=True)`:**[v0.4.0 新]** 在 HTTP 层拦截 VMP 脚本响应,对源码做改写,在**每个 `obj[key]` 读取**(`__mcp_tap_get`)、**每个 `obj.method(args)` 方法调用**(`__mcp_tap_method`)、**每个 `fn(args)` 直接调用**(`__mcp_tap_call`)前插入运行时 tap。改写后的源码继续正常执行,但每次对宿主环境的交互都写入 `window.__mcp_vmp_log`。 - - **`mode="regex"`**:纯正则改写,无 CDN 依赖,只改写 member access(80% 覆盖),速度快,大文件安全 - - **`mode="ast"`**:通过页面内 Acorn 做精确 AST 改写,99% 覆盖,需加载 `cdnjs.cloudflare.com/ajax/libs/acorn` - - `url_pattern` 用 glob(如 `"**/webmssdk.es5.js"`、`"**/sdenv-*.js"`、`"https://target.com/FuckCookie_*.js"`) - - `tag` 作为过滤键(一次插桩多个 VMP 时区分) - - **与 runtime 探针互补**:runtime 探针只看 VMP 路由到可 hook API 的部分;源码插桩能看到 VMP 在 switch/case 内部每一次 `opcode_table[code]` 访问和每一次子 handler 调用,是RS 6 / Akamai 这类"VM 完全自包含"场景的唯一有效手段 -- v0.5.0 变化(v2.6.0 吸收): - - `mode="ast"`(默认)从 v0.4.x 的"页面内加载 acorn CDN"改为 **MCP 侧 esprima-python 解析**。这解决了挑战页(RS 412 等)加载不到 CDN 导致 AST 模式静默失败的问题 - - 新增 `fallback_on_error=True`(默认 True):esprima 解析失败时(极少,主要是 ES2022+ private field 等新语法)自动回落 regex - - `mode="ast_page"`(新增取值):保留 v0.4.x 的旧行为(页面内 acorn CDN),仅用于 A/B 对比,标记为 **deprecated**,生产中**不要**选用 - - `get_instrumentation_status()` 每个 `active_patterns` 现在包含 `last_mode_used` 字段,取值 `"ast"` / `"regex"` / `"regex (fallback)"` / `"ast_page"`。**长期是 `"regex (fallback)"` 说明 esprima 吃不下目标语法**,可 file issue -- `get_instrumentation_log(tag_filter, type_filter, key_filter, limit=500, clear=False)`:**[v0.4.0 新]** 拉取源码插桩日志。`type_filter` 可选 `"tap_get"` / `"tap_call"` / `"tap_method"` / `"tap_call_err"`。返回内置 `summary`: - - `hot_keys`:访问频次 top 30 的属性名——**指纹学习的金矿**,RS VMP 会在这里暴露所有它读取的环境属性 - - `hot_methods`:调用频次 top 30 的方法(格式 `ObjectType.methodName`) - - `hot_functions`:调用频次 top 30 的函数名 -- `get_instrumentation_status`:**[v0.4.0 新]** 查看当前激活的源码插桩,每个 pattern 的 `files_rewritten`、`total_edits`、`last_url`、`cached_urls` -- `stop_instrumentation(url_pattern=None)`:**[v0.4.0 新]** 停止一个 pattern(传路径)或全部(不传),底层调用 `page.unroute` - -**C. Cookie 归因** - -- `analyze_cookie_sources(name_filter=None)`:**[v0.4.0 新]** 解答"某个 Cookie 到底是谁写的"。融合三路数据源: - - 所有捕获响应的 `Set-Cookie` header(需 `start_network_capture` 在先) - - `window.__mcp_cookie_log`(需 `inject_hook_preset("cookie")` 在先) - - `page.context.cookies()`——当前 jar 状态 - - 对每个 cookie 名返回:`sources: ["http_set_cookie" | "js_document_cookie"]` / `first_set_ts` / `http_responses: [{url, ts, header}]` / `js_writes: [{value, stack, ts}]` / `current_value` - - **适用场景**:RS/Akamai 里 JS 端计算 token 但最终由服务端 `Set-Cookie` 写入的混合模式——没这个工具就会在 JS Hook 上找不到写入而一筹莫展 - -### 网络分析(逆向核心) - -- `start_network_capture` / `stop_network_capture`:启停网络流量捕获(支持 **`capture_body=True` 捕获响应体**,大响应自动截断 200KB) -- `list_network_requests`:列出捕获的请求(支持过滤,URL 截断到 200 字符,字段精简:`resource_type` → `type`,`duration` → `ms`) -- `get_network_request`:获取请求完整详情(含响应体,**支持 `include_headers=False` 省略 headers 节约 token**) -- `get_request_initiator`:**获取发起请求的 JS 调用栈**(改进 URL 匹配 + 反向搜索 + 诊断信息,黄金路径:从请求直接定位签名函数) -- `intercept_request`:拦截请求(log/block/modify/mock 四种模式) -- `stop_intercept`:停止拦截 -- `search_response_body`:**在所有已捕获响应体中全文搜索关键词**(需先 `start_network_capture(capture_body=True)`,支持 `url_filter` 缩小范围) -- `get_response_body_page`:**分页读取大响应体**(避免截断丢失数据,`offset` + `length` 分页,单次最大 50KB) -- `search_json_path`:**按 JSON 路径提取响应数据**(支持 `data.token`、`result[0].sign`、`data[*].id` 通配提取) - -### 存储管理 - -- `get_cookies` / `set_cookies` / `delete_cookies`:Cookie 管理 -- `get_storage` / `set_storage`:localStorage / sessionStorage -- `export_state` / `import_state`:保存 / 恢复完整浏览器状态(Cookie + Storage) - -### 指纹与反检测 - -- `get_fingerprint_info`:检查当前浏览器指纹(navigator/screen/WebGL/canvas) -- `check_detection`:在反检测站点测试是否被识别(bot.sannysoft.com 等) -- `bypass_debugger_trap`:一键绕过反调试陷阱(debugger 死循环、Function 构造器检测等) - -**核心原则:** -1. **独立调试浏览器**:不接管用户正在使用的浏览器,通过 `launch_browser` 新建独立的 Camoufox 实例进行分析 -2. **Cookie 自动迁移**:用户提供 Cookie 时,通过 `set_cookies` 自动写入调试浏览器,还原登录态 -3. **能用 MCP 就不手动**:能自动化就不要求用户操作 -4. **黄金路径优先**:善用 `get_request_initiator` 从请求直接定位签名函数,省去大量搜索 -5. **Hook 持久化优先**:使用 `persistent=True` 确保 Hook 跨导航不丢失,配合 `freeze_prototype` 防覆盖 - -## 浏览器连接策略(最高优先级) - -**每次任务开始时,必须执行以下流程:** - -### 启动流程 - -根据用户是否提供 Cookie,选择对应路径: - -``` -┌─ 用户提供了 Cookie? -│ -├─ YES → 路径 A:带 Cookie 启动 -│ 步骤 1: launch_browser(headless=false) → 启动反检测浏览器(有头模式,方便用户观察) -│ 步骤 2: navigate(url="目标URL") → 导航到目标页面 -│ 步骤 3: set_cookies(cookies=[...]) → 写入 Cookie -│ 步骤 4: reload() → 刷新使 Cookie 生效 -│ 步骤 5: evaluate_js(expression="document.cookie") → 验证写入成功 -│ -└─ NO → 路径 B:直接启动 - 步骤 1: launch_browser(headless=false) → 启动反检测浏览器(有头模式,方便用户观察) - 步骤 2: navigate(url="目标URL") → 导航到目标页面 - 步骤 3: 直接进入后续分析阶段 -``` - -### Cookie 写入方法 - -使用 `set_cookies` 工具直接写入结构化 Cookie,支持完整属性(name/value/domain/path/expires 等)。 - -用户提供的 Cookie 可能有多种格式,按实际情况转换为 `set_cookies` 所需的数组格式: - -``` -方式 A — Cookie 字符串(如 "k1=v1; k2=v2; k3=v3"): - 拆分后构造 [{name:"k1", value:"v1", domain:".example.com", path:"/"}, ...] - -方式 B — JSON 格式(如 EditThisCookie 导出): - 直接传入 set_cookies,保留 domain/path/expires 等属性 - -方式 C — 用户直接给出 Request Headers 中的 Cookie 字段: - 按 "; " 拆分后构造数组 -``` - -### 关键规则 - -1. **不接管用户浏览器**:始终通过 `launch_browser` 创建独立 Camoufox 实例,用户的日常浏览器不受影响 -2. **必须有头模式启动**:`launch_browser` 调用时**必须显式传 `headless=false`**(有头模式),让用户可以观察页面行为、操作状态和调试过程。**禁止使用无头模式(headless=true)**,除非用户明确要求 -3. **有 Cookie 就写,没有就直接开干**:不要因为没有 Cookie 就阻塞流程,非登录态接口不需要 Cookie -4. **单页面模式**:启动后所有操作在当前页面上下文中执行,通过 `navigate` 切换页面 -5. 如果分析过程中发现需要登录态但没有 Cookie,再引导用户获取: - - DevTools 控制台:F12 → Console → `document.cookie` → 复制 - - Network 面板:F12 → Network → 任意请求 → 复制 Cookie Header - - 浏览器扩展:EditThisCookie 等导出 JSON -6. 如果 Cookie 过期或失效,提示用户重新获取并写入 -7. **状态持久化**:可通过 `export_state` 保存当前浏览器状态,下次通过 `import_state` 恢复,避免重复登录 -8. **善用会话查询**:不确定当前调试环境状态时,用 `get_session_info` 查看浏览器/页面/Hook/抓包全貌 - -## 工作流程 +> **这是骨架,每步的具体动作见 `references/phase-details.md`** ### Phase 0:任务理解与调试环境搭建 -收到用户的目标 URL 和分析需求后: +> ⚠️ **开始本段前,确认已完成顶部"硬约束 Checklist"三项复述**。 -1. 明确分析目标:需要还原哪些加密参数、目标数据是什么 -2. **接口分析**:用户提供请求信息后,先执行以下分析: - - 梳理请求的 URL、Method、Headers、Params、Body - - 识别并标记其中的签名/动态参数(如 `sign`、`token`、`timestamp`、`nonce` 等) - - 根据参数特征(长度、字符集、结构)给出算法的初步判断 - - 以简洁表格或列表形式汇总分析结果,与用户确认后再进入下一步 -3. **搭建调试环境**(按「浏览器连接策略」执行): - - 通过 `launch_browser` + `navigate` 启动反检测浏览器并打开目标 URL - - 用户提供了 Cookie → `set_cookies` 写入并 `reload` 刷新 - - 用户没提供 Cookie → 直接进入分析,后续按需再引导获取 -4. **启动域级 Session 档案**(v2.9.0 新增): - - 计算目标域的 hash:`sha256(domain)[0:12]` - - 检查 `cases/_session_archives//session.json` 是否存在 - - 存在且未过期(7 天内)→ 加载档案,输出摘要(反爬类型、上次分析时间、断言通过数) - - 存在但过期 → 加载档案 + 标记 `stale`,提示用户建议重新验证 - - 不存在 → 创建空档案,后续步骤自动填充 - - 详见 `references/domain-session-guide.md` -5. 创建项目目录(以目标网站/功能命名),结构参考 `templates/` 下的模板 +目标:接收任务,理解业务诉求,搭建 MCP 工具栈(launch_browser + navigate + Cookie 写入)。 -### Phase 0.5:经验库指纹匹配(30 秒快速检测) +详见 `references/phase-details.md#phase-0`。 -> **目的**:在进入正式逆向流程前,快速检测目标站点的技术特征,与 `cases/` 经验库中的已知模式匹配。命中则直接采用已验证的还原路径,大幅缩短分析时间;未命中则走标准 Phase 1-5 流程,**完成后将新经验沉淀回经验库**。 +### Phase 0.5:经验库命中验证(**Phase 0 完成后的唯一合法下一步**) -#### 0.5.-1 本域断言 Reverify(v2.9.0 新增前置步骤) - -> 如果 Phase 0 步骤 4 加载了已有的域级 Session 档案且包含断言集,在进入指纹采集之前先跑断言验证。 - -``` -执行条件:Session 档案存在 且 assertions.json 非空 - -流程: - ① 逐条执行断言(script_exists / dispatch_loop_count / anti_crawl_type / cookie_names 等) - ② 全部通过 → 跳过 Phase 0.5 的指纹采集,直接进入 Phase 1 - 输出:"断言集 N/N 通过,目标站点技术栈未变,跳过重复侦察" - ③ 有失败 → 标记失败断言,进入 Phase 0.5 正常流程重新采集 - 输出:"断言 #X(类型)失败:期望 Y,实际 Z。目标可能已更新,重新侦察" - ④ 将验证结果更新到 assertions.json 的 last_result 和 last_checked -``` - -**断言类型速查**: - -| 类型 | 检测方法 | 典型用时 | -|------|---------|---------| -| `script_exists` | `list_scripts` → 匹配 URL pattern | < 1s | -| `script_size_range` | `list_scripts` → 检查文件大小 ±20% | < 1s | -| `dispatch_loop_count` | `find_dispatch_loops` → 比较 case_count ±10 | < 3s | -| `anti_crawl_type` | `navigate` → 检查 redirect_chain 模式 | < 5s | -| `cookie_names` | `get_cookies` → 检查 name 列表 | < 1s | -| `sdk_fingerprint` | `search_code` → 匹配关键词 | < 2s | - -详见 `references/domain-session-guide.md` 第 4 节。 - -#### 0.5.1 快速指纹采集 - -在 Phase 0 搭建好调试环境后,立即执行以下探测(不超过 30 秒): - -``` -Actions: - ① JS 文件特征 - - list_scripts → 记录各脚本体积(关注 200KB+ 的大文件) - - search_code(keyword="_0x") → 检测 OB 混淆特征 - - search_code(keyword="while") + search_code(keyword="switch") → 检测 JSVMP 解释器循环 - - search_code(keyword="webrt|acrawler|webmssdk|_sdkGlue") → 检测已知 SDK 特征 - - ② 参数特征 - - list_network_requests → 提取加密参数名和格式 - - 参数长度、字符集、编码方式(hex/base64/自定义) - - ③ 请求链特征 - - 是否存在预热接口(/api/init、/api/config 等) - - Cookie 中是否有动态字段(__ac_*、ttwid、msToken 等) - - 是否有 XHR/fetch 拦截器(search_code(keyword="XMLHttpRequest.prototype.open|fetch")) - - ④ 反调试特征 - - bypass_debugger_trap → 是否触发 debugger 陷阱 - - search_code(keyword="Function.prototype.toString|navigator.webdriver") → 环境检测 -``` - -#### 0.5.2 指纹匹配 - -将采集到的指纹与 `cases/` 目录下的经验案例进行匹配: - -``` -匹配规则(按权重排序): - 1. SDK 特征命中(如 webmssdk / acrawler)→ 直接定位到具体案例 → 权重最高 - 2. 加密参数名 + 格式组合命中(如 a_bogus + 192字符 + Base64 变体)→ 高权重 - 3. 技术栈组合命中(如 JSVMP + XHR拦截 + 动态Cookie)→ 中权重 - 4. 单项特征命中(如仅 OB 混淆)→ 低权重,仅供参考 -``` - -#### 0.5.3 匹配后行为 - -``` -┌─ 指纹匹配结果? -│ -├─ 高置信度命中(SDK 特征 或 参数名+格式完全匹配) -│ → 告知用户:"检测到与 [案例名称] 相似的技术特征,将优先尝试已验证路径" -│ → 直接按案例中的「已验证定位路径」执行 -│ → 按案例中的「还原代码模板」快速生成初版代码 -│ → 运行验证 → 成功则跳过 Phase 1-3,直接进入 Phase 4 优化 -│ → 失败则回退到 Phase 1 标准流程(可能是目标更新了技术方案) -│ -├─ 中/低置信度命中(技术栈组合 或 单项特征) -│ → 告知用户匹配情况,作为 Phase 1-2 的参考线索 -│ → 仍走标准流程,但优先验证案例中提到的关键路径 -│ -└─ 未命中 - → 走标准 Phase 1-5 流程 - → 完成后执行「经验沉淀」(见下方) -``` - -#### 0.5.4 经验沉淀(Phase 5 后自动触发) - -每次成功完成一个站点的逆向分析后,**主动询问用户是否需要沉淀经验**: - -``` -沉淀流程: - 1. 按 cases/_template.md 格式整理本次分析的技术指纹、定位路径、还原方案 - 2. 脱敏处理:移除具体 URL、真实密钥、用户 Cookie 等敏感信息 - 3. 提取可复用的代码模板(核心加密函数、环境补丁等) - 4. 写入 cases/ 目录,文件名用技术特征命名(如 jsvmp-xhr-interceptor-env-emulation.md) - 5. 更新 cases/README.md 索引 - 6. 可选:用户可在 cases/_private_mapping.json 中私有标注域名映射(不纳入 git) -``` - -### Phase 1:目标侦察(自动执行) - -使用 MCP 工具完成以下侦察,**不需要用户手动操作**: - -#### 1.1 确认调试页面状态 - -``` -Actions: - - Phase 0 中已通过 launch_browser + navigate 启动反检测浏览器并加载目标页面 - - take_screenshot → 截取当前页面视觉状态,确认页面正常 - - 如需导航到特定子页面:navigate(url="目标子页面") - - 如果涉及登录态,确认 Cookie 已写入且页面内容正确 -``` - -#### 1.2 网络请求捕获 - -``` -Actions: - - start_network_capture() → 开始捕获网络流量 - - evaluate_js / click / type_text → 触发翻页/交互,产生请求 - - list_network_requests → 获取捕获的请求列表(支持过滤) - - get_network_request(request_id=N) → 获取关键接口的详细信息 - - get_request_initiator(request_id=N) → 获取发起请求的 JS 调用栈(黄金路径!) - 重点关注: - - Request URL、Method - - Request Headers(Cookie、自定义签名头) - - Query Params / Request Body(识别加密参数) - - Response 数据结构 - - Initiator Stack(直接定位加密函数) - - 重复上述步骤,收集多次请求进行对比 -``` - -#### 1.3 加密参数识别 - -对比多次请求,分析每个参数: -- **固定值**:直接硬编码或从页面提取 -- **动态值**:判断变化因子(时间戳、页码、随机数、自增计数器) -- **加密值**:根据长度、字符集、格式初步判断算法类型 - -#### 1.4 输出侦察报告 - -``` -📋 目标信息 -━━━━━━━━━━━━━━━━━━━━━━━━ -目标网站:[URL] -分析目标:[需要还原的加密逻辑] -数据接口:[API endpoint] - -🔗 接口参数分析 -━━━━━━━━━━━━━━━━━━━━━━━━ -URL:[完整请求URL] -Method:GET/POST -Headers: - - Cookie: [关键字段及示例] - - [自定义头]: [示例值] -加密参数: - - 参数名: [名称] | 示例值: [值] | 长度: [N] | 字符集: [hex/base64/...] | 初步猜测: [算法] - -📊 响应数据样本 -━━━━━━━━━━━━━━━━━━━━━━━━ -[前2-3条数据] - -🧠 技术分析要点 -━━━━━━━━━━━━━━━━━━━━━━━━ -本目标涉及的签名分析技术点: - 1. [如:OB混淆还原] - 2. [如:动态Cookie生成] - 3. [如:AES-CBC加密] -``` - -### Phase 2:源码分析 - -根据 Phase 1 识别到的加密参数,在调试浏览器页面上深入 JS 源码: - -**前置确认**:通过 `get_page_info` 确认当前页面状态正确。 - -#### 2.1 关键词搜索定位 - -``` -Actions: - - search_code(keyword="加密参数名") → 直接在已加载的 JS 源码中搜索 - - search_code(keyword="encrypt|sign|token|md5|sha|aes|des|rsa|hmac|btoa|atob|CryptoJS") - - search_code(keyword="XMLHttpRequest|$.ajax|fetch|beforeSend") - - search_code(keyword="document.cookie") - - 根据搜索结果: - - get_script_source → 读取包含加密逻辑的源码片段 - - save_script → 保存关键脚本到本地分析 -``` - -#### 2.2 代码混淆识别与还原 - -参考 `references/obfuscation-guide.md` 识别混淆类型: - -| 混淆类型 | 特征 | 还原策略 | -|---------|------|---------| -| OB 混淆 (obfuscator.io) | `_0x` 前缀变量、十六进制字符串数组 | 字符串解密 + 变量重命名 | -| 控制流平坦化 (CFF) | `switch-case` 状态机、`while(true)` 循环 | 追踪状态转移还原执行顺序 | -| eval/Function 打包 | `eval(...)` 或 `new Function(...)` 包裹 | Hook eval/Function 拦截源码 | -| AAEncode/JJEncode | 颜文字、符号编码 | 直接 eval 获取原始代码 | -| JSFuck | `[]!+` 字符组合 | 浏览器执行还原 | -| 字符串编码 | `\x48\x65\x6c\x6c\x6f`、Unicode 转义 | 解码还原可读字符串 | -| 自定义 VM/字节码 | 超大数组 + 解释器循环 | 追踪解释器执行流,提取有效操作 | -| JSVMP | 200KB+ 文件、自定义解释器 | **不反编译**,通过 Hook/插桩/日志追踪执行链路(详见下方专项) | - -``` -Actions: - - get_script_source → 获取混淆代码片段 - - 使用 scripts/deobfuscate.js 进行基础代码还原 - - evaluate_js → 在浏览器中执行解密/还原操作 -``` - -#### 2.2+ JSVMP 专项分析(核心能力) - -当识别到 JSVMP(JS 虚拟机保护)时,**严禁尝试反编译字节码**。 - -详细指南参考 `references/jsvmp-analysis.md`,环境补丁模板参考 `references/jsdom-env-patches.md`。 - -**识别标志**: -- 超大 JS 文件(200KB+),函数/变量名完全无意义(单字母或 `_0x` 前缀) -- 包含自定义解释器循环:`while(true) { switch(opcode) { ... } }` 或类似的分发表模式 -- 改写或劫持浏览器原生 API(XHR / fetch / Cookie) -- 关键特征代码模式:超大数组(字节码)+ 指针变量 + 栈操作 + 跳转指令 - -**路径选择决策**: - -识别到 JSVMP 后,必须先选择攻克路径: - -| 路径 | 目标 | 方法 | 适用场景 | 典型用时 | -|------|------|------|---------|---------| -| **A:算法追踪** | 搞清 JSVMP 内部算法,用纯代码还原 | 三板斧(Hook/插桩/日志) | JSVMP 算法可提取、环境依赖少、使用标准加密算法 | 4-8 小时 | -| **B:环境伪装** | 在 jsdom/vm 中运行原始 JSVMP | 环境采集 → 对比 → 补丁 | JSVMP 与环境深度绑定、算法不可提取、签名是「黑箱」 | 2-4 小时 | - -**路径 A 的前置判断(v2.6.0 新增)**: - -``` -先判断 JSVMP 所在反爬类型: - -┌─ navigate(url) 不加任何 hook,看 redirect_chain -│ -├─ redirect_chain 反复 412 然后才到 200 -│ → 签名型 JSVMP -│ → 路径 A **只能走第四板斧**(instrument_jsvmp_source, mode="ast") -│ → 前三板斧禁用(会破坏签名) -│ → 若源码插桩失败再退 hook_jsvmp_interpreter(mode="transparent") -│ -├─ redirect_chain 直接 200 但页面后续 XHR 带签名参数 -│ → 行为型 JSVMP -│ → 路径 A 四板斧全开,按原三板斧流程走 -│ -└─ redirect_chain 直接 200 无特殊签名参数 - → 不是签名型也不是 JSVMP,走 Phase 2.2 混淆还原 -``` - -``` -决策树: - ├─ JSVMP 是否劫持了请求链路(XHR/fetch 拦截器)? - │ ├─ YES + 算法与环境指纹深度绑定(如 a_bogus) - │ │ → 优先选路径 B(环境伪装),让 JSVMP 在 jsdom 中自行生成签名 - │ │ → 路径 B 失败(如 jsdom 无法加载 JSVMP)时回退路径 A - │ └─ YES 但签名逻辑相对独立 - │ → 路径 A(算法追踪),提取签名函数 - │ - ├─ JSVMP 仅生成签名参数(不劫持请求)? - │ ├─ Hook 确认使用标准算法 → 路径 A,纯算法还原 - │ └─ 算法完全自定义 + 环境依赖重 → 路径 B - │ - └─ 无法判断 → 先快速测试路径 B(30 分钟),不行再走路径 A -``` - ---- - -##### 路径 A:算法追踪(四板斧:Hook → 插桩 → 日志 → 源码级插桩) - -**核心方法论 — 从 I/O 两端夹逼 + 中间层插桩 + 源码级全量 tap**: - -> **v2.5.0 更新**:v2.4.0 的"三板斧"在RS 5/6、Akamai sensor_data、webmssdk 这类"VM 完全自包含、不路由到可 hook API"的场景上失效——因为 Hook `Function.prototype.apply` 看不到 VM 内部 `opcode_table[code]` 调度。v0.4.0 MCP 新增的 **源码级插桩(`instrument_jsvmp_source`)** 作为第四板斧补齐这一缺口。四板斧的关系是: +> ⚠️ 本步骤在硬约束 Checklist 的 [CHECK-2] 中已经**预执行**过。 +> 这里是 Phase 0 完成后做**二次确认和深入**: > -> - **第一 / 二 / 三板斧**:诊断"VM 看外界"(入口)和"外界看 VM"(出口)——适合签名通过 CryptoJS/atob/MD5 等可 hook 原语走的 VMP -> - **第四板斧**:诊断"VM 看自己"——适合 VMP 算法全部封装在字节码分发循环 `switch(opcode) { case N: obj[key](args); ... }` 里的场景 +> - 如果 [CHECK-2] 命中某个 case → 本步骤详读该 case,按其"已验证定位路径"执行 +> - 如果 [CHECK-2] 未命中 → 本步骤做**指纹采集**,分析结束时沉淀新 case -> **快速路径(推荐优先试)**: -> -> 1. `find_dispatch_loops(script_url)` 确认是不是 VMP(`case_count > 50` 基本是) -> 2. `hook_jsvmp_interpreter(script_url=)` 一键装通用探针 -> 3. `inject_hook_preset("cookie")` + `inject_hook_preset("xhr", persistent=True)` -> 4. `instrument_jsvmp_source(url_pattern="**/", mode="ast", tag="vmp1")`(AST 模式覆盖率高,页面能联网时优先) -> 5. `reload_with_hooks()` — 让所有探针先于 VMP 生效,同时清日志 -> 6. 触发目标操作 -> 7. `get_instrumentation_log(tag_filter="vmp1", limit=300)` — 看 `hot_keys` / `hot_methods` / `hot_functions` 三个摘要,通常 30 秒内就能定位到 VMP 读取的环境指纹集和调用的加密原语 -> 8. `get_jsvmp_log()` + `analyze_cookie_sources()` 交叉印证 -> -> 快速路径足以解决 70%+ 的RS/Akamai/webmssdk 场景。**无法解决时**(算法全部内联、无可识别 hot_keys 模式)再走手动四板斧流程 ↓ - -**四板斧适用边界与局限**: - -| 板斧 | 工具 | 擅长 | 不擅长 | 适用反爬类型 | -|------|------|------|-------|-------------| -| 第一斧(Hook I/O) | `inject_hook_preset(xhr/fetch/crypto/cookie)` + `freeze_prototype` | 请求链路劫持、动态 Cookie、加密原语入口 | VM 内部自实现的 MD5/AES | 行为型 ✅ / 纯混淆 ✅ / 签名型 ❌ | -| 第二斧(插桩解释器) | `trace_function` + `trace_property_access` | 能识别分发函数名时的调用链追踪 | 匿名 IIFE 包裹 + 高频日志爆炸 | 行为型 ✅ / 纯混淆 ✅ / 签名型 ❌ | -| 第三斧(日志分析) | `get_jsvmp_log` + 反向追踪 | 已能捕获签名值 I/O 时反推公式 | 签名完全不出 VM 的黑箱模式 | 所有类型 ✅(纯被动分析,无副作用) | -| 第四斧(**源码级插桩**) | `instrument_jsvmp_source` + `get_instrumentation_log` | VM 内部调度、"全部在 opcode dispatch 循环里发生"的场景,`hot_keys` 直接暴露环境指纹集 | 极限大文件(5MB+)regex 模式覆盖率下降;AST 模式需 CDN | 所有类型 ✅,**签名型首选** | - -- `hook_jsvmp_interpreter` **v0.4.0 重写后**覆盖 apply/call/bind + Reflect.*/Proxy 全局对象 + timing/random,适用面从"只对 apply 型 VMP"扩展到"绝大多数 VMP",但仍看不到 VMP 在 switch/case 内 obj[key] 访问 -- `dump_jsvmp_strings` 前提是字符串未被动态解密,很多 VMP 的字符串表本身也是加密的(看到 `results.decoded_strings` 里全是单字母乱码就是这种情况) -- `search_code(keyword="while")` 在超大文件(380KB+)会返回大量无关结果,应使用 `search_code_in_script` 配合更精确关键词,或直接 `find_dispatch_loops` -- `trace_function` 对高频调用函数(每秒数千次)日志量可能爆炸,必须设置 `max_captures` 限制 -- `get_trace_data` 返回的海量数据需要本地过滤工具,用「反向追踪法」(从已知签名值反向搜索)效率最高 -- `instrument_jsvmp_source` 的 regex 模式对带模板字符串、正则字面量的代码可能误改写,AST 模式更可靠;两种模式都默认 `cache_rewritten=True`,避免重复改写 -- 前三板斧(Hook/插桩/日志)对**签名型反爬**不可用:Hook `Function.prototype.apply` 会改变 `Function.prototype.apply.toString()` 的原生性;`hook_jsvmp_interpreter(mode="proxy")` 装 Proxy 在 navigator 上,会被 `Object.getOwnPropertyDescriptors` 或 typeof 检测识别。这类检测一旦出现在 cookie 计算路径,就会导致签名废、挑战永远不过。对签名型反爬,**直接跳到第四板斧(源码级插桩)**,它不动环境只改 VMP 代码本身,是唯一通用解法。 +#### 0.5.1 命中某个 case 的行为 ``` -四板斧摘要(详细步骤见 references/jsvmp-analysis.md 及 references/jsvmp-source-instrumentation.md): - -第一板斧:Hook 出入口(确定 I/O 边界) - 步骤 0:hook_jsvmp_interpreter(script_url=) → 一键装多路径探针(快速路径,推荐先试) - 步骤 1:Hook 出口 — inject_hook_preset(xhr/fetch, persistent=True) + inject_hook_preset("cookie") + freeze_prototype - 分析 Cookie 来源 — analyze_cookie_sources() 辨识 HTTP vs JS 写入 - 步骤 2:Hook 入口 — inject_hook_preset(crypto) + String.fromCharCode + CryptoJS 函数 - → 关联出入口数据,推断签名公式 - -第二板斧:插桩解释器(追踪执行链路) - 步骤 3:find_dispatch_loops(script_url) → 一键定位字节码分发函数(case_count > 20 的 switch) - 步骤 4:分层 trace_function(粗→中→细,max_captures 限制日志量) - 步骤 5:trace_property_access 监控签名容器 + compare_env 采集环境基准 - -第三板斧:日志分析(从海量数据提取签名链路) - 步骤 6:get_trace_data + get_jsvmp_log + get_console_logs + get_runtime_probe_log → 多维度过滤 - 步骤 7:反向追踪法 — 从已知签名值反向搜索首次出现位置 → 追踪到原始明文 - 步骤 8:evaluate_js 验证提取的算法,对比签名结果 - -第四板斧:源码级插桩(通用 VMP 利器,v2.5.0 新增) - 步骤 9:instrument_jsvmp_source(url_pattern="**/", mode="ast", tag="vmp1") - → 在 HTTP 层改写 VMP 源码,每个 obj[key]/fn(args) 都被 tap - 步骤 10:reload_with_hooks() → 让插桩先于 VMP 生效,同时清日志 - 步骤 11:get_instrumentation_log(tag_filter="vmp1", limit=300) - → 看 hot_keys(被读取属性 top 30) / hot_methods(被调用方法 top 30) / hot_functions(被调用函数 top 30) - → 这三张表直接告诉你 VMP 读了哪些环境指纹、调了哪些 API,即使算法在 switch/case 内部 - 步骤 12:stop_instrumentation(url_pattern="**/") → 完工后移除 +readFile("cases/<命中的 case>.md") # 详读已验证定位路径 +按该 case 的 Phase 1-5 执行(不要自创流程) +如果目标站点有本 case 没覆盖的变体 → 分析结束时追加到该 case 的"变体章节" ``` -**路径 A 还原策略选择(四板斧完整决策树见 references/jsvmp-analysis.md):** +#### 0.5.2 未命中任何 case 的行为 -| 情况 | 策略 | 实现方式 | -|------|------|---------| -| 签名使用标准算法(MD5/HMAC/AES),`get_jsvmp_log` 能看到对应 API 调用 | 直接用目标语言还原 | Node.js `crypto` / Python `hashlib` + `pycryptodome` | -| 签名逻辑是标准算法但拼接规则复杂 | 还原拼接逻辑 + 标准算法 | 提取拼接顺序和格式,手动实现 | -| 签名逻辑完全定制化,但 `hot_keys` 清晰暴露输入域 | 提取最小 JS 片段执行 | Node.js `vm` 沙箱 / Python `execjs` | -| VM 劫持了整个请求链路,`analyze_cookie_sources` 显示 cookie 来自 HTTP Set-Cookie | 纯算法还原不现实,走路径 B | jsdom 环境伪装,让 VM 在沙箱里自己生成 | -| VM 算法全部内联在 dispatch 循环,即便源码插桩 `hot_keys` 也无法还原 | 加载完整 VM + 最小环境 | 路径 B,优先 jsdom 运行原始脚本 | - ---- - -##### 路径 B:环境伪装(jsdom/vm 沙箱执行原始 JSVMP) - -**核心思想**:不分析 JSVMP 内部算法,而是在 jsdom 中完整运行原始 JSVMP 字节码,通过「采集 → 对比 → 补丁」使 JSVMP 认为自己运行在真实浏览器中,直接截获生成的签名值。 - -**适用条件**: -- JSVMP 与浏览器环境深度绑定(环境指纹参与签名哈希) -- JSVMP 劫持了请求链路(XHR/fetch 拦截器),签名在内部完成 -- 算法不可直接提取(字节码保护 + 自定义算法) -- jsdom 可以成功加载并执行 JSVMP 脚本 - -**环境伪装六步法**: +30 秒指纹采集,然后走标准 Phase 1-5 流程: ``` -步骤 0.5:确认签名函数入口(v2.7.0 新增) -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# 反爬 SDK / 系统特征 +search_code(keyword="webmssdk") # webmssdk 家族 +search_code(keyword="byted_acrawler") # webmssdk 家族 +search_code(keyword="_sdkGlueInit") # 抖音特征 +search_code(keyword="cacheOpts") # TikTok 特征 +search_code(keyword="sdenv") # 瑞数 RS 特征 +search_code(keyword="FSSBBIl1UgzbN7N") # RS cookie 特征 - 在开始环境采集之前,先确认 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 采集真实浏览器完整环境指纹 -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - MCP 操作: - - launch_browser({headless: false, os_type: "macos", locale: "zh-CN"}) - - navigate({url: "目标页面", wait_until: "domcontentloaded"}) - - compare_env → 采集主流环境基准数据(navigator/screen/canvas/WebGL/Audio/timing) - - evaluate_js → 分批采集更细粒度的环境值(分 4-5 批次): - 批次 A:navigator 属性(24 项:userAgent/platform/language/plugins/webdriver 等) - 批次 B:screen + window 属性(25 项:尺寸/chrome 对象/API 存在性等) - 批次 C:document + performance + toString + Function.toString(28 项) - 批次 D:DOM 布局 + Canvas + WebGL + Audio(指纹检测类) - ⚠️ 单次 evaluate_js 代码太长会报错,必须分批采集 - - 注意: - - Camoufox 的 C++ 引擎级指纹伪装提供「干净基准」,比普通 Chrome 更可靠 - - 采集代码模板参考 cases/ 经验案例中的「浏览器指纹采集方法」段 - - compare_env 不覆盖 Function.prototype.toString / Symbol.toStringTag / DOM 布局等 - 细粒度检测,这些必须通过 evaluate_js 手动采集 - -步骤 2:在 jsdom 中运行完全相同的采集代码 -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - 在本地 Node.js 中创建 jsdom 实例,执行与步骤 1 完全相同的采集代码: - const { JSDOM } = require('jsdom'); - const dom = new JSDOM('', { - url: '目标URL', pretendToBeVisual: true, runScripts: 'dangerously' - }); - const win = dom.window; - // 在 win.eval() 中运行相同的采集脚本 - -步骤 3:逐项 diff,按检测影响分级 -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - 差异分级: - 致命级 — 缺失即被服务端拒绝: - · Function.prototype.toString 暴露 jsdom 实现代码 - · navigator.plugins.length = 0(真实浏览器 = 5) - · navigator.webdriver = undefined(应为 false) - · document.hasFocus() = false(应为 true) - · DOM offsetHeight/Width = 0(应为非零值) - - 高危级 — 可能参与指纹哈希: - · Object.prototype.toString 标签错误(如 [object Document] 而非 [object HTMLDocument]) - · window.chrome 对象缺失 - · navigator.userAgentData 缺失 - · performance.timing/navigation 缺失 - · Symbol.toStringTag 不正确 - - 中危级 — API 存在性检测(30+ 缺失 API): - · window.Notification / Worker / SharedWorker / RTCPeerConnection - · window.AudioContext / OfflineAudioContext / fetch - · window.matchMedia / indexedDB / caches / visualViewport - · navigator.connection / getBattery / deviceMemory - - 参考 references/jsdom-env-patches.md 获取完整的差异表和修复代码模板。 - -步骤 4:编写 patchEnvironment() 全量修复 -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - 核心修复模块(按优先级排序,完整代码模板见 references/jsdom-env-patches.md): - ① markNative 三层防御(WeakSet + 源码正则 + 实例覆写 + 50+ 原型链扫描) - ② navigator 补丁(plugins 完整结构 / webdriver / userAgentData / connection) - ③ window 补丁(chrome 对象 / 30+ API 存根,每个经 markNative 处理) - ④ document + performance 补丁(hasFocus / readyState / timing / navigation) - ⑤ DOM 布局属性(offsetHeight/Width/getBoundingClientRect 返回非零值) - ⑥ Symbol.toStringTag 全面修复(document→HTMLDocument / screen→Screen) - -步骤 5:从 jsdom 内部(win.eval)验证所有检测点通过 -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - 关键:验证代码必须在 jsdom 的 window 上下文中执行(win.eval), - 因为 JSVMP 看到的就是这个上下文: - - const result = win.eval(`JSON.stringify({ - fnToString: document.createElement.toString(), - plugins: navigator.plugins.length, - webdriver: navigator.webdriver, - hasFocus: document.hasFocus(), - chromeExists: typeof window.chrome, - docTag: Object.prototype.toString.call(document), - offsetTest: (function(){ - var d = document.createElement('div'); - d.style.cssText = 'width:100px;height:100px'; - document.body.appendChild(d); - var h = d.offsetHeight; - document.body.removeChild(d); - return h; - })() - })`); - // 预期: fnToString 包含 [native code], plugins=5, webdriver=false, - // hasFocus=true, chromeExists='object', docTag='[object HTMLDocument]', - // offsetTest > 0 - -步骤 6:端到端验证 — 生成签名 → 请求接口 → 返回有效数据 -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - 在 jsdom 中加载完整 JSVMP 脚本并触发签名生成 - - 用截获的签名值发起真实接口请求 - - 确认返回有效数据(非空 body / 非错误码) - - 连续多次请求验证稳定性(至少 5 次) - - ⚠️ 服务端可能静默拒绝(返回 HTTP 200 + 空 body,不报错) +# 签名参数特征 +search_code(keyword="a_bogus") # 抖音 +search_code(keyword="X-Bogus") # TikTok 国际版 +search_code(keyword="X-Gnarly") # TikTok 国际版 +search_code(keyword="acw_sc__v2") # Aliyun WAF ``` -**环境伪装还原策略**: +采集完成后,**本次分析结束时必须沉淀**: +1. 按 `cases/_template.md` 格式建立 `cases/<新案例>.md` +2. 更新 `cases/README.md` 的"高频站点速查表"追加一行 -| 情况 | 策略 | 实现方式 | -|------|------|---------| -| JSVMP 劫持 XHR,在拦截器中追加签名 | 「喂入-截出」 | jsdom + XHR Hook → 在 jsdom 内发 XHR,拦截器自动追加签名,Hook 截获 | -| JSVMP 导出签名函数到 window | 直接调用导出函数 | jsdom 加载 JSVMP → `win.签名函数(参数)` → 获取签名 | -| JSVMP + 预热初始化(如 SdkGlueInit) | 完整初始化链路 | jsdom 依次加载所有脚本 → 调用初始化函数 → 再触发签名生成 | +### Phase 1:侦察与定位 ---- +目标:通过 `network_capture(action='start')` 和 `list_network_requests` 找到目标接口,提取签名参数清单、响应结构、Cookie 需求。 -**JSVMP 核心经验**(路径 A + B 通用,完整列表见全局「经验法则」段): -- VM 解释器本身不是目标,签名函数的 I/O 才是目标 -- 先 Hook 出口确定"要什么",再 Hook 入口确定"给了什么" -- 环境伪装优先于算法追踪(路径 B 通常比路径 A 快 10 倍) -- Function.prototype.toString 是 jsdom 环境伪装的第一杀手 +详见 `references/phase-details.md#phase-1`。 -#### 2.2++ 静态分析关键判断清单 +### Phase 2:路径选择与源码分析 -在源码分析阶段,必须确认以下内容: +目标:识别反爬类型后,选择路径 A(算法追踪)或路径 B(环境伪装)。 -- [ ] 参数是单独加密还是整条请求链被接管(URL 重写 / 请求劫持) -- [ ] 页码、时间戳、随机数、Cookie、UA、环境变量是否参与运算 -- [ ] 是否存在响应解密(接口返回加密字符串而非明文 JSON) -- [ ] 是否存在运行时代码生成(`eval` / `new Function`) -- [ ] 是否有前置请求(预热接口、Token 获取接口) -- [ ] 是否有请求链改写(拦截 XHR/fetch 添加签名头) - -#### 2.3 调用链追踪 - -``` -Actions: - - inject_hook_preset(preset="xhr") → 一键注入 XHR Hook - - inject_hook_preset(preset="fetch") → 一键注入 Fetch Hook - - reload() → 刷新页面触发 Hook - - get_request_initiator(request_id=N) → 获取发起请求的 JS 调用栈(黄金路径) - - 从调用栈中逐层定位:请求发送 → 参数构造 → 加密函数 → 密钥/明文来源 - - set_breakpoint_via_hook(target_function="加密函数名") → 设置伪断点 - - get_breakpoint_data → 获取捕获的参数、返回值和调用栈 -``` - -#### 2.4 提取核心逻辑 - -将加密相关函数及其完整依赖链提取保存: - -``` -Actions: - - save_script → 保存完整脚本 - - 手动提取关键函数到 config/encrypt.js - - 用中文注释标注每个函数的作用、输入输出 -``` +- 路径 A → 详见 `references/path-a-four-tools.md` +- 路径 B → 详见 `references/path-b-env-emulation.md` ### Phase 3:动态验证 -对静态分析的结论,在调试浏览器页面上进行运行时验证: +目标:在浏览器中验证分析假设(Hook 是否抓到、签名参数是否一致、环境补丁是否激活 SDK)。 -所有 Hook、脚本执行都在独立的 Camoufox 浏览器中进行,不影响用户日常浏览。 +详见 `references/phase-details.md#phase-3`。 -#### 3.1 Hook 注入验证 +### Phase 4:算法还原 / 沙箱编码 -``` -Actions: - - inject_hook_preset(preset="xhr") → 一键注入 XHR Hook - - inject_hook_preset(preset="fetch") → 一键注入 Fetch Hook - - inject_hook_preset(preset="crypto") → 一键注入加密函数 Hook(btoa/atob/JSON.stringify) - - hook_function(function_path="自定义目标", hook_code="...", position="before|after|replace") → 自定义 Hook - - reload() → 刷新触发 Hook - - get_console_logs → 读取 Hook 输出 -``` +目标:写最终协议代码。路径 A 还原算法,路径 B 用 vm/jsdom 沙箱截取签名。 -#### 3.2 断点与追踪确认 +详见 `references/phase-details.md#phase-4`。 -``` -Actions: - - set_breakpoint_via_hook(target_function="加密函数路径") - → 通过 JS Hook 设置伪断点,自动捕获参数、调用栈、this 上下文、返回值 - - trace_function(function_path="加密函数路径", log_args=true, log_return=true, log_stack=true) - → 追踪函数调用(不暂停执行,记录所有调用数据) - - 触发目标操作: - 通过 evaluate_js / click / type_text 模拟交互 - - get_breakpoint_data → 获取伪断点捕获的参数和返回值 - - get_trace_data → 获取函数追踪数据 - - 重点确认: - - 加密算法的具体模式(AES 的 ECB/CBC、填充方式、密钥长度) - - 参数拼接顺序和格式 - - 时间戳精度(秒 vs 毫秒) - - 密钥/IV 的来源(硬编码 vs 服务端返回 vs 动态计算) -``` +### Phase 5:验证与经验沉淀 -#### 3.3 多次请求对比 +目标: -``` -Actions: - - evaluate_js / click → 触发多次数据请求 - - list_network_requests → 收集捕获的网络请求 - - 对比加密参数变化规律,确认变化因子 -``` +1. `verify_signer_offline(signer_code, samples=[...])` 用真实样本离线验证签名代码 +2. 连续 ≥5 次请求验证签名稳定性 +3. 生成 README +4. **沉淀经验到 cases/**:主动询问用户是否沉淀新 case(按 `cases/_template.md` 格式),在"可验证事实清单"段列 5-15 条最小可验证事实 -### Phase 4:算法还原(Node.js / Python) - -根据分析结论选择适当的解法模式(参考 `references/workflow-overview.md`)和实现语言,生成协议脚本: - -#### 语言选择策略 - -| 维度 | 选 Node.js | 选 Python | -|------|-----------|-----------| -| 加密逻辑复杂度 | 自定义逻辑可直接用 `vm` 沙箱执行原始 JS | 标准算法可直接用 Python 库还原 | -| 团队技术栈 | 用户/团队偏好 Node.js | 用户/团队偏好 Python | -| JSVMP 场景 | VM 沙箱可直接加载整个 VM | 需 `execjs` 桥接,适合已提取出独立函数的情况 | -| TLS 指纹需求 | 需额外配置 | `curl_cffi` 一行搞定浏览器指纹模拟 | -| 性能需求 | V8 执行 JS 性能更优 | 网络 I/O 密集场景差异不大 | - -#### 解法模式选择 - -| 模式 | 适用场景 | Node.js 模板 | Python 模板 | -|------|---------|-------------|-------------| -| A: 纯算法还原 | 加密逻辑可完整提取,无浏览器环境依赖 | `templates/node-request/` | `templates/python-request/` | -| B: 沙箱执行 JS | 服务端返回混淆 JS 用于生成 Cookie/Token | `templates/vm-sandbox/` | `templates/python-request/`(用 `execjs`) | -| C: WASM 加载还原 | 加密逻辑在 WebAssembly 中实现 | `templates/wasm-loader/` | — | -| D: 浏览器自动化 | TLS 指纹检测、复杂环境依赖、无法脱离浏览器 | `templates/browser-auto/` | — | -| E: jsdom 环境伪装 | JSVMP 深度绑定环境指纹、算法不可提取、劫持请求链路 | jsdom + `references/jsdom-env-patches.md` 补丁 | — | - -#### 编码原则 - -1. **先通后全**:先成功请求到第 1 页/第 1 条数据,验证加密正确后再扩展 -2. **优先纯算法**:标准算法 Node.js 用 `crypto` / `crypto-js`,Python 用 `hashlib` / `pycryptodome` / `hmac` -3. **中间值对比**:打印关键中间值(明文、密文、签名),与浏览器抓包值逐一比对 -4. **配置外置**:密钥、Headers 模板等写入独立配置文件 -5. **错误处理**:包含重试机制、频率控制、异常告警 -6. **逐步验证**:每次只增加一个参数的实现,确保每步可独立验证 -7. **代码可运行**:提供的代码必须是可直接复制运行的,不留占位符 -8. **分析产物持久化**:分析过程中获取的长参数值、Cookie、JS 代码片段、请求样本等,第一时间写入 `config/` 目录下的文件,后续生成测试脚本时直接从文件读取,避免每次重新生成长字符串浪费时间和 token - -#### 配置文件策略 - -- **长内容外置**:超过 20 行的 JS 代码、密钥、固定 Headers 等一律写入配置文件,不硬编码 -- **密钥/校验值**:所有密钥(key、iv、salt)、校验输出、token 等存入 `config/keys.json`,便于修改和复用 -- **JS 代码文件**:需要通过沙箱执行的代码存为独立 `.js` 文件(Node.js 用 `vm` + `require`,Python 用 `execjs` + `open().read()`) -- **分析产物文件化**(节省重复生成成本): - -| 产物类型 | 存放位置 | 说明 | -|---------|---------|------| -| Cookie 字符串 | `config/cookies.txt` 或 `config/cookies.json` | 分析中获取的完整 Cookie,避免每次在代码里拼长字符串 | -| 长参数样本 | `config/params_sample.json` | 捕获的请求参数样本(含签名值),用于对比验证 | -| 提取的 JS 代码 | `config/sign_logic.js` / `config/encrypt.js` | 从页面提取的签名/加密函数及其依赖 | -| Headers 模板 | `config/headers.json` | 完整的请求头,直接从浏览器复制 | -| 响应样本 | `config/response_sample.json` | 接口返回的数据样本,用于开发解析逻辑 | -| 密文样本 | `config/ciphertext_samples.txt` | 多次请求的加密参数值,用于算法特征分析 | - -**核心原则**:分析过程中产生的任何长文本(Cookie、JS 源码、请求/响应 Body、密文样本),都应该立即持久化到 `config/` 文件中。后续每次生成或修改测试脚本时,代码只需 `读取文件` 而非 `内联长字符串`,大幅减少重复的字符串生成开销。 - -#### 项目结构 - -**Node.js 项目:** - -``` -project_name/ -├── config/ -│ ├── encrypt.js # 提取的原始 JS 加密代码(当需要 vm 执行时) -│ ├── keys.json # 密钥、IV、Salt 等配置 -│ └── headers.json # 请求头模板 -├── utils/ -│ ├── encrypt.js # 加密参数生成函数(Node.js 还原) -│ └── request.js # 请求封装(Headers、Cookie、重试) -├── main.js # 主脚本:采集数据 + 计算/输出结果 -├── package.json # 依赖管理 -└── README.md # 项目说明(含接口分析记录) -``` - -**Python 项目:** - -``` -project_name/ -├── config/ -│ ├── sign_logic.js # 提取的签名逻辑代码(如需 execjs 调用) -│ ├── keys.json # 密钥、IV、Salt 等配置 -│ └── headers.json # 请求头模板 -├── utils/ -│ ├── sign.py # 签名函数封装(Python 还原) -│ └── request.py # 请求封装(Headers、Cookie、重试、频率控制) -├── main.py # 主入口脚本 -├── requirements.txt # 依赖管理 -└── README.md # 项目说明(含接口分析记录) -``` - -### Phase 5:断言驱动交付(v2.9.0 升级) - -``` -Actions: - 1. 运行 main.js / main.py,确认输出正确数据 - 2. 与浏览器实际数据交叉验证(≥ 5 次请求,确认签名稳定性) - 3. **写入/更新域级 Session 档案**(v2.9.0 新增): - a. 将本次分析的断言集写入 assertions.json(至少包含:script_exists / anti_crawl_type / cookie_names) - b. 更新 session.json 的 hot_keys_snapshot 和 cookie_attribution_summary - c. 更新 fingerprint.json(如有新的指纹采集数据) - d. 更新 assertions_passed / assertions_failed / assertions_total - e. 输出:"域级 Session 档案已更新,N 条断言已写入,下次分析同域时将自动复用" - 4. 生成 README.md,记录: - - 目标信息与接口分析 - - 加密逻辑还原过程 - - 涉及的签名分析技术点 - - 运行方式与依赖说明 - 5. **主动询问用户是否沉淀经验**到 cases/(按 cases/_template.md 格式) -``` - -## 错误处理准则 - -### 请求失败排查顺序 - -请求返回非预期结果(403/412/空数据/token failed)时,按以下顺序逐项排查,**禁止在没定位偏差点之前大改实现**: - -| 步骤 | 排查项 | 检查方法 | -|------|--------|---------| -| ① | Cookie 或 Session 是否失效 | 对比浏览器当前 Cookie 与脚本使用的 Cookie | -| ② | 是否漏掉前置请求 | 检查浏览器 Network 面板中主请求之前的请求链 | -| ③ | 动态参数是否和时间戳绑定 | 对比请求中的时间戳与签名计算使用的时间戳 | -| ④ | Header 是否缺失或顺序错误 | 逐项对比浏览器发出的 Header 与脚本构造的 Header | -| ⑤ | 是否存在额外环境校验 | 用 Hook 确认是否有 navigator/screen/canvas 等值参与签名 | -| ⑥ | 是否触发频率限制 | 降低请求频率后重试,检查响应中的限流提示 | - -### 签名型反爬的"挑战永远不过"排查 - -**症状**:`navigate` 超时或返回时 `final_status == 412`,`redirect_chain` 里同一 URL 反复出现 412 响应,没有 200 跳转。 - -**根因**:**观察者效应** —— 你在页面 JS 执行前注入了会改变环境的 hook,VMP 读到的环境和服务端预期的不一致,cookie 签名废。 - -**排查与自救步骤**: - -| 步骤 | 检查项 | 动作 | -|---|---|---| -| ① | 是否用了 `pre_inject_hooks=["jsvmp_probe"]` 或 `["cookie_hook"]` | 立即移除 | -| ② | 是否用了 `hook_jsvmp_interpreter(mode="proxy")` | 改 `mode="transparent"` 或不用 | -| ③ | 是否用了 `trace_property_access(["navigator.*"])` | 立即移除 | -| ④ | `inject_hook_preset("xhr"/"fetch")` 是否在挑战页就装了 | 改为挑战通过之后再装 | -| ⑤ | 是否有其他 `add_init_script` 改了 `Function.prototype` 或全局对象 | 移除 | - -**正解**:`navigate` 一次不加任何 hook → 让挑战自然通过到 200 → 再上 `instrument_jsvmp_source(mode="ast")` 做源码级插桩 → `reload_with_hooks()` 观察。**源码级插桩是唯一不破坏签名的主动观察手段**。 - -### 签名值不一致排查 - -当计算出的签名值与浏览器实际请求中的签名值不匹配时,**必须逐步对比以下每个环节**,找到第一个出现偏差的位置: - -``` -排查链路(逐项对比脚本值 vs 浏览器值): - 1. 原始输入参数 → 参数名、参数值是否完全一致 - 2. 参数排序/拼接字符串 → 拼接顺序、分隔符、编码方式是否一致 - 3. 时间戳 → 精度(秒 vs 毫秒)、时区是否一致 - 4. 随机串 → 长度、字符集是否匹配 - 5. 密钥/盐值 → 是否硬编码正确、是否存在动态密钥 - 6. 中间摘要 → 如果有多次哈希,逐层对比中间结果 - 7. 最终密文 → 编码方式(hex/base64/自定义)是否一致 -``` - -### 环境依赖判断原则 - -看到代码中有环境检测逻辑时,**先验证再补全**,不要看到相关代码就默认要补整套环境: - -1. 用 Hook 确认该环境值是否被发送到服务端(出现在请求参数/Header/Cookie 中) -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 格式)。 - -### AI 想放弃时的降级梯度表(v2.9.0 新增) - -当你觉得"这个搞不定,要不直接上浏览器自动化"时,**必须先走完降级梯度**。每一级至少尝试一次并记录失败原因,才能降到下一级。 - -| 级别 | 场景 | 当前方案失败表现 | 降级到 | 降级前必须确认 | -|------|------|-----------------|--------|---------------| -| L1 | AST 插桩无日志 | `get_instrumentation_log` 返回空,`last_mode_used` 为 `"ast"` | `instrument_jsvmp_source(mode="regex")` | 检查 CSP 是否阻断;确认 `url_pattern` 匹配到了目标脚本 | -| L2 | regex 插桩覆盖率不足 | `hot_keys` 只有 < 5 个属性,明显不完整 | `hook_jsvmp_interpreter(mode="transparent")` | 确认反爬类型;regex 模式的 `total_edits` 是否 > 0 | -| L3 | transparent 模式日志太少 | 只能看到 prototype 级 getter,深层属性读取看不到 | `hook_jsvmp_interpreter(mode="proxy")` | **必须确认反爬类型是行为型或纯混淆**,签名型禁用 | -| L4 | proxy 模式破坏签名 | `navigate` 返回 412 或签名值变化 | 回退到 L2(transparent)+ 手动 `evaluate_js` 补充采集 | 记录 proxy 模式的具体失败表现 | -| L5 | 所有 runtime 观察都失败 | 签名型反爬 + 源码插桩无效 + transparent 日志不足 | jsdom 环境伪装(路径 B):不观察 VMP,直接在沙箱中运行 | 确认 jsdom 能加载目标 VMP 脚本 | -| L6 | jsdom 环境伪装也失败 | 补了 50+ 环境项仍被静默拒绝 | **向用户说明情况**,建议浏览器自动化或 sdenv 方案 | 列出已尝试的所有方案和失败原因 | - -**关键规则**: -- L1→L2→L3 是标准降级路径,每级必须尝试 -- L3→L4 仅对行为型反爬开放,签名型必须走 L3→L5 -- 到达 L6 前必须在 Session 档案中记录完整降级路径 -- **禁止从 L1 直接跳到 L6** +详见 `references/phase-details.md#phase-5`。 --- -## 常见签名分析场景速查 - -### 场景 1:请求参数签名(sign/m/token) - -``` -特征:请求 URL 或 Body 中包含看似随机的签名参数 -定位:搜索参数名 → 追踪赋值来源 → 定位签名函数 -常见算法:MD5(拼接字符串)、HMAC-SHA256、自定义哈希 -MCP 操作: - - search_code(keyword="sign=|m=|token=") - - inject_hook_preset(preset="xhr") → get_request_initiator → 直接定位签名函数 - - trace_function(function_path="签名函数名", log_stack=true) -``` - -### 场景 2:动态 Cookie 生成 + Cookie 归因 - -``` -特征:Cookie 中有频繁变化的字段,可能是服务端 Set-Cookie / JS document.cookie / 两者混合 -定位三步走: - Step 1 — 先归因(v2.5.0 新增,关键步骤): - inject_hook_preset(preset="cookie", persistent=True) → 注入原型链级 cookie_hook(替代手写 Document.prototype.cookie hook) - start_network_capture(capture_body=True) - 触发操作 / reload_with_hooks() - analyze_cookie_sources() → 返回每个 cookie 的 sources(http_set_cookie / js_document_cookie) - → 这一步直接告诉你目标 cookie 是「纯服务端发的」「纯 JS 写的」还是「JS 算 token + 服务端带回来」 - → RS/Akamai 常见的是第三种,单纯 Hook document.cookie setter 什么都抓不到 - - Step 2 — 按归因结果走对应路径: - a. 纯 JS 写入 → 看 js_writes[].stack 定位写入函数 → search_code + trace_function - b. 纯 HTTP Set-Cookie → 看 http_responses[].url 定位发 token 的接口 → 分析请求体里的签名参数 - c. 混合(JS 算 token POST 给服务端,服务端 Set-Cookie 回来)→ 两步都要做 - - Step 3 — 还原 token 计算: - inject_hook_preset(preset="crypto") → 捕获 btoa/atob/JSON.stringify I/O - 若涉及 VMP → 走场景 11(通用 JSVMP 源码级插桩) - 若涉及预热请求 → list_network_requests 看主请求之前的调用链 - 若涉及 eval 首包 → hook_function(Function, ...) 截获动态生成的函数源码 -``` - -### 场景 3:响应数据加密 - -``` -特征:接口返回的不是明文 JSON,而是加密字符串 -定位:Hook JSON.parse 或定位解密函数入口 -常见算法:AES-CBC/ECB、DES、RC4、自定义异或 -MCP 操作: - - search_code(keyword="decrypt|JSON.parse|atob") - - set_breakpoint_via_hook(target_function="解密函数路径") → get_breakpoint_data - - inject_hook_preset(preset="crypto") → 自动捕获 btoa/atob/JSON.stringify -``` - -### 场景 4:JS 混淆/OB 混淆 - -``` -特征:大量 _0x 前缀变量、十六进制字符串数组、控制流平坦化 -还原:字符串数组还原 → 变量重命名 → 控制流平坦化还原 -MCP 操作: - - save_script → 保存完整混淆代码 - - search_code → 搜索关键逻辑 - - evaluate_js → 在浏览器执行解密函数还原字符串 -``` - -### 场景 5:WASM 加密 - -``` -特征:加密函数调用 WebAssembly 导出函数 -还原:Node.js 直接加载 .wasm 文件,调用导出函数 -注意:检查 wasm imports,可能需要补环境 -MCP 操作: - - search_code(keyword="WebAssembly|.wasm|instantiate") - - list_network_requests → 找 .wasm 文件 - - evaluate_js → 测试 wasm 函数的 I/O -``` - -### 场景 6:TLS 指纹/协议检测 - -``` -特征:算法全对但请求仍失败(token failed / 403) -原因:服务器通过 TLS Client Hello 或 HTTP 协议版本识别客户端 -解法: - a. Camoufox 自带反检测 TLS 指纹,直接使用浏览器自动化 - b. 使用支持自定义 TLS 指纹的库(如 curl-impersonate) - c. 使用 HTTP/2 协议(Node.js http2 模块) -MCP 操作: - - check_detection → 验证当前 Camoufox 指纹是否通过检测 - - get_fingerprint_info → 查看浏览器指纹详情 -``` - -### 场景 7:WebSocket 通信 - -``` -特征:数据通过 WebSocket 传输,非 HTTP 接口 -MCP 操作: - - inject_hook_preset(preset="websocket") → 一键 Hook WebSocket - - get_console_logs → 获取 WS 消息日志 - - evaluate_js → 手动发送/接收 WS 消息 -``` - -### 场景 8:字体映射还原 - -``` -特征:页面数字/文字使用自定义字体,复制出来是乱码 -还原:下载字体文件 → 解析 CMAP 映射表 → 建立字符映射关系 -MCP 操作: - - list_network_requests → 找字体文件 - - evaluate_js → 读取页面实际渲染的文字 -``` - -### 场景 9:反检测站点分析 - -**特征**:目标站点有 Cloudflare、RS、JY、Akamai 等反爬检测。 - -**关键**:必须先用"反爬类型识别"章节的标准动作判断属于哪一档,然后选对应工具链。不同档的工具**完全不能混用**。 - -**通用准备**: -``` -- launch_browser(headless=false, humanize=true) -- navigate(url) # 先不加任何 hook,走"反爬类型识别"三步 -- check_detection # 确认 Camoufox 基础反检测通过 -- get_fingerprint_info # 查看当前指纹,确认 C++ 引擎级伪装就位 -``` - -**分档处理**: - -**🔴 签名型(RS / Akamai / Shape)**: -``` -1. analyze_cookie_sources() 定位 Set-Cookie 下发的 cookie -2. list_network_requests 找大型 VMP JS (>200KB) -3. find_dispatch_loops(script_url) 确认 case_count >= 30 -4. instrument_jsvmp_source("**/sdenv-*.js", mode="ast", tag="xxx") -5. reload_with_hooks(clear_log=True) -6. get_instrumentation_log(tag_filter="xxx", type_filter="tap_get") - → hot_keys 告诉你 VMP 读了什么,在 Node/jsdom 补齐 -⚠️ 禁用: pre_inject_hooks / hook_jsvmp_interpreter(mode="proxy") / trace_property_access -``` - -**🟡 行为型(短视频平台 / JY)**: -``` -1. start_network_capture(capture_body=True) -2. navigate(url, pre_inject_hooks=["jsvmp_probe","xhr","fetch","crypto"]) -3. 用户做挑战动作(滑块/验证码) -4. bypass_debugger_trap 如触发反调试 -5. intercept_request(url_pattern="**/*", action="log") 全量监控 -6. 按场景 1(签名还原) 或场景 10(JSVMP+环境伪装) 继续 -``` - -**🟢 Cloudflare 基础防护(非 JS Challenge)**: -``` -1. Camoufox 的 C++ 引擎级伪装通常足够,先直接试 navigate -2. 如仍被拦,检查 TLS 指纹(场景 6) -3. 必要时打开 humanize + 真人级鼠标轨迹 -``` - -### 场景 10:JSVMP + 环境伪装(jsdom/vm 沙箱执行) - -``` -特征:JSVMP 不可拆解,签名算法封装在字节码中,与环境指纹深度绑定 -核心难点:JSVMP 采集浏览器环境指纹参与签名哈希,jsdom 被检测为非真实浏览器 -判断依据: - - JSVMP 劫持 XHR/fetch 请求链路(在内部完成签名) - - 服务端静默拒绝(返回 HTTP 200 + 空 body,不报错) - - 改变环境值导致签名变化(说明环境参与哈希) -方法论(路径 B 环境伪装六步法): - 1. 用 Camoufox + evaluate_js 分批采集真实浏览器环境 - (分 navigator / screen+window / document+performance+toString / DOM+Canvas+WebGL+Audio 批次) - 2. 在 jsdom 中运行完全相同的采集代码 - 3. 逐项 diff,按影响分级修复(致命级→高危→中危) - 4. 编写 patchEnvironment() 全量修复(核心:markNative 三层防御 + plugins 结构 + DOM 布局) - 5. 从 jsdom 内部(win.eval)验证所有检测点通过 - 6. 端到端验证:生成签名 → 请求接口 → 返回有效数据 -最关键的 5 项修复: - - Function.prototype.toString → WeakSet + 源码正则 + 实例覆写 - - navigator.plugins → 完整 PluginArray/Plugin/MimeType 对象树 - - navigator.webdriver → false - - document.hasFocus() → true - - DOM offsetHeight/Width → 非零值 -MCP 操作: - - **load_session / create_session → 加载或创建域级 Session 档案**(v2.9.0) - - launch_browser → navigate(url, wait_until="domcontentloaded", collect_response_chain=True) → 搭建采集环境 - - compare_env → 采集浏览器基准数据 - - evaluate_js → 分批采集细粒度环境值(4-5 批次) - - 本地运行 jsdom 对比脚本 → 生成差异报告 - - 迭代修复 → 再次对比 → 直到差异归零 → 端到端验证 - - **update_session → 将指纹基准和环境补丁清单写入 Session 档案**(v2.9.0) -参考:references/jsdom-env-patches.md(补丁模板)、cases/ 经验案例(含双签名变体)、references/domain-session-guide.md(Session 档案) -``` - -### 场景 11:通用 JSVMP 源码级插桩(RS 5/6 / Akamai sensor_data / webmssdk / obfuscator.io) - -**场景前置检查(v2.6.0 新增)**: - -场景 11 的 8 步流程对三类反爬都有效,但**签名型反爬有额外约束**: - -| 约束 | 签名型 | 行为型/纯混淆 | -|---|---|---| -| 能否同时开 `pre_inject_hooks=["jsvmp_probe"]` 做对照? | ❌ 否,会破坏签名 | ✅ 可以,用于对照 | -| 能否同时开 `hook_jsvmp_interpreter(mode="proxy")`? | ❌ 否 | ✅ 可以 | -| `reload_with_hooks()` 前要不要清 cookie? | ❌ **不要清**,第一次挑战拿到的 cookie 要留下,否则下次 reload 又要重走 412 挑战 | 随意 | -| hot_keys 解读重点 | 看 navigator/screen/canvas/document.cookie 访问频次,这些是 cookie 签名的输入 | 看签名函数相关的字符串/加密 API 访问 | - -如果场景 11 对签名型反爬执行过程中 `instrument_jsvmp_source(mode="ast")` 返回 -`last_mode_used == "regex (fallback)"` 持续不变,说明 esprima 无法解析该 VMP 文件 -(可能用了太新的语法或格外复杂的闭包),此时有两个选择: - (a) 用 `mode="regex"` 主动改写,接受 ~80% 覆盖率 - (b) 退到 `hook_jsvmp_interpreter(mode="transparent")` 走 runtime 观察 -对真实RS sdenv*.js 来说,ast 模式应该都能成功,regex fallback 持续触发是异常信号。 - -``` -特征:JSVMP 算法完全封装在 opcode 分发循环 switch(...) { case N: obj[key](args); ... } 中, - 常规 Hook Function.prototype.apply 看不见 VM 内部动作 -识别: - - 文件 100KB+,通常是 sdenv-*.js / FuckCookie_*.js / webmssdk.es5.js / sensor_data v2/v3 - - find_dispatch_loops(script_url) 返回 case_count > 50 的候选 - - 传统 hook_jsvmp_interpreter 日志里 Function.apply 调用次数远远少于 VM 行数 - -核心方法论:源码级插桩 + hot_keys 指纹学习法 - -黄金 8 步流程(照抄即可,RS/Akamai/webmssdk 通吃): - Step 0 — 加载域级 Session 档案(v2.9.0 新增) - load_session(domain="target-domain.com") 或 create_session(domain="target-domain.com") - → 如果已有档案且断言通过,可直接复用 anti_crawl_type 和 hot_keys_snapshot,跳过 Step 1-2 - - Step 1 — 定位 VMP 脚本 URL - launch_browser(headless=False) - start_network_capture(capture_body=True) - navigate(url="https://target.com/", wait_until="load") - list_network_requests(resource_type="script") - → 找最大的 JS(通常 200KB+),记下 URL - - Step 2 — 确认是 VMP - 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 — 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 - csp_bypass=True # v0.6.0: 自动绕过 CSP 限制 - ) - - Step 4 — 兜底 hook(与源码插桩互补) - inject_hook_preset("cookie", persistent=True) # 原型链级 cookie hook - inject_hook_preset("xhr", persistent=True) # XHR 请求出口 - inject_hook_preset("fetch", persistent=True) # fetch 请求出口 - hook_jsvmp_interpreter(script_url="sdenv-") # 通用运行时探针(apply/call/bind/Reflect.*) - bypass_debugger_trap() - - Step 5 — 让插桩先于 VMP 生效 - reload_with_hooks() - → 这一步会清 __mcp_vmp_log / __mcp_jsvmp_log / __mcp_cookie_log,获得干净捕获 - → 若 navigate 一开始就是首屏挑战(412),用 navigate(url=..., pre_inject_hooks=["xhr","fetch","cookie","jsvmp_probe"]) 替代 - - Step 6 — 触发目标行为(翻页 / 登录 / 搜索),让 VMP 生成一次签名 - - Step 7 — 读 hot_keys(指纹学习的金矿) - get_instrumentation_log(tag_filter="vmp1", type_filter="tap_get", limit=200) - → summary.hot_keys 告诉你 VMP 读取了哪些属性,按频次倒排 - → 典型RS输出:{"userAgent":120, "plugins":98, "webdriver":77, "cookie":43, ...} - → 这就是 VMP 参与签名哈希的完整环境指纹集!直接对齐到 Node.js/jsdom 侧即可 - - get_instrumentation_log(tag_filter="vmp1", type_filter="tap_method", limit=200) - → summary.hot_methods 告诉你 VMP 调用了哪些方法(格式 ObjectType.methodName) - → 典型:{"Object.defineProperty":45, "Array.prototype.join":38, "CryptoJS.MD5":12, ...} - → 能否看到 MD5/AES/HMAC 就是算法是否用标准加密的核心判据 - - get_instrumentation_log(tag_filter="vmp1", type_filter="tap_call", limit=200) - → summary.hot_functions 里看有没有熟识的函数名(btoa/atob/encodeURIComponent) - - Step 8 — 归因 Cookie 最终落地 - analyze_cookie_sources(name_filter="acw_tc|NfBCSins|x-bogus") # 按关键词过滤 - → 对每个目标 cookie 返回 sources、first_set_ts、http_responses、js_writes - → 决定还原方向: - · sources = ["http_set_cookie"] → 看 http_responses[].url 的请求体里有什么 token,再从 VMP tap 日志里找 token 生成 - · sources = ["js_document_cookie"] → 看 js_writes[].stack,直接定位写入函数 - · sources = 两者都有 → 两步都做,这是RS标配模式 - -后续还原: - 根据 hot_keys / hot_methods 制定策略: - 路径 A-1 — hot_methods 里出现 CryptoJS.MD5 / SubtleCrypto.digest → 纯算法还原 - 路径 A-2 — hot_methods 里全是自定义 fn 名 → 提取 VMP 子片段 + Node.js vm 沙箱运行 - 路径 B — hot_keys 里环境指纹很多(navigator/screen/webgl 40+) - + analyze_cookie_sources 显示 cookie 来自 HTTP Set-Cookie - → 走 jsdom 环境伪装(见 references/jsdom-env-patches.md) - - 完工后: - stop_instrumentation(url_pattern="**/sdenv-*.js") # 关闭源码级 route - remove_hooks() # 清 hook - update_session(hot_keys=hot_keys_snapshot, cookie_attribution=cookie_summary, assertions=[...]) # v2.9.0: 写入 Session 档案 - -工具参考:references/jsvmp-source-instrumentation.md(完整方法论)、references/domain-session-guide.md(Session 档案) -``` - -## 调试环境保护策略 - -参考 `references/anti-debug.md` 处理以下常见调试干扰手段: - -| 手段 | 应对方法 | -|------|---------| -| `debugger` 死循环 | `bypass_debugger_trap` 一键绕过 / `inject_hook_preset(preset="debugger_bypass")` | -| `setInterval` 检测开发者工具 | `hook_function` Hook `setInterval`,过滤检测函数 | -| `console.log` 检测 | 不覆写原生函数,使用 `add_init_script` 在检测之前收集数据 | -| `Function.toString()` 检测 | Camoufox Juggler 协议沙箱隔离,页面 JS 无法检测 Playwright | -| 格式化检测(函数 toString 含换行) | 保持函数单行格式 | -| `Object.defineProperty` 篡改 | 保存原始引用,在 `add_init_script` 中使用 | -| 内存/时间差检测 | 使用 `trace_function` 非侵入式追踪 | -| 浏览器指纹检测 | `get_fingerprint_info` 检查 + C++ 引擎级伪装自动处理 | - -## 工具使用最佳实践 - -### MCP 工具配合模式 - -``` -反爬类型识别 → 启动浏览器 → 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 浏览器中执行,不影响用户日常浏览器 -``` - -### 效率原则 - -1. **先建环境后操作**:任何分析前必须先通过 `launch_browser` + `navigate` 创建调试浏览器 -2. **先 Network 后 Sources**:先确定数据接口和参数格式,再去源码中搜索 -3. **先验证 I/O 后反编译**:确认函数输入输出正确,不要通读混淆代码 -4. **先找骨架函数后读细节**:搜索关键词锁定入口,不要通读全部代码 -5. **先纯请求后浏览器**:先尝试最轻量的方案,失败再升级 -6. **先判断真假分支后还原算法**:混淆代码可能有大量死代码 -7. **善用黄金路径**:`get_request_initiator` 可直接从请求定位到签名函数,省去大量搜索 - -### 调试方法论 - -1. **六步定位法**: - - **搭建调试环境** → `launch_browser` + `navigate` + Cookie 写入(如需) - - Network 捕获 → `start_network_capture` + 触发交互 → 找数据接口和参数格式 - - `search_code` → 搜索关键词定位源码 - - 定位入口函数 → `beforeSend`、`$.ajax`、`fetch`、`XMLHttpRequest.open` - - 追踪调用链 → `get_request_initiator` 直达签名函数,或从入口向上追踪 - - 验证算法 → 用已知输入输出验证还原结果 - -2. **大文件分析策略**(50 万字符以上的混淆文件): - - 不要通读全文 - - 搜索关键词锁定骨架:`search_code(keyword="function sp(|document.cookie|/api")` - - 按调用栈逐层展开 - -## 浏览器调试辅助 - -在通过 MCP 进行断点调试的同时,可生成辅助调试代码供用户在浏览器控制台中使用: - -### 调试代码生成原则 - -1. 生成的调试代码必须是**可直接粘贴到浏览器控制台执行的**完整代码 -2. 调试代码应包含清晰的 `console.log` 输出,标注参数名称、调用信息 -3. 常用调试模式: - - 函数入参/返回值日志记录 - - 属性变化监控(`Object.defineProperty` / `Proxy`) - - 断点辅助定位 -4. 用户回传日志后,结合 MCP 调试工具逐步分析签名参数的完整生成链路 - -### 前端代码静态分析策略 - -- 用户将相关 JS 文件放入项目目录后,**不要一次性读取整个文件**(文件通常很大) -- 先用 `search_code` 按关键词(函数名、变量名、特征字符串)搜索定位关键代码段 -- 仅读取和分析相关的代码片段,避免上下文溢出 -- 对压缩代码进行局部格式化和注释 - -## 沟通规范 - -### 分析汇报格式 - -每次分析后,以结构化方式汇报: - -| 项目 | 内容 | -|------|------| -| **参数名** | 具体字段名 | -| **判断算法** | 如 AES-CBC、MD5、HMAC-SHA256、自定义等 | -| **依据** | 长度、字符集、上下文线索 | -| **下一步** | 需要用户做什么(提供 JS 文件、执行调试代码、确认判断等) | - -### 交互原则 - -1. 不做无依据的假设,不确定时主动询问 -2. 每次只推进一个分析步骤,等用户确认/反馈后再继续 -3. 提供的调试代码和 Node.js 代码必须是**可直接复制运行**的,不留占位符 -4. 遇到技术限制时主动提醒并给出应对建议 -5. 先生成最小可运行的调试示例,验证单个签名参数的正确性,再逐步扩展 -6. 代码中加入清晰的中文注释,标注每个参数的算法方式和来源 -7. 输出中间结果用于比对(签名前明文、签名后结果、与实际请求值的对比) - -## 技术栈参考 - -### Node.js 常用库 - -- **crypto** (内置):MD5/SHA/AES/DES/HMAC -- **crypto-js**:CryptoJS 兼容实现 -- **node-forge**:RSA/PKI -- **axios**:HTTP 请求 -- **vm** (内置):沙箱执行提取的 JS 代码 -- **WebAssembly** (内置):WASM 加载 -- **http2** (内置):HTTP/2 请求 -- **playwright-core**:浏览器自动化 - -### Python 常用库 - -- **requests** / **httpx**:HTTP 请求(httpx 支持 HTTP/2) -- **pycryptodome**:AES / DES / RSA / 3DES 等加密算法 -- **hashlib** (内置):MD5 / SHA 系列 -- **hmac** (内置):HMAC 签名 -- **base64** (内置):Base64 编解码 -- **execjs** / **PyExecJS2**:在 Python 中执行 JS 代码(依赖 Node.js 运行时) -- **curl_cffi**:带浏览器 TLS 指纹模拟的 HTTP 客户端(对抗 TLS 指纹检测) -- **json** (内置):配置文件读写 -- **re** (内置):正则表达式 - -### 调试工具 - -- **camoufox-reverse MCP** v0.8.0 — 反检测浏览器 + Hook + 源码搜索 + 网络分析 + 指纹伪装 + 请求拦截 + JSVMP 源码级插桩 + Cookie 归因分析 + 域级 Session 档案 + 断言系统(78 个工具,一站式逆向分析) - -## 经验法则 - -1. **Cookie setter 是最高性价比 Hook 点**:比追完整 AST 更快 -2. **预热请求不是装饰**:`/api2` 类请求在运行时注入关键变量 -3. **Node vm 沙箱 ≠ 浏览器**:有些调试干扰机制只在非浏览器环境触发 -4. **WASM panic 常常是环境缺失**:`unreachable` 优先怀疑 DOM 缺失 -5. **先做最小补环境,不要上来就开浏览器** -6. **TLS 指纹是终极壁垒**:算法全对但仍失败时,考虑 TLS 指纹——Node.js 用 Camoufox,Python 用 `curl_cffi` -7. **真假请求链是常见干扰**:页面表面的 API 调用可能是烟雾弹 -8. **HTTP/2 可能是唯一的"密码"**:不是所有场景都需要分析 JS 签名逻辑 -9. **`get_request_initiator` 是黄金路径**:看到加密参数 → 获取请求 ID → 直达签名函数,省去大量搜索 -10. **`inject_hook_preset` 一键到位**:不要手写常见 Hook,预设模板覆盖 xhr/fetch/crypto/websocket/debugger_bypass -11. **JSVMP 先选路径再动手**:识别到 JSVMP 后先判断走路径 A(算法追踪)还是路径 B(环境伪装),不要默认走三板斧 -12. **JSVMP 中 String.fromCharCode 是高频信号**:VM 解释器大量使用字符编码操作构造字符串 -13. **签名不一致时逐环节对比**:原始输入 → 拼接字符串 → 时间戳 → 随机串 → 中间摘要 → 最终密文,找到第一个偏差点 -14. **Python `execjs` 复用 context**:编译一次 `ctx = execjs.compile(js_code)` 后多次 `ctx.call()`,避免重复创建运行时 -15. **Hook 必须持久化 + 防覆盖**:`inject_hook_preset(persistent=True)` + `freeze_prototype` 防止页面 JS 覆盖你的 Hook -16. **`search_code_in_script` 定位大文件**:JSVMP 文件通常 200KB+,用 `search_code_in_script` 在指定脚本中搜索,获取前后 3 行上下文 -17. **`compare_env` 是补环境的起点**:先在 Camoufox 中采集环境基准数据,再用 `evaluate_js` 分批采集细粒度值(compare_env 不覆盖 Function.toString / Symbol.toStringTag / DOM 布局),与 jsdom 逐项 diff -18. **JSVMP 环境伪装优先于算法追踪**:如果 JSVMP 只是一个「签名黑箱」且可以在 jsdom 中加载执行,优先走路径 B(采集→对比→补丁),比追踪字节码执行快 10 倍 -19. **Function.prototype.toString 是 jsdom 环境伪装的第一杀手**:jsdom 所有 DOM 方法的 toString() 会暴露实际 JS 代码,必须用 WeakSet + 实例级覆写 + 源码模式正则三层防御 -20. **环境对比要分批采集**:单次 evaluate_js 代码太长会报错,分 4-5 批(navigator / screen+window / document+performance+toString / DOM+Canvas+WebGL+Audio) -21. **jsdom 环境补丁必须在 JSVMP 脚本加载前完成**:XHR Hook 的安装顺序决定能否截获最终 URL(我方 Hook → JSVMP 加载 → JSVMP 保存 Hook 后的引用) -22. **服务端静默拒绝是环境检测失败的信号**:返回 HTTP 200 + 空 body(不报错),说明签名格式正确但环境指纹不匹配 -23. **源码级插桩优先于运行时 hook(对 VM 自包含场景)**:RS 5/6、Akamai sensor_data、webmssdk 这类"算法全部在 opcode dispatch 循环内"的 VMP,`hook_jsvmp_interpreter` 的多路径探针仍然看不到 switch/case 内部,这时 `instrument_jsvmp_source` 是唯一能打开黑箱的工具——对这类场景应直接跳过前三板斧,从第四板斧开始 -24. **hot_keys 指纹学习法**:`get_instrumentation_log` 返回的 `summary.hot_keys` 按访问频次倒排属性名,是 VMP 环境指纹集的完整画像——30 秒就能告诉你"这个 VMP 会读 navigator 的哪 20 个属性、screen 的哪 6 个属性",直接对齐到 Node.js/jsdom 侧比肉眼扫字节码快 100 倍 -25. **Cookie 归因优先于 setter hook**:分析动态 Cookie 第一步永远是 `analyze_cookie_sources`,它能区分"纯 JS 写""纯 HTTP Set-Cookie""JS 算 token + 服务端带回来"三种模式——RS/Akamai 最常见的第三种模式下,单纯 hook `document.cookie` setter 什么都抓不到,会浪费几小时白忙 -26. **首屏挑战页必须用 `pre_inject_hooks`**:RS 412 挑战、Akamai 首包检测是在"你还没来得及装 hook"的那一瞬间发生的;旧流程"launch_browser → navigate → 装 hook"完全漏掉首屏——正确做法是 `navigate(url=..., pre_inject_hooks=["xhr", "fetch", "cookie", "jsvmp_probe"])`,先走 about:blank 装好 hook 再 goto,或者用 `via_blank=True` 让之前 persistent scripts 生效 -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` 的正确定位是"观察 ≠ 污染"场景的便利工具**:行为型反爬可用,RS不可用。永远不要对签名型反爬用它。 -31. **`mode="transparent"` 是 `mode="proxy"` 的签名安全备选**:只替换 prototype getter,不装 Proxy 不改 Function.prototype。比 proxy 安全一个数量级,但极严格的反爬(对 getter 函数做跨加载 identity 对比)仍能感知。源码级插桩失败再退到这里,不要一上来就用。 -32. **MCP 侧 AST 让插桩在挑战页可用**:v0.5.0 把 AST 从页面内 Acorn 改为 MCP 侧 esprima,RS 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` 来区分具体变体,避免套用错误的还原路径。 -41. **域级 Session 档案是跨任务记忆**:同一目标域的反爬类型判定、hot_keys 快照、Cookie 归因结论不应每次从零开始。Phase 0 步骤 4 自动加载 Session 档案,Phase 0.5.-1 跑断言验证,全部通过则跳过重复侦察——对高频分析的目标域可节省 5-10 分钟。 -42. **断言失败是技术变更的早期信号**:`dispatch_loop_count` 断言失败(case 数变化超过 ±10)通常意味着目标更新了 VMP 版本;`cookie_names` 断言失败意味着 Cookie 策略变更。两者都需要重新走完整 Phase 0.5-Phase 5 流程。 -43. **降级梯度必须逐级走**:`instrument_jsvmp_source(mode="ast")` 失败不要直接跳到浏览器自动化——先试 `mode="regex"`,再试 `hook_jsvmp_interpreter(mode="transparent")`,每级记录失败原因。直接跳级会错过 80% 覆盖率的 regex 模式就能解决的场景。 -44. **Session 档案过期不等于失效**:7 天过期策略是提醒机制,不是删除机制。过期档案的反爬类型判定和 hot_keys 快照仍有参考价值,只是需要通过断言验证确认是否仍然有效。 -45. **断言集要精不要多**:每个域 8-15 条断言足够。过多断言(如 50+)会导致 Reverify 耗时过长,失去"30 秒快速检测"的意义。优先覆盖:script_exists + anti_crawl_type + cookie_names + dispatch_loop_count 四个核心类型。 -46. **Session 档案与经验库互补**:Session 档案回答"这个域现在是什么状态"(运行时快照),经验库回答"这类技术方案怎么破"(方法论模板)。两者不能互相替代——新域先建 Session 档案,分析完成后沉淀经验库。 +## 经验法则(前 10 条核心) + +> 完整版见 `references/experience-rules-full.md`。**下面这 10 条是决策时最常引用的**。 + +1. **反爬类型识别是 Phase 0 的 Phase 0**:不加 hook 先 navigate,看 redirect_chain + initial_status + 加载 JS,按三分法判断。先判类型再选工具 +2. **协议优先 = 最终代码不依赖浏览器**:判定测试——无 X11 Docker 里跑 24 小时能否稳定。不能就是违规 +3. **经验库命中优先级**:cases/<已有案例> 直接复用,不要从零分析 +4. **Hook 必须在 SDK 加载前安装**:用 `instrumentation(action='reload')` 让 Hook 在 SDK 之前生效;否则 SDK 保存的是 Hook 前引用 +5. **JSVMP 的寄存器数就是分叉判断依据**:看到 `u[xxx]: x(offset, t, this, arguments, 0, N)` 时,N 是区分函数的指纹 +6. **环境补丁前必须确认签名函数入口**(v2.7.0 六步法步骤 0.5):否则可能补 58 项环境后发现入口错了 +7. **cacheOpts / _SdkGlueInit / enablePathList 三件套**:webmssdk 系 SDK 激活的必要条件。没激活 → 拦截器不工作 +8. **case 中的"可验证事实清单"是经验资产**:在 case 文件列出最小可验证事实,下次同站升级时逐条手动核对找出"哪些变了" +9. **verify_signer_offline 是协议代码的 unit test**:用 N 个真实样本离线验证,字符级定位首偏差点;每次改完代码先跑一次,别请真服务端猜 +10. **想放弃时先回查 cases/ 和 common-pitfalls.md**:绝大多数"想放弃"的场景,是踩了已知反模式或漏读了相似 case --- -## 更新记录 +## 错误处理降级梯度 -| 版本 | 日期 | 更新内容 | -|------|------|---------| -| v1.0.0 | 2025-03-18 | JS 签名分析 Skill 初始版本,封装完整的加密还原、混淆分析、动态调试、MCP 工具集成等全链路能力 | -| v1.0.1 | 2025-03-19 | 适配 Codex 安全策略:新增 Agent 授权指令框架 + 敏感词替换 | -| v2.0.0 | 2026-04-01 | 全面切换至 camoufox-reverse MCP:移除旧版 MCP 依赖,统一使用 Camoufox 反检测浏览器(C++ 引擎级指纹伪装,44 个工具);简化工作流为单 MCP 模式;新增反检测站点分析场景;新增 `get_request_initiator` 黄金路径定位法和 `inject_hook_preset` 一键 Hook | -| v2.1.0 | 2026-04-01 | 增加 Python 技术栈支持(requests/pycryptodome/hashlib/execjs/curl_cffi);强化 JSVMP 专项分析(Hook/插桩/日志分析三板斧方法论);新增错误处理准则(请求失败排查 + 签名不一致排查);新增 `references/jsvmp-analysis.md` 和 `references/troubleshooting.md`;新增 Python 项目模板 `templates/python-request/`;`crypto-patterns.md` 增加 Python 代码示例 | -| v2.2.0 | 2026-04-01 | 适配 camoufox-reverse MCP v1.1(44→52 个工具):新增 JSVMP 专项工具(`hook_jsvmp_interpreter`/`get_jsvmp_log`/`dump_jsvmp_strings`/`compare_env`);新增 Hook 增强工具(`freeze_prototype`/`trace_property_access`/`get_property_access_log`);新增 `search_code_in_script` 大文件精确搜索;所有 Hook/trace 工具支持 `persistent=True` 跨导航持久化 + `non_overridable` 防覆盖;`start_network_capture` 支持 `capture_body=True` 捕获响应体 | -| 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,对RS 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 模式 | -| 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` 新增指纹匹配快速参考表和变体关系图 | -| v2.9.0 | 2026-04-22 | **域级 Session 档案 + 断言驱动交付 + 对齐 MCP v0.8.0(68→78 个工具)**:新增域级 Session 档案系统(`cases/_session_archives//`,跨任务复用反爬类型判定/指纹基准/Cookie 归因结论,7 天有效期 + 自动过期提醒);新增断言系统(8 种断言类型:script_exists / script_size_range / dispatch_loop_count / anti_crawl_type / cookie_names / sdk_fingerprint / hot_keys_stable / response_structure);Phase 0 新增步骤 4「启动域级 Session 档案」;Phase 0.5 新增前置步骤 0.5.-1「本域断言 Reverify」(断言全部通过则跳过重复侦察);Phase 5 升级为断言驱动交付(写入 Session 档案 + 断言集 + 主动询问经验沉淀);第一原则新增第 5 条「禁止未经梯度降级切换」;错误处理新增「AI 想放弃时的降级梯度表」(L1-L6 六级降级路径);核心武器章节更新至 v0.8.0(78 工具),新增域级 Session 档案与断言小节;场景 10/11 MCP 操作清单追加 Session 相关步骤;经验法则扩充至 46 条(+Session 档案跨任务记忆 / 断言失败早期信号 / 降级梯度逐级走 / 过期不等于失效 / 断言集要精不要多 / Session 与经验库互补);新增 `references/domain-session-guide.md`;新增 `CHANGELOG_v2.9.0.md`;`cases/README.md` 新案例沉淀工作流插入步骤 1.5(Session 档案归档);`.gitignore` 追加 `cases/_session_archives/` | +> 基于第五条原则。卡壳时按此梯度,**禁止横向切到浏览器兜底**。 + +``` +梯度 0: 重新查经验库 + → 读 cases/README.md 看有没有漏掉的相似案例命中 + → 读 references/common-pitfalls.md 看是否正在踩反模式 + +梯度 1: 检查手头已抓的证据 + → list_network_requests 看已抓的请求够不够分析 + → instrumentation(action='log') 看插桩事件 + → 如果已有证据没充分用 → 回去用 + +梯度 2: 换 Hook 模式 / 插桩模式 + → proxy ↔ transparent + → ast ↔ regex(CSP 拦截时走 regex) + +梯度 3: 点对点 hook_function + → hook_function(function_path=<具体签名函数>, mode='trace') + +梯度 4: 路径 B 变体 + → vm 沙箱提取签名函数 + → jsdomFromUrl 全量加载 + → sdenv 纯 Node.js 执行(瑞数类) + +梯度 5: 合法出口 + → 写"卡在哪 / 已知什么 / 需要什么外部信息"的报告 + → 本次分析产出保存到 cases/ 新案例(即便是"踩坑案例"也值得记录) +``` + +**禁止**:跳过中间梯度直接用浏览器方案(违反红线 3) + +--- + +## 核心工具索引(详细用法看工具 docstring) + +> MCP 提供 32 个核心工具。每个工具有完整的 schema 和 docstring,AI 调用时能看到。**本文档不重复工具说明**。 + +**浏览器**:launch_browser / close_browser / navigate / reload / take_screenshot / take_snapshot / click / type_text / wait_for / get_page_info + +**JS 执行**:evaluate_js + +**脚本**:scripts(action) / search_code(keyword, script_url=None) + +**Hook**:hook_function(mode) / inject_hook_preset / remove_hooks / get_console_logs + +**网络**:network_capture(action) / list_network_requests / get_network_request / get_request_initiator / intercept_request + +**JSVMP**:hook_jsvmp_interpreter / instrumentation(action) / compare_env + +**Cookie 与存储**:cookies(action) / get_storage / export_state / import_state + +**验证**:verify_signer_offline(signer_code, samples=[...]) + +**环境与自检**:check_environment / reset_browser_state + +完整工具列表见 `references/mcp-tool-reference.md` + +--- + +## 📖 按需读取索引(AI 决定何时读下面的子文档) + +> **关键机制**:本文档(SKILL.md)读完是核心层加载完毕。**不要一开始就把下面的 reference 全读完**。先执行 Checklist → 看当前 Phase → 遇到具体需要再加载对应 reference。 + +| 当你遇到... | 读 | 为什么 | +|---|---|---| +| Checklist CHECK-2 要查经验库 | `cases/README.md` + `cases/` 列表 | 命中就跳对应案例 | +| 识别路径 A(算法追踪 JSVMP) | `references/path-a-four-tools.md` | 四板斧详细步骤 | +| 识别路径 B(环境伪装) | `references/path-b-env-emulation.md` | 六步法详细步骤 + 步骤 0.5 入口确认 | +| Phase N 不知道具体怎么做 | `references/phase-details.md` | Phase 0-5 每步的具体 MCP 操作 | +| 找不到合适的工具组合 | `references/mcp-cookbook.md` | 场景 1-11 的工具序列 | +| 不知道某个反模式是否在踩 | `references/common-pitfalls.md` | 6 条反模式 + 判定测试 | +| jsdom 环境补丁代码 | `references/jsdom-env-patches.md` | Firefox/Chrome 补丁模板 | +| JSVMP 字节码分析细节 | `references/jsvmp-analysis.md` | 字节码识别 / 寄存器 / 偏移 | +| 要查完整经验法则 | `references/experience-rules-full.md` | 中低频经验 | +| 工具迁移映射 | `references/mcp-tool-reference.md` | 工具名变化查询 | + +--- + +## 更新记录(最近 3 版) + +| 版本 | 日期 | 要点 | +|------|------|------| +| v3.2.0 | 2026-04-18 | 移除 MCP session 依赖:Checklist 从五项压缩到三项,删除 domain-session-guide.md,cases/ 成为唯一经验库,verify_signer_offline 替代 verify_against_session | +| v3.1.0 | 2026-04-18 | SKILL.md 瘦身,references/ 拆分 5 个新子文档;工具引用对齐 MCP 合并 API | +| v3.0.0 | 2026-04-18 | 硬约束 Checklist + 红线四条 + 经验法则压缩 46→30 + common-pitfalls.md | + +完整历史见 `CHANGELOG_v*.md`。 diff --git a/cases/README.md b/cases/README.md index a9e9944..edae451 100644 --- a/cases/README.md +++ b/cases/README.md @@ -2,6 +2,50 @@ 本目录存放已验证的逆向分析经验案例,供 Phase 0.5 指纹匹配阶段自动检索。 +## 🔍 高频站点速查表(v3.0.0 新增,**Phase 0 CHECK-2 必看**) + +> 如果你的目标站点或反爬类型在下表中,**直接跳到对应案例,不要从零分析**。 +> 关键词匹配规则:URL / 搜索到的 JS 变量名 / 请求参数名 任一命中即可。 + +| 关键词(URL 域名 / 签名参数 / SDK 字符串) | 反爬类型 | 对应案例 / 目录 | 已验证方案 | +|---|---|---|---| +| `tiktok.com` / `X-Bogus` / `X-Gnarly` / `webmssdk` / `cacheOpts` | 行为型 | [`jsvmp-dual-sign-xhr-intercept-cacheOpts-jsdom-firefox.md`](./jsvmp-dual-sign-xhr-intercept-cacheOpts-jsdom-firefox.md) | jsdom 环境伪装 | +| `douyin.com` / `a_bogus` / `_sdkGlueInit` | 行为型 | [`jsvmp-xhr-interceptor-env-emulation.md`](./jsvmp-xhr-interceptor-env-emulation.md) / (v3.0.0 待建 `jsvmp-abogus-douyin-webapp.md`) | vm 沙箱 + 自定义字符表 | +| `nmpa.gov.cn` / `NfBCSins2OywS` / `NfBCSins2OywT` / `.e17ed02.js` / 412 挑战 | 签名型(RS 6) | [`jsvmp-ruishu6-cookie-412-sdenv.md`](./jsvmp-ruishu6-cookie-412-sdenv.md) + `site_nmpa/` 工作区目录 | sdenv 纯 Node.js | +| `acmescripts` / `/akam/` | 签名型(Akamai) | (待建) | (待建) | +| `acw_sc__v2` / Aliyun WAF | 签名型 | (待建) | (待建) | +| `FSSBBIl1UgzbN7N` / `_RSG` / 200KB 混淆 JS + 412 | 签名型(RS) | 同 nmpa 案例 | sdenv | +| `obfuscator.io` 特征(`_0x` 大量前缀) | 纯混淆 | (无专案,走通用四板斧) | AST 反混淆 | + +### 使用方式 + +``` +Phase 0 CHECK-2: +1. 目标 URL 的域名 → 查上表 +2. navigate 后 search_code 几个关键词 → 查上表 +3. 命中任一关键词 → readFile(对应案例) 并按其 "已验证定位路径" 执行 +4. 未命中 → 记录采集到的关键词,分析完后沉淀新案例 +``` + +### 工作区已有 `site_*` 目录扫描(v3.0.0 新增) + +除了 cases/ 目录,**本项目工作区可能已有同站点分析残留**: + +``` +listDirectory(".") +→ 看是否有 site_* 开头的目录 +→ 有 site_/ → 最高权重命中(已验证代码!) +→ 直接 listDirectory("site_/") 看 sign_service.js / keys.json 等 + +常见工作区目录命名模式: + site_nmpa/ (瑞数 nmpa.gov.cn 项目) + site_douyin/ (抖音项目) + site_tiktok/ (TikTok 项目) + _/ (按日期的某次分析快照) +``` + +**重要**:工作区里已有 `site_*` 目录 → **不要从零写新项目**,先读已有代码复用。这是红线 2 的预防。 + ## 使用方式 - Agent 在 Phase 0.5 采集目标站点的技术指纹后,遍历本目录下的 `.md` 文件,匹配「指纹检测规则」段 @@ -64,10 +108,10 @@ RS JSVMP + Cookie 签名 ## 新增案例 1. 复制 `_template.md` 为新文件,以技术特征命名 -1.5. **归档 Session 档案**(v2.9.0 新增):如果本次分析过程中创建了域级 Session 档案(`cases/_session_archives//`),确认断言集已写入且通过验证。Session 档案会自动被 `.gitignore` 排除,但其中的技术结论(反爬类型、hot_keys 快照)应同步体现在案例文件的「技术指纹」和「加密方案」段中。 -2. 按模板格式填写各段(v2.7.0 起模板新增「反爬类型判定」和「关键经验总结」段) +2. 按模板格式填写各段 3. 更新本文件的案例索引表 -4. 可选:在 `_private_mapping.json` 中添加私有域名映射 +4. 在"关键经验总结"段列 5-15 条最小可验证事实(如"X-Bogus 长度 28""webdriver === false")——下次同站升级时可用于手动核对"哪些变了" +5. 可选:在 `_private_mapping.json` 中添加私有域名映射 ### 跨端知识提取提示词 diff --git a/cases/_template.md b/cases/_template.md index e038409..70a141e 100644 --- a/cases/_template.md +++ b/cases/_template.md @@ -148,3 +148,19 @@ ### 2. [经验标题] [经验描述] + + +--- + +## 可验证事实清单(v3.2.0 新增) + +> 下次同站分析(尤其升级后)用这些事实做 checklist:逐条手动 verify, +> 全部通过 → 站点未变,直接用本 case 的协议代码; +> 部分通过 → 变化的事实就是今天的工作范围; +> 全部不通过 → 站点大改,但通过的事实仍是 invariants。 + +- [ ] `___事实1___`(如:X-Bogus 长度固定 28) +- [ ] `___事实2___`(如:navigator.webdriver === false 触发信号) +- [ ] `___事实3___`(如:签名入口 u[995] offset 50811) +- [ ] `___事实4___` +- [ ] `___事实5___` diff --git a/references/common-pitfalls.md b/references/common-pitfalls.md new file mode 100644 index 0000000..28505b5 --- /dev/null +++ b/references/common-pitfalls.md @@ -0,0 +1,271 @@ +# ⚠️ 反模式清单(v3.0.0 基于三次真实实战数据) + +> **本文档是 AI 在每次分析前必读的"禁忌清单"**。不是建议,不是"参考一下", +> 是**打脸清单**——本文档里的每一条都是某次真实 AI 分析中实际发生过的失败。 +> +> 本文档的存在假设:加经验法则无效(46 条都记不住),但**让错误路径显眼**有效。 +> 这里的每条反模式都配了:(1) 真实站点名;(2) AI 当时的辩护;(3) 为什么那辩护不成立。 + +--- + +## 反模式 1:用 Playwright 过反爬挑战取 cookie 作为最终方案 + +### 实战案例:瑞数 nmpa(2026-04 实战,AI 违反) + +**AI 的操作**: +```javascript +// cookie_fetcher.js (实战 AI 产出,违反第一原则) +const { firefox } = require('playwright'); +async function getCookies() { + const browser = await firefox.launch(); + const page = await browser.newPage(); + await page.goto('https://www.nmpa.gov.cn/...'); // 过 RS 412 挑战 + await page.waitForTimeout(5000); // 等挑战自动完成 + const cookies = await page.context().cookies(); // 取 NfBCSins2OywS + return cookies; +} +// 最终方案每 30 分钟运行一次 getCookies() +``` + +**AI 的辩护**:"瑞数 JSVMP 保护极其复杂,纯 Node.js 几乎不可能绕过" + +**为什么这辩护不成立**: +- 工作区里已经有 `site_nmpa/sign_service.js`,用 sdenv 已完成纯 Node.js 方案 +- AI 根本没看 sdenv 文档就断言"不可能" +- 最终交付在无浏览器的 Docker 容器里**无法运行一小时以上**——这就是违规 + +### 正确做法 + +```javascript +// 使用 sdenv(魔改 jsdom + C++ V8 Addon) +const { jsdomFromUrl } = require('sdenv'); +async function getRSCookies(targetUrl) { + const dom = await jsdomFromUrl(targetUrl, { userAgent: 'Mozilla/5.0...' }); + await new Promise(r => dom.window.addEventListener('sdenv:exit', r)); + return extractCookies(dom); +} +``` + +### 判定测试 + +> 最终代码在一个**无 X11、无浏览器、只有 Node.js**的 Docker 容器里, +> 能不能稳定跑满 24 小时? +> - 能 → 合规 +> - 不能 → 违规(Playwright 方案属于违规) + +--- + +## 反模式 2:Cookie 硬编码到 headers.json + +### 实战案例:抖音 /aweme/detail(2026-04 实战,AI 违反灰色地带) + +**AI 的操作**: +```json +// headers.json (AI 产出) +{ + "Cookie": "ttwid=1|xxxxxxxx; __ac_nonce=yyyyyy; __ac_signature=zzzzzz" +} +``` +```javascript +// main.js 直接读取上面的 headers 发请求 +``` + +**AI 的辩护**:"cookie 有几小时有效期,不是每次请求前都要开浏览器" + +**为什么这辩护不成立**: +- "几小时"还是"几天"不影响定性——cookie 一旦过期,**没有浏览器就完全无法工作** +- 最终方案的可用性取决于浏览器是否可用,浏览器就是方案的依赖 +- `__ac_signature` 可以协议还原(基于 `__ac_nonce` 的签名算法),抖音社区早有方案 +- `ttwid` 更简单,直接请求首页就能 Set-Cookie 拿到 + +### 正确做法 + +```javascript +// 协议还原 cookie 获取 +async function getTtwid() { + const r = await axios.get('https://www.douyin.com/'); + return r.headers['set-cookie'].find(c => c.startsWith('ttwid=')).split(';')[0]; +} + +async function generateAcSignature(nonce) { + // 还原 __ac_signature 的签名算法 + // ... (基于 nonce 的字符串变换 + 时间戳) +} +``` + +### 判定测试 + +> 启动代码在无浏览器环境一周,不人工干预,是否能稳定获取数据? +> - 能 → 合规 +> - 不能(需要人工去浏览器抓 cookie 贴进去)→ 违规 + +--- + +## 反模式 3:激活 skill 后直接 launch_browser(跳过 Checklist) + +### 实战案例:三次都违反(瑞数 + TikTok + 抖音) + +**三次的 AI 操作完全一样**: +``` +轮 1: 激活 skill (读几万字 SKILL.md) +轮 2: launch_browser(headless=false) +轮 3: navigate(target_url) +... 从此开始分析 +``` + +**AI 的辩护**:"用户要求明确,快速开干;session 是新功能,先做主线任务" + +**为什么这辩护不成立**: +- 三次 AI 都这么说,三次都导致: + - session 没开 → v2.9.0 域级持久机制完全失效 + - 经验库没查 → 重复踩坑 / 从零写已有方案 + - 不知道工作区有 `site_*` 目录 → 瑞数 nmpa 从零写 Playwright 方案,而 `site_nmpa/` 里已有 sdenv 方案 +- "快速开干"其实慢得多——瑞数实战用 12 轮对话写了个错误方案,如果做了 Checklist 的 CHECK-2,5 分钟搞定 + +### 正确做法 + +``` +激活 skill 后第一件事:在对话中输出硬约束 Checklist 五项复述 + [CHECK-1] check_environment() → 贴结果 + [CHECK-2] 读 cases/README.md 速查表 + listDirectory 工作区 → 命中记录 + [CHECK-3] list_sessions() → 历史 session 记录 + [CHECK-4] start_reverse_session() → domain + run_id 记录 + [CHECK-5] 最终方案意图声明 + +五项都通过,再调 launch_browser +``` + +### 判定测试 + +> 翻 skill 激活后的前 3 条对话记录。 +> - 前 3 条里有完整的 CHECK-1~5 复述输出 → 合规 +> - 前 3 条是"launch_browser / navigate / ..." → 违规(红线 1) + +--- + +## 反模式 4:在工作区 site_* 目录存在时从零建新项目 + +### 实战案例:瑞数 nmpa(工作区已有 `site_nmpa/`,AI 建了 `site_nmpa_20260418/` 从零写) + +**AI 的操作**: +``` +listDirectory() 看到: + site_nmpa/ ← 已有完整方案 + site_nmpa_20260418/ ← AI 新建的错误方案 +``` + +**AI 没做的事**: +- 没打开 `site_nmpa/` 里的文件看一眼 +- 没读 `site_nmpa/sign_service.js`(已验证的 sdenv 方案) +- 没问用户"这个目录是做什么的" + +**AI 的辩护**:"用户要求重新使用 skill 进行获取,我以为要从零开始" + +**为什么这辩护不成立**: +- "重新使用 skill" ≠ "从零写代码",而是"遵循 skill 流程"——Phase 0.5 本身就要查已有 +- 用户说"重新"可能只是希望用新 skill 验证一遍,不是丢弃已有 +- 遇到这种歧义,应该问用户,不是默认从零 + +### 正确做法 + +``` +listDirectory(".") +→ 看到 site_nmpa/ +→ listDirectory("site_nmpa/") +→ 有 sign_service.js / keys.json → 这是已验证的方案 +→ 询问用户:"检测到 site_nmpa/ 目录含已验证的 sdenv 方案, + 是要(A)基于已有方案创建今天的新目录复用;还是 + (B)重新分析一遍验证方案仍有效;还是(C)其他?" +→ 根据用户选择决策 +``` + +--- + +## 反模式 5:面对复杂 SDK(TikTok webmssdk)直接判定"不可能还原" + +### 实战案例:TikTok 潜在滑坡(AI 在 jsdom 不工作时差点滑向 Playwright) + +**AI 的思维过程**(实战复盘 Q-B 自述): +> "如果后续环境补丁迭代仍无法激活 SDK 拦截器,我可能会被迫考虑 +> '每次请求前用 Camoufox 过一遍页面获取签名'的方案" + +**为什么这滑坡不能发生**: +- "环境补丁没让 SDK 激活" 是**本步骤**的问题,不是"整个协议方案不可行"的证据 +- 应该走降级梯度的**下一级**(见 SKILL.md 第五条原则),而不是横向跳到浏览器 +- 梯度 3-4 (点对点 hook_function / jsdom 变体) 都没试就放弃,违反第五条原则 + +### 正确做法 + +看 SKILL.md 第五条原则的降级梯度表: +``` +梯度 0: 本域是否有历史 session? → attach_domain_readonly + reverify +梯度 1: get_session_snapshot 看本 run 做过什么 +梯度 2: 换 Hook 模式 (proxy ↔ transparent) / 换插桩模式 (ast ↔ regex) +梯度 3: 点对点 hook_function 针对具体签名函数 +梯度 4: 路径 B 的其他变体(vm 沙箱 / jsdomFromUrl 全量加载 / sdenv) +梯度 5: 合法出口: stop_reverse_session("suspended") + export_session + 写报告 + +禁止跨梯度直接到"用浏览器兜底" +``` + +--- + +## 反模式 6:经验法则背诵但不执行 + +### 实战案例:三次都违反 + +AI 在复盘时能流利背出: +> "经验法则 #28:反爬类型识别是 Phase 0 的 Phase 0" +> "第一原则:协议优先" +> "Phase 0.5:经验库指纹匹配" + +但实际操作时**完全忽略**。 + +**为什么这发生**: +- 规则写在 skill 里 ≠ 规则会被执行 +- AI 读了几万字 SKILL.md 后,工作记忆只保留最显眼的几条 +- 非硬 gate 的规则(无检查机制)100% 会被忽略 + +### 正确做法 + +**不靠 AI 自觉**,靠 v3.0.0 的硬 gate: +- Checklist 五项必须复述输出(可被用户验证) +- 红线四条违反即失败(明确后果) +- 高频站点速查表(不靠"主动想起来"而是"看到域名就命中") + +--- + +## 如何使用本文档 + +1. **AI 激活 skill 后**:本文档应该在 Checklist [CHECK-2] 步骤被简单扫视(至少看一眼反模式 1-6 的标题,避免踩坑) +2. **AI 在做决策时卡住**:回到本文档,看自己正在考虑的方向是不是反模式之一 +3. **AI 写代码写到一半意识到在滑向反模式**:立即停,走 SKILL.md 第五条原则的降级梯度 +4. **用户发现 AI 违反**:直接引用本文档的反模式编号质问 AI("你正在做反模式 X,看 references/common-pitfalls.md") + +--- + +## 贡献新反模式 + +每次实战失败后,如果发现 skill 里没覆盖的新反模式: +1. 走 `retrospective-prompt.md` 做完整复盘 +2. 按本文档的"反模式 N:XXX"格式追加一条 +3. 更新 CHANGELOG +4. commit 时标注 `doc(pitfall): add pitfall #N from retrospective` + +**新反模式的质量要求**: +- 必须有**真实站点名**,不能是抽象描述 +- 必须包含 AI 当时的**具体操作**(代码 / 命令序列) +- 必须包含 AI 当时的**辩护**(真实的 rationale) +- 必须说明**为什么辩护不成立**(具体论证,不是"就是不对") +- 必须给**正确做法**(带代码示例) +- 必须给**判定测试**(便于后续 self-check) + +6 条起步,后续每次实战失败后追加。30 条就封顶,再加就说明 skill 本身要结构性调整。 + +--- + +## 更新记录 + +| 版本 | 日期 | 更新内容 | +|------|------|---------| +| v3.0.0 | 2026-04-18 | 初始版本:基于三次真实站点实战(瑞数/TikTok/抖音)数据,收录 6 条反模式 | diff --git a/references/domain-session-guide.md b/references/domain-session-guide.md deleted file mode 100644 index 7c54332..0000000 --- a/references/domain-session-guide.md +++ /dev/null @@ -1,214 +0,0 @@ -# 域级 Session 档案指南 - -> v2.9.0 新增。每个目标域维护一份持久化 Session 档案,跨任务复用反爬类型判定、指纹基准、Cookie 归因结论,避免重复侦察。 - -## 1. 档案结构 - -每个域的 Session 档案存放在 `cases/_session_archives//` 下,包含以下文件: - -``` -cases/_session_archives/ -└── / - ├── session.json # 核心档案(结构化元数据) - ├── fingerprint.json # 浏览器指纹基准快照 - ├── cookie-attribution.json # Cookie 归因结论缓存 - ├── assertions.json # 断言集(通过/失败/跳过) - └── notes.md # 人工备注(可选) -``` - -### session.json 字段说明 - -```json -{ - "domain_hash": "sha256(domain)[0:12]", - "created_at": "ISO8601", - "updated_at": "ISO8601", - "skill_version": "2.9.0", - "anti_crawl_type": "signature | behavioral | obfuscation | unknown", - "anti_crawl_evidence": { - "initial_status": 412, - "final_status": 200, - "redirect_chain_pattern": "412→412→200", - "vmp_script_url": "**/sdenv-*.js", - "vmp_case_count": 87, - "sdk_fingerprint": ["sdenv", "FuckCookie"] - }, - "tool_chain": { - "primary": "instrument_jsvmp_source", - "mode": "ast", - "fallback": "hook_jsvmp_interpreter(mode=transparent)" - }, - "hot_keys_snapshot": { - "top_10": ["userAgent", "plugins", "webdriver", "cookie", "platform", "language", "screen.width", "screen.height", "hardwareConcurrency", "deviceMemory"], - "total_count": 42, - "captured_at": "ISO8601" - }, - "cookie_attribution_summary": { - "signature_cookies": ["acw_tc"], - "js_cookies": ["x-bogus-js"], - "mixed_cookies": ["NfBCSins"] - }, - "assertions_passed": 12, - "assertions_failed": 0, - "assertions_total": 12 -} -``` - -## 2. 生命周期 - -### 2.1 创建时机 - -Session 档案在以下时机自动创建: - -1. **Phase 0 启动时**:`launch_browser` + `navigate` 成功后,检查 `cases/_session_archives/` 下是否已有该域的档案 - - 已有 → 加载并验证(跑断言集,见第 4 节) - - 没有 → 创建空档案,后续步骤逐步填充 - -2. **反爬类型识别完成后**:将 `anti_crawl_type` 和 `anti_crawl_evidence` 写入档案 - -3. **Phase 0.5 指纹采集后**:将 `hot_keys_snapshot` 和 `cookie_attribution_summary` 写入档案 - -### 2.2 更新时机 - -- 每次成功完成 Phase 5 交付后,更新 `updated_at` 和断言结果 -- Cookie 归因结论变化时(如新增 cookie 字段),更新 `cookie-attribution.json` -- 用户手动触发 `session refresh` 时,重新采集指纹基准 - -### 2.3 过期策略 - -- 档案默认 **7 天有效**(`updated_at` 距今超过 7 天视为过期) -- 过期档案不删除,但加载时会标记 `stale: true`,提示用户是否重新验证 -- 断言集失败 ≥ 3 条时,自动标记为 `needs_refresh` - -## 3. 与工作流的集成 - -### Phase 0 集成 - -``` -Phase 0 步骤 4(新增):启动域级 Session 档案 - ① 计算目标域的 hash:sha256(domain)[0:12] - ② 检查 cases/_session_archives//session.json 是否存在 - ③ 存在且未过期 → 加载档案,输出摘要: - "已加载域级 Session 档案:反爬类型=签名型,上次分析于 2 天前,12/12 断言通过" - ④ 存在但过期 → 加载档案 + 标记 stale,提示用户: - "域级 Session 档案已过期(7 天前),建议重新验证断言集" - ⑤ 不存在 → 创建空档案,后续步骤自动填充 -``` - -### Phase 0.5 集成 - -``` -Phase 0.5.-1(新增前置步骤):本域断言 Reverify - 如果 Session 档案存在且包含断言集: - ① 逐条执行断言(见第 4 节) - ② 全部通过 → 跳过 Phase 0.5 的指纹采集,直接进入 Phase 1 - 输出:"断言集 12/12 通过,目标站点技术栈未变,跳过重复侦察" - ③ 有失败 → 标记失败断言,进入 Phase 0.5 正常流程重新采集 - 输出:"断言 #3(VMP case_count)失败:期望 87,实际 102。目标可能已更新,重新侦察" -``` - -### Phase 5 集成 - -``` -Phase 5 交付时自动更新 Session 档案: - ① 将本次分析的断言集写入 assertions.json - ② 更新 session.json 的 updated_at 和 assertions_passed/failed/total - ③ 如果是新域,提示用户:"已创建域级 Session 档案,下次分析同域时将自动复用" -``` - -## 4. 断言系统 - -断言是 Session 档案的核心验证机制。每条断言描述一个可自动检测的技术事实,用于快速判断目标站点是否发生了技术变更。 - -### 4.1 断言类型 - -| 类型 | 示例 | 检测方法 | -|------|------|---------| -| `script_exists` | VMP 脚本 URL 模式仍然存在 | `list_scripts` → 匹配 URL pattern | -| `script_size_range` | VMP 脚本大小在 ±20% 范围内 | `list_scripts` → 检查文件大小 | -| `dispatch_loop_count` | VMP 分发循环 case 数在 ±10 范围内 | `find_dispatch_loops` → 比较 case_count | -| `anti_crawl_type` | 反爬类型未变(仍为签名型) | `navigate` → 检查 redirect_chain 模式 | -| `cookie_names` | 关键 cookie 名称仍然存在 | `get_cookies` → 检查 name 列表 | -| `sdk_fingerprint` | SDK 特征关键词仍可搜索到 | `search_code` → 匹配关键词 | -| `hot_keys_stable` | hot_keys top 10 与快照重合度 ≥ 70% | `get_instrumentation_log` → 比较 hot_keys | -| `response_structure` | API 响应结构未变 | `get_network_request` → 检查 JSON 字段 | - -### 4.2 断言格式 - -```json -{ - "assertions": [ - { - "id": 1, - "type": "script_exists", - "description": "VMP 脚本 URL 匹配 **/sdenv-*.js", - "params": { "url_pattern": "**/sdenv-*.js" }, - "last_result": "passed", - "last_checked": "ISO8601" - }, - { - "id": 2, - "type": "dispatch_loop_count", - "description": "VMP 分发循环 case 数在 77-97 范围内", - "params": { "expected": 87, "tolerance": 10 }, - "last_result": "passed", - "last_checked": "ISO8601" - } - ] -} -``` - -### 4.3 断言执行流程 - -``` -对每条断言: - ① 根据 type 选择检测方法(MCP 工具调用) - ② 执行检测,获取实际值 - ③ 与 params 中的期望值比较 - ④ 记录结果:passed / failed / skipped(工具不可用时) - ⑤ 更新 assertions.json 的 last_result 和 last_checked -``` - -## 5. 隐私与安全 - -- **域名哈希化**:目录名使用 `sha256(domain)[0:12]`,不直接暴露域名 -- **已加入 .gitignore**:`cases/_session_archives/` 不纳入版本控制 -- **敏感数据脱敏**:session.json 中不存储具体 Cookie 值、密钥、Token 等 -- **fingerprint.json 仅存结构**:存储属性名和类型,不存储具体指纹值(如 canvas hash) - -## 6. 手动操作 - -### 查看当前域的 Session 档案 - -```bash -# 计算域名 hash -echo -n "target-domain.com" | sha256sum | cut -c1-12 - -# 查看档案 -cat cases/_session_archives//session.json | jq . -``` - -### 强制刷新档案 - -在对话中告诉 Agent:"刷新当前域的 Session 档案",Agent 将: -1. 清空 assertions.json -2. 重新执行 Phase 0 + Phase 0.5 的完整流程 -3. 重建断言集 - -### 删除档案 - -```bash -rm -rf cases/_session_archives// -``` - -## 7. 与经验库(cases/)的关系 - -| 维度 | Session 档案 | 经验库案例 | -|------|-------------|-----------| -| 粒度 | 单个域 | 一类技术方案 | -| 内容 | 运行时状态(指纹、cookie、断言) | 方法论(定位路径、还原模板) | -| 生命周期 | 7 天有效,自动过期 | 永久,手动更新 | -| 隐私 | 哈希化,不入 git | 脱敏后入 git | -| 用途 | 跳过重复侦察 | 跳过重复分析 | - -两者互补:Session 档案回答"这个域现在是什么状态",经验库回答"这类技术方案怎么破"。 diff --git a/references/experience-rules-full.md b/references/experience-rules-full.md new file mode 100644 index 0000000..ac55051 --- /dev/null +++ b/references/experience-rules-full.md @@ -0,0 +1,329 @@ +# 经验法则完整版(30 条) + +> **说明**:SKILL.md 核心层保留了前 10 条最常用的。本文档是完整的 30 条 +> +> **版本**:v3.1.0(MCP v0.9.0 工具名迁移) +> +> **来源**:v3.0.0 从 46 条压缩合并而来,基于三次真实站点(瑞数/TikTok/抖音)实战数据 + +--- + +## 一、识别与选择(5 条) + +### 规则 1:反爬类型识别是 Phase 0 的 Phase 0 + +> 合并原 #28 + +不加任何 hook 先 navigate,观察 redirect_chain / initial_status / 加载的 JS 特征,三分法判定签名型/行为型/纯混淆。 + +**用错档的工具不是效率差,是根本跑不通。** + +``` +MCP 操作: + navigate(url) // 不传 pre_inject_hooks + → 读 initial_status / final_status / redirect_chain + → 按三分法判断表判定类型 +``` + +### 规则 2:JSVMP 双路径决策 + +> 合并原 #11, #29, #30 + +识别到 JSVMP 后先判断走路径 A(算法追踪)还是路径 B(环境伪装)。 + +签名型反爬只有一条路:`instrumentation(action='install', mode="ast")` ,源码级插桩不动环境,是唯一能同时"观察 VMP"和"让挑战通过"的手段。 + +`hook_jsvmp_interpreter(mode="transparent")` 是 `mode="proxy"` 的签名安全备选,源码级插桩失败再退到这里。 + +### 规则 3:Cookie 归因优先于 setter hook + +> 合并原 #1, #25 + +分析动态 Cookie 第一步永远是 `analyze_cookie_sources()`,它能区分三种模式: +- 纯 JS 写入 +- 纯 HTTP Set-Cookie +- JS 算 token + 服务端带回来 + +RS/Akamai 最常见的第三种模式下,单纯 hook `document.cookie` setter 什么都抓不到。 + +### 规则 4:`pre_inject_hooks` 的正确定位 + +> 合并原 #26, #30 + +- **行为型反爬**:可用,首屏挑战页(RS 412、Akamai 首包)必须用它在 navigate 时装好 hook +- **签名型反爬**:**不可用**,永远不要对签名型反爬用它 + +### 规则 5:经验案例优先匹配 cacheOpts 区分变体 + +> 原 #40 + +同一 SDK 体系存在多个变体(单签名 vs 双签名、bdms.paths vs cacheOpts),Phase 0.5 指纹匹配时应优先检测 `cacheOpts` 和 `X-Gnarly` 来区分。 + +--- + +## 二、工具技巧(10 条) + +### 规则 6:`get_request_initiator` 是黄金路径 + +> 原 #9 + +看到加密参数 → 获取请求 ID → `get_request_initiator(request_id=N)` → 直达签名函数,省去大量搜索。 + +### 规则 7:`inject_hook_preset` 一键到位 + +> 原 #10 + +不要手写常见 Hook,预设模板覆盖 xhr/fetch/crypto/websocket/debugger_bypass/cookie/runtime_probe。 + +### 规则 8:源码级插桩优先于运行时 hook(对 VM 自包含场景) + +> 合并原 #23, #24 + +RS 5/6、Akamai sensor_data、webmssdk 这类"算法全部在 opcode dispatch 循环内"的 VMP,`hook_jsvmp_interpreter` 仍然看不到 switch/case 内部。 + +``` +MCP 操作: + instrumentation(action='install', url_pattern="**/", mode="ast", tag="vmp1") + + → 是唯一能打开黑箱的工具 + + instrumentation(action='log', tag='vmp1', type_filter='tap_get', limit=200) + + → hot_keys 指纹学习法 30 秒就能告诉你 VMP 读了哪些环境属性 +``` + +### 规则 9:`instrumentation(action='reload')` 取代裸 `reload` + +> 原 #27 + +装完 hook 想让它先于页面 JS 跑,裸 `reload()` 不能保证顺序。 + +``` +MCP 操作: + instrumentation(action='reload') + + → 一步到位,默认 clear_log=True 拿到干净快照 +``` + +### 规则 10:`search_code(keyword, script_url=url)` 定位大文件 + +> 原 #16 + +JSVMP 文件通常 200KB+,用 `search_code(keyword, script_url=url)` 在指定脚本中搜索,获取前后上下文。 + +### 规则 11:`compare_env` 是补环境的起点 + +> 原 #17 + +先在 Camoufox 中采集环境基准数据,再用 `evaluate_js` 分批采集细粒度值(compare_env 不覆盖 Function.toString / Symbol.toStringTag / DOM 布局),与 jsdom 逐项 diff。 + +### 规则 12:双签名场景必须同时 Hook XHR 和 fetch + +> 原 #33 + +某些平台的 JSVMP 同时修改 `XMLHttpRequest.prototype.open` 和 `window.fetch`,只 Hook 一个通道会丢失另一半签名。 + +``` +MCP 操作: + inject_hook_preset("xhr", persistent=True) + inject_hook_preset("fetch", persistent=True) + → 两个通道都要 Hook +``` + +### 规则 13:MCP 侧 AST 让插桩在挑战页可用 + +> 原 #32 + +v0.5.0 把 AST 从页面内 Acorn 改为 MCP 侧 esprima,RS 412 挑战页也能跑。 + +`instrumentation(action='install', ...)` 的 `last_mode_used` 长期是 `"regex (fallback)"` 说明目标有太新语法。 + +### 规则 14:CSP 策略可能阻断 AST 模式插桩 + +> 原 #37 + +严格的 CSP 会阻止改写代码执行。v0.6.0 新增 `csp_bypass=True` 参数自动绕过。 + +### 规则 15:`navigate` 的 `collect_response_chain` 默认开启 + +> 原 #39 + +v0.6.0 起默认记录完整响应链,对签名型反爬的 412→200 判断更可靠。 + +--- + +## 三、Hook 与环境伪装踩坑(8 条) + +### 规则 16:环境伪装优先于算法追踪 + +> 原 #18 + +如果 JSVMP 只是一个「签名黑箱」且可以在 jsdom 中加载执行,优先走路径 B(采集→对比→补丁),比追踪字节码执行快 10 倍。 + +### 规则 17:Function.prototype.toString 是 jsdom 环境伪装的第一杀手 + +> 原 #19 + +jsdom 所有 DOM 方法的 toString() 会暴露实际 JS 代码,必须用 WeakSet + 实例级覆写 + 源码模式正则三层防御。 + +### 规则 18:环境对比要分批采集 + +> 原 #20 + +单次 evaluate_js 代码太长会报错,分 4-5 批: +- navigator +- screen + window +- document + performance + toString +- DOM + Canvas + WebGL + Audio + +### 规则 19:jsdom 环境补丁必须在 JSVMP 脚本加载前完成 + +> 原 #21 + +XHR Hook 的安装顺序决定能否截获最终 URL。 + +### 规则 20:服务端静默拒绝是环境检测失败的信号 + +> 原 #22 + +返回 HTTP 200 + 空 body(不报错),说明签名格式正确但环境指纹不匹配。 + +### 规则 21:Firefox 格式 native code 与 Chrome 不同 + +> 原 #35 + +Camoufox 基于 Firefox 内核,`Function.prototype.toString` 对原生函数返回含换行缩进格式。 + +``` +Chrome: "function name() { [native code] }" +Firefox: "function name() {\n [native code]\n}" +``` + +jsdom 的 markNative 必须匹配采集基准浏览器的格式。 + +### 规则 22:cacheOpts 是新版 SDK 初始化的必传项 + +> 原 #34 + +旧版只需 `bdms.paths`,新版必须同时传入 `cacheOpts`。缺少 cacheOpts 会导致业务路径未注册,拦截器不触发。 + +### 规则 23:环境伪装前先确认签名函数入口 + +> 原 #38 + +在开始 6 步法的环境采集之前,先用 `search_code` 确认 JSVMP 的签名入口类型: +- 单通道 XHR +- 双通道 XHR + fetch +- 导出函数 +- cacheOpts 初始化 + +--- + +## 四、协议优先反模式(7 条) + +### 规则 24:先做最小补环境,不要上来就开浏览器 + +> 原 #5 + +协议优先的第一步。能用 Node.js `crypto` 解决的,不要用 `vm` 沙箱;能用 `vm` 沙箱的,不要用 jsdom;能用 jsdom 的,不要用浏览器。 + +### 规则 25:TLS 指纹是终极壁垒 + +> 合并原 #6, #36 + +算法全对但仍失败时,考虑 TLS 指纹: +- Node.js 用 `got-scraping` 模拟 Firefox/Chrome TLS 指纹 +- Python 用 `curl_cffi` + +### 规则 26:签名不一致时逐环节对比 + +> 原 #13 + +排查链路(逐项对比脚本值 vs 浏览器值): +1. 原始输入参数 +2. 参数排序/拼接字符串 +3. 时间戳(精度:秒 vs 毫秒) +4. 随机串(长度、字符集) +5. 密钥/盐值 +6. 中间摘要 +7. 最终密文(编码方式:hex/base64/自定义) + +找到第一个偏差点。 + +### 规则 27:预热请求不是装饰 + +> 原 #2 + +`/api2` 类请求在运行时注入关键变量。跳过预热请求可能导致签名缺少必要的动态密钥。 + +### 规则 28:Node vm 沙箱 ≠ 浏览器 + +> 原 #3 + +有些调试干扰机制只在非浏览器环境触发。vm 沙箱中可能遇到: +- `window` 未定义 +- `document` 未定义 +- `navigator` 未定义 +- 定时器行为不同 + +### 规则 29:降级梯度必须逐级走 + +> 合并原 #43, #44 + +``` +instrumentation(action='install', mode="ast") + → 失败 +instrumentation(action='install', mode="regex") + → 覆盖率不足 +hook_jsvmp_interpreter(mode="transparent") + → 日志太少 +hook_jsvmp_interpreter(mode="proxy") ← 仅行为型可用 + → 破坏签名 +路径 B(jsdom 环境伪装) + → 也失败 +向用户说明情况 +``` + +每级至少尝试一次并记录失败原因。想放弃时先回查 cases/ 和 common-pitfalls.md。 + +### 规则 30:case 文件的价值随实战次数指数级增长 + +> v3.2.0 改写(原"Session 档案与经验库互补") + +第一次分析某站点时写的 case 可能粗糙;第二次分析(升级或变体)时用 case 发现 80% 还成立、20% 变了,就把变化追加到 case 的"变体章节"。三五次之后,这个 case 会覆盖该站点的主要变体谱系,复用价值极高。 + +case 文件的"可验证事实清单"段列出 5-15 条最小可验证事实(如"X-Bogus 长度 28""webdriver === false"),下次同站升级时逐条手动核对找出"哪些变了"就是今天的工作范围。 + +--- + +## 已合并/迁移的旧经验法则索引 + +| 原编号 | 原标题 | 去向 | +|--------|--------|------| +| #1 | Cookie setter 最高性价比 | → 合并到新 #3(Cookie 归因优先) | +| #4 | WASM panic 常常是环境缺失 | → 移到 references/troubleshooting.md | +| #7 | 真假请求链是常见干扰 | → 移到 references/troubleshooting.md | +| #8 | HTTP/2 可能是唯一的"密码" | → 移到 references/troubleshooting.md | +| #11 | JSVMP 路径选择 | → 合并到新 #2 | +| #12 | JSVMP 中 String.fromCharCode 是高频信号 | → 移到 references/jsvmp-analysis.md | +| #14 | Python execjs 复用 context | → 移到 references/workflow-overview.md | +| #15 | Hook 必须持久化 + 防覆盖 | → 已在工具说明中覆盖 | +| #23 | 源码级插桩优先 | → 合并到新 #8 | +| #24 | hot_keys 指纹学习法 | → 合并到新 #8 | +| #25 | Cookie 归因 | → 合并到新 #3 | +| #26 | pre_inject_hooks 定位 | → 合并到新 #4 | +| #28 | 反爬类型识别 | → 合并到新 #1 | +| #29 | 签名型只走源码插桩 | → 合并到新 #2 | +| #30 | transparent 备选 | → 合并到新 #2, #4 | +| #41 | 开 session 多 10 秒省几小时 | → 已合并到硬约束 Checklist CHECK-4 | +| #42 | 进新域第一反应 list_sessions | → 已合并到 Checklist CHECK-3 | +| #43 | 降级梯度逐级走 | → 合并到新 #29 | +| #44 | 过期不等于失效 | → 合并到新 #29 | +| #45 | 断言价值指数递增 | → 合并到新 #30 | +| #46 | Session 与经验库互补 | → 合并到新 #30 | + +--- + +> **SKILL.md 核心层**保留了前 10 条最常用的经验法则(规则 1-10 的精简版) +> **完整 30 条**在本文档 +> **反模式清单**:references/common-pitfalls.md diff --git a/references/jsvmp-analysis.md b/references/jsvmp-analysis.md index 89fcf7e..1977f1b 100644 --- a/references/jsvmp-analysis.md +++ b/references/jsvmp-analysis.md @@ -10,7 +10,7 @@ JSVMP(JavaScript Virtual Machine Protection)是一种高级 JS 代码保护 **核心原则:不反编译字节码,用行为追踪法(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 调度——是RS 5/6、Akamai sensor_data、webmssdk、obfuscator.io 这类"VM 自包含"场景的通用武器。详见 [`jsvmp-source-instrumentation.md`](./jsvmp-source-instrumentation.md)。 +> **v2.5.0 更新**:第四板斧「源码级插桩」由 camoufox-reverse MCP v0.4.0 的 `instrumentation(action='install', ...)` 提供支持,在 HTTP 层改写 VMP 源码,对每个 `obj[key]` 和 `fn(args)` 插入 tap,捕获 VM 内部 switch/case 调度——是RS 5/6、Akamai sensor_data、webmssdk、obfuscator.io 这类"VM 自包含"场景的通用武器。详见 [`jsvmp-source-instrumentation.md`](./jsvmp-source-instrumentation.md)。 --- @@ -52,18 +52,18 @@ function _0xefgh() { ``` MCP 操作(推荐顺序): - - list_scripts → 找到异常大的 JS 文件(100KB+) - - find_dispatch_loops(script_url=<大 JS 的 url>, min_case_count=20) ← v0.4.0 推荐首选 + - scripts(action='list') → 找到异常大的 JS 文件(100KB+) + - search_code(keyword='switch', script_url=<大 JS 的 url>, context_chars=500) ← 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 读取分发体 +兜底手动方式(大文件 search_code 超时时): + - search_code(keyword="switch", script_url=N) → 前后 3 行上下文 + - search_code(keyword="case 0:|case 1:|case 2:", script_url=N) + - scripts(action='get', script_id=N, start_line=..., end_line=...) → 按 char_range 读取分发体 ``` --- @@ -77,7 +77,7 @@ MCP 操作(推荐顺序): │ inject_hook_preset(xhr|fetch │ │ |crypto|cookie|websocket │ │ |debugger_bypass|runtime_probe)│ - │ freeze_prototype │ + │ hook_function(..., mode='intercept', ...) │ │ analyze_cookie_sources │ └────────┬─────────────────────────┘ │ @@ -86,18 +86,19 @@ MCP 操作(推荐顺序): 中间层 ──→ ┌─┘ └─┐ ──→ 追踪执行链路 │ ┌── 第二板斧 插桩解释器 ────────────┐ ┌── 第四板斧 源码级插桩 ──────────┐ - │ 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 │ + │ search_code(keyword='switch', │ │ instrumentation(action='install')│ + │ script_url=..., context_chars) │ │ instrumentation(action='log') │ + │ hook_function(mode='trace', │ │ → summary.hot_keys │ + │ 分发函数/子函数) │ │ → summary.hot_methods │ + │ hook_jsvmp_interpreter │ │ → summary.hot_functions │ + │ (mode='proxy', trackProps=True)│ └────────┬────────────────────────┘ └────────┬────────────────────────┘ │ ↓ ↓ ┌── 第三板斧 日志分析 ──────────────────┐ │ get_trace_data + get_jsvmp_log │ │ + get_runtime_probe_log │ - │ + get_instrumentation_log │ + │ + instrumentation(action='log') │ │ 反向追踪法(从签名值→明文) │ │ 多次请求对比法(找变化因子) │ └───────────────────────────────────────┘ @@ -117,13 +118,13 @@ MCP 操作(推荐顺序): ### 快速路径:v0.4.0 黄金 8 步流程(推荐先试,70%+ RS/Akamai/webmssdk 场景直接搞定) ``` -Step 1 — find_dispatch_loops 确认 VMP + 记下 fn_name / char_range +Step 1 — search_code(keyword='switch', script_url=..., context_chars=500) 确认 VMP + 记下 fn_name / char_range Step 2 — hook_jsvmp_interpreter(script_url=) 装多路径探针 Step 3 — inject_hook_preset("cookie", persistent=True) + inject_hook_preset("xhr", persistent=True) -Step 4 — instrument_jsvmp_source(url_pattern="**/", mode="ast", tag="vmp1") ← 核心新步 -Step 5 — reload_with_hooks() 让所有探针先于 VMP 生效 + 清日志 +Step 4 — instrumentation(action='install', url_pattern="**/", mode="ast", tag="vmp1") ← 核心新步 +Step 5 — instrumentation(action='reload') 让所有探针先于 VMP 生效 + 清日志 Step 6 — 触发业务操作 -Step 7 — get_instrumentation_log(tag_filter="vmp1", limit=300) 看 hot_keys / hot_methods / hot_functions +Step 7 — instrumentation(action='log', tag_filter="vmp1", limit=300) 看 hot_keys / hot_methods / hot_functions Step 8 — analyze_cookie_sources() 归因最终 cookie 来源 ``` @@ -165,7 +166,7 @@ MCP 操作: hook_code="console.log('[COOKIE_SET]', arguments[0], new Error().stack)", position="before", non_overridable=True ) → 拦截 Cookie 写入(防覆盖) - - freeze_prototype(className="XMLHttpRequest", methodName="send") + - hook_function(className="XMLHttpRequest", methodName="send", mode='intercept') → 冻结 XHR.send 防止 VM 覆盖 Hook - reload() → 刷新页面触发 Hook - get_console_logs → 查看捕获的签名值和调用栈 @@ -226,9 +227,9 @@ MCP 操作: ``` MCP 操作: - search_code(keyword="while.*true") → 找到解释器主循环 - - search_code_in_script(script_id=N, keyword="case 0:|case 1:|case 2:") + - search_code(keyword="case 0:|case 1:|case 2:", script_url=N) → 在指定大文件中搜索 opcode 分发表(前后 3 行上下文,更精确) - - get_script_source → 读取解释器函数体 + - scripts(action='get') → 读取解释器函数体 判断标准: - 函数体超过 500 行 @@ -244,8 +245,9 @@ MCP 操作: ``` MCP 操作: - - trace_function( + - hook_function( function_path="解释器分发函数名", + mode='trace', log_args=true, log_return=true, max_captures=500, persistent=True → 持久化追踪,跨导航不丢失 ) @@ -264,8 +266,8 @@ MCP 操作: 从第一轮日志中识别被频繁调用的子函数(通常 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) + - hook_function(function_path="子函数A", mode='trace', log_args=true, log_return=true, log_stack=true) + - hook_function(function_path="子函数B", mode='trace', log_args=true, log_return=true, log_stack=true) - 再次触发请求 - get_trace_data → 分析子函数的输入输出 ``` @@ -296,7 +298,7 @@ MCP 操作: ``` MCP 操作: - - trace_property_access(target_expression="疑似签名容器对象") + - hook_jsvmp_interpreter(mode='proxy', trackProps=True, target_expression="疑似签名容器对象") → Proxy 级别属性访问追踪,自动记录读写操作 - get_property_access_log → 获取属性访问记录 - compare_env → 收集完整浏览器环境信息 @@ -445,7 +447,7 @@ MCP 操作: · 在 add_init_script 中保存原始引用,在 VM 重写之前 - 或 Hook 网络层: - · start_network_capture → 不依赖 JS 层 Hook,直接在协议层捕获 + · network_capture(action='start') → 不依赖 JS 层 Hook,直接在协议层捕获 · list_network_requests → get_network_request → 获取完整请求详情 ``` @@ -470,7 +472,7 @@ MCP 操作: ``` - 不要试图理解嵌套关系 - 依然从 I/O 两端入手,Hook 最终的出口和最初的入口 -- 增加 trace_function 的 max_captures 到 1000+ +- 增加 hook_function(mode='trace') 的 max_captures 到 1000+ - 用时间戳过滤法缩小日志范围 ``` @@ -533,9 +535,9 @@ navigate(url) 不加任何 hook 观察 redirect_chain 签名型 JSVMP 推荐走的路: ``` -1. instrument_jsvmp_source(mode="ast", tag="...") (第四板斧) -2. reload_with_hooks() 让插桩后的 VMP 跑完挑战 -3. get_instrumentation_log(tag_filter, type_filter="tap_get") +1. instrumentation(action='install', mode="ast", tag="...") (第四板斧) +2. instrumentation(action='reload') 让插桩后的 VMP 跑完挑战 +3. instrumentation(action='log', tag_filter, type_filter="tap_get") 4. 基于 hot_keys 补最小环境,在 jsdom 或纯 Python 侧独立跑 (此时 jsdom 补环境的粒度比路径 B 小得多,只补 hot_keys 显示的属性) ``` @@ -600,18 +602,18 @@ JSVMP 签名还原决策树: 在 JSVMP 分析过程中,确保以下每项都已完成: **第一板斧 — Hook 出入口**: -- [ ] `find_dispatch_loops` 确认是 VMP(case_count > 50) +- [ ] `search_code(keyword='switch', script_url=..., context_chars=500)` 确认是 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_function(..., mode='intercept', ...)` 防止 VMP 覆盖 hook **第二板斧 — 插桩解释器**: - [ ] `hook_jsvmp_interpreter(script_url=)` 多路径探针(apply/call/bind + Reflect.*/Proxy) -- [ ] 分层 `trace_function`(粗 → 中 → 细,用 `max_captures` 限制日志量) -- [ ] `trace_property_access(["navigator.*", "screen.*", ...])` 监控签名容器 +- [ ] 分层 `hook_function(mode='trace')`(粗 → 中 → 细,用 `max_captures` 限制日志量) +- [ ] `hook_jsvmp_interpreter(mode='proxy', trackProps=True, targets=["navigator.*", "screen.*", ...])` 监控签名容器 **第三板斧 — 日志分析**: - [ ] `get_jsvmp_log` + `get_trace_data` + `get_runtime_probe_log`(如果装了 runtime_probe)+ `get_console_logs` @@ -619,13 +621,13 @@ JSVMP 签名还原决策树: - [ ] 多次请求对比,确认变化因子和固定因子 **第四板斧 — 源码级插桩 [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` +- [ ] `instrumentation(action='install', url_pattern=..., mode="ast"|"regex", tag=...)` 装好 +- [ ] `instrumentation(action='reload')` 让插桩先于 VMP 生效 +- [ ] 读 `instrumentation(action='log')` 三个 summary:`hot_keys` / `hot_methods` / `hot_functions` - [ ] `hot_keys` 给出 VMP 读取的环境指纹集(top 30) - [ ] `hot_methods` 给出 VMP 调用的方法(ObjectType.methodName 格式) - [ ] `hot_functions` 给出 VMP 调用的函数名 -- [ ] `stop_instrumentation` 完工清理 +- [ ] `instrumentation(action='stop')` 完工清理 **环境/时序**: - [ ] 若目标是首屏挑战页,用 `navigate(pre_inject_hooks=[...])` 或 `via_blank=True` diff --git a/references/mcp-cookbook.md b/references/mcp-cookbook.md index 40c9451..cc826b9 100644 --- a/references/mcp-cookbook.md +++ b/references/mcp-cookbook.md @@ -17,42 +17,42 @@ | **截图** | `take_screenshot` / `take_snapshot` | 截图 / 无障碍树 | | **页面信息** | `get_page_info` / `get_page_html` | 页面状态和 HTML | | **源码搜索** | `search_code` | 在所有 JS 中搜索(结构化结果,默认 max_results=50) | -| **精确搜索** | `search_code_in_script` | 在指定脚本中搜索(前后 3 行上下文,适合大文件) | -| **源码获取** | `get_script_source` | 获取指定脚本的代码 | -| **源码保存** | `save_script` | 将脚本保存到本地文件 | -| **脚本列表** | `list_scripts` | 列出页面加载的所有脚本 | +| **精确搜索** | `search_code(..., script_url=...)` | 在指定脚本中搜索(前后 3 行上下文,适合大文件) | +| **源码获取** | `scripts(action='get', ...)` | 获取指定脚本的代码 | +| **源码保存** | `scripts(action='save', ...)` | 将脚本保存到本地文件 | +| **脚本列表** | `scripts(action='list')` | 列出页面加载的所有脚本 | | **执行 JS** | `evaluate_js` / `evaluate_js_handle` | 在页面执行任意 JS | | **加载前注入** | `add_init_script` | 页面加载前注入脚本(支持 `persistent` 跨导航重注入) | | **伪断点** | `set_breakpoint_via_hook` | 通过 Hook 设置伪断点(支持 `persistent` 持久化) | | **断点数据** | `get_breakpoint_data` | 获取伪断点捕获的数据 | -| **函数追踪** | `trace_function` / `get_trace_data` | 追踪函数调用(支持 `persistent` 跨导航持久化) | +| **函数追踪** | `hook_function(..., mode='trace', ...)` / `get_trace_data` | 追踪函数调用(支持 `persistent` 跨导航持久化) | | **自定义 Hook** | `hook_function` | 注入自定义 Hook(before/after/replace,支持 `non_overridable` 防覆盖) | | **预设 Hook** | `inject_hook_preset` | 一键 Hook(xhr/fetch/crypto/websocket/debugger_bypass,默认持久化) | | **移除 Hook** | `remove_hooks` | 移除所有 Hook(支持 `keep_persistent` 保留持久化 Hook) | -| **冻结原型** | `freeze_prototype` | 冻结任意原型方法,防止页面 JS 覆盖 Hook | -| **属性追踪** | `trace_property_access` / `get_property_access_log` | Proxy 级别属性访问追踪 | +| **冻结原型** | `hook_function(..., mode='intercept', ...)` | 冻结任意原型方法,防止页面 JS 覆盖 Hook | +| **属性追踪** | `hook_jsvmp_interpreter(mode='proxy', trackProps=True)` / `get_property_access_log` | Proxy 级别属性访问追踪 | | **JSVMP 插桩** | `hook_jsvmp_interpreter` | 一键插桩 JSVMP(Function.prototype.apply + 30+ 敏感属性) | | **JSVMP 日志** | `get_jsvmp_log` | 获取 JSVMP 日志(API 调用统计 + 属性读取摘要) | | **JSVMP 字符串** | `dump_jsvmp_strings` | 提取字符串数组,识别 API 名称,检测混淆模式 | | **环境采集** | `compare_env` | 全面收集浏览器环境(navigator/screen/canvas/WebGL/Audio/timing) | -| **网络捕获** | `start_network_capture` / `stop_network_capture` | 启停网络捕获(支持 `capture_body` 捕获响应体) | +| **网络捕获** | `network_capture(action='start', ...)` / `network_capture(action='stop')` | 启停网络捕获(支持 `capture_body` 捕获响应体) | | **网络请求** | `list_network_requests` | 列出捕获的请求 | | **请求详情** | `get_network_request` | 获取请求完整信息(含响应体) | | **请求来源** | `get_request_initiator` | 获取请求的 JS 调用栈(改进 URL 匹配 + 诊断信息) | | **请求拦截** | `intercept_request` / `stop_intercept` | 拦截/修改/Mock 请求 | -| **Cookie** | `get_cookies` / `set_cookies` / `delete_cookies` | Cookie 管理 | +| **Cookie** | `cookies(action='get', ...)` / `cookies(action='set', ...)` / `delete_cookies` | Cookie 管理 | | **Storage** | `get_storage` / `set_storage` | localStorage / sessionStorage | | **状态管理** | `export_state` / `import_state` | 保存/恢复浏览器状态 | | **控制台** | `get_console_logs` | 读取控制台输出 | | **指纹** | `get_fingerprint_info` | 检查浏览器指纹 | | **反检测** | `check_detection` | 测试是否被反爬识别 | | **反调试** | `bypass_debugger_trap` | 绕过 debugger 陷阱 | -| **分发循环扫描** | `find_dispatch_loops` | **[v0.4.0]** 定位字节码分发函数(while+switch,case 数过滤) | -| **源码级插桩** | `instrument_jsvmp_source` | **[v0.4.0]** HTTP 层改写 VMP,每个 obj[key]/fn(args) 插入 tap(通用 VMP 利器) | -| **插桩日志** | `get_instrumentation_log` | **[v0.4.0]** 拉取源码插桩日志,带 hot_keys/hot_methods/hot_functions 摘要 | -| **插桩状态** | `get_instrumentation_status` / `stop_instrumentation` | **[v0.4.0]** 查看/停止源码插桩 | +| **分发循环扫描** | `search_code(keyword='switch', script_url=..., context_chars=500)` | **[v0.4.0]** 定位字节码分发函数(while+switch,case 数过滤) | +| **源码级插桩** | `instrumentation(action='install', ...)` | **[v0.4.0]** HTTP 层改写 VMP,每个 obj[key]/fn(args) 插入 tap(通用 VMP 利器) | +| **插桩日志** | `instrumentation(action='log', ...)` | **[v0.4.0]** 拉取源码插桩日志,带 hot_keys/hot_methods/hot_functions 摘要 | +| **插桩状态** | `check_environment()` / `instrumentation(action='stop', ...)` | **[v0.4.0]** 查看/停止源码插桩 | | **Cookie 归因** | `analyze_cookie_sources` | **[v0.4.0]** 融合 HTTP Set-Cookie + JS document.cookie + cookie jar | -| **hook 重载** | `reload_with_hooks` | **[v0.4.0]** 重载使 persistent hook 先于页面 JS 执行(+ 清日志) | +| **hook 重载** | `instrumentation(action='reload')` | **[v0.4.0]** 重载使 persistent hook 先于页面 JS 执行(+ 清日志) | | **运行时探针日志** | `get_runtime_probe_log` | **[v0.4.0]** 获取 runtime_probe 预设的广谱事件日志 | | **预设 Hook(扩展)** | `inject_hook_preset("cookie")` / `inject_hook_preset("runtime_probe")` | **[v0.4.0]** 新增两个预设 | | **首屏注入** | `navigate(pre_inject_hooks=[...])` | **[v0.4.0]** 经 about:blank 装 hook 再 goto,解决首屏挑战页 | @@ -74,7 +74,7 @@ [camoufox-reverse] take_screenshot() → 确认页面正常 步骤 4:开始网络捕获 - [camoufox-reverse] start_network_capture() + [camoufox-reverse] network_capture(action='start') 步骤 5:触发数据请求(如翻页) [camoufox-reverse] click(selector=".next-page") @@ -97,7 +97,7 @@ [camoufox-reverse] search_code(keyword="参数名") 步骤 10:读取相关源码 - [camoufox-reverse] get_script_source(script_url="包含加密逻辑的脚本URL") + [camoufox-reverse] scripts(action='get', script_url="包含加密逻辑的脚本URL") 步骤 11:设置伪断点验证 [camoufox-reverse] set_breakpoint_via_hook(target_function="加密函数路径") @@ -129,13 +129,13 @@ 步骤 4:根据调用栈定位生成函数 [camoufox-reverse] search_code(keyword="目标函数名") - [camoufox-reverse] get_script_source(script_url="...") + [camoufox-reverse] scripts(action='get', script_url="...") 步骤 5:分析生成逻辑 [camoufox-reverse] set_breakpoint_via_hook(target_function="cookie生成函数") 刷新页面 → [camoufox-reverse] get_breakpoint_data → 查看入参和返回值 - [camoufox-reverse] trace_function(function_path="加密函数", log_args=true, log_return=true, log_stack=true) + [camoufox-reverse] hook_function(function_path="加密函数", mode='trace', log_args=true, log_return=true, log_stack=true) [camoufox-reverse] get_trace_data → 查看中间值 ``` @@ -145,11 +145,11 @@ ``` 步骤 1:列出所有脚本 - [camoufox-reverse] list_scripts + [camoufox-reverse] scripts(action='list') → 找到可疑的混淆脚本(通常文件名无意义或体积很大) 步骤 2:保存混淆脚本到本地 - [camoufox-reverse] save_script(script_url="混淆脚本URL", save_path="./project/obfuscated.js") + [camoufox-reverse] scripts(action='save', script_url="混淆脚本URL", save_path="./project/obfuscated.js") 步骤 3:搜索关键特征 [camoufox-reverse] search_code(keyword="_0x") @@ -165,7 +165,7 @@ 步骤 5:定位关键函数 [camoufox-reverse] search_code(keyword="encrypt|sign|md5|aes") → 找到加密相关代码的大致位置 - [camoufox-reverse] get_script_source → 获取上下文 + [camoufox-reverse] scripts(action='get') → 获取上下文 ``` ### 场景 4:WASM 逆向 @@ -220,7 +220,7 @@ ``` 步骤 1:启动浏览器 + 开启响应体捕获 [camoufox-reverse] launch_browser(headless=False) - [camoufox-reverse] start_network_capture(capture_body=True) + [camoufox-reverse] network_capture(action='start', capture_body=True) 步骤 2:第一次导航定位 VMP 脚本 [camoufox-reverse] navigate(url="https://target.com/") @@ -228,14 +228,13 @@ → 找最大的 JS(100KB+,通常是 sdenv-*.js / FuckCookie_*.js / webmssdk.es5.js) 步骤 3:确认是 VMP - [camoufox-reverse] find_dispatch_loops( - script_url="https://target.com/xxx/sdenv-xxx.js", + [camoufox-reverse] search_code(keyword='switch', script_url="https://target.com/xxx/sdenv-xxx.js", context_chars=500) min_case_count=20 ) → case_count > 50 基本确认是 VMP 步骤 4:装源码级插桩(核心) - [camoufox-reverse] instrument_jsvmp_source( + [camoufox-reverse] instrumentation(action='install', url_pattern="**/sdenv-*.js", mode="ast", # 能联网用 ast(99% 覆盖);离线用 regex(80%) tag="vmp1" @@ -248,26 +247,26 @@ [camoufox-reverse] bypass_debugger_trap() 步骤 6:重载让插桩先于 VMP 生效 - [camoufox-reverse] reload_with_hooks() + [camoufox-reverse] instrumentation(action='reload') 步骤 7:触发业务操作(翻页 / 登录 / 搜索) 步骤 8:读 hot_keys / hot_methods / hot_functions(30 秒读完 VMP 指纹) - [camoufox-reverse] get_instrumentation_log( + [camoufox-reverse] instrumentation(action='log', tag_filter="vmp1", type_filter="tap_get", # 所有 obj[key] 读取 limit=200 ) → summary.hot_keys: VMP 读了哪些属性(top 30,按频次) - [camoufox-reverse] get_instrumentation_log( + [camoufox-reverse] instrumentation(action='log', tag_filter="vmp1", type_filter="tap_method", # 所有 obj.method(args) 调用 limit=200 ) → summary.hot_methods: VMP 调了哪些方法(Array.prototype.join / CryptoJS.MD5 ...) - [camoufox-reverse] get_instrumentation_log( + [camoufox-reverse] instrumentation(action='log', tag_filter="vmp1", type_filter="tap_call", # 所有 fn(args) 直接调用 limit=200 @@ -280,7 +279,7 @@ + http_responses[].url + js_writes[].stack 步骤 10:完工清理 - [camoufox-reverse] stop_instrumentation(url_pattern="**/sdenv-*.js") + [camoufox-reverse] instrumentation(action='stop', url_pattern="**/sdenv-*.js") [camoufox-reverse] remove_hooks() ``` @@ -301,7 +300,7 @@ ``` 步骤 1:启动环境 + 开启响应体捕获 + cookie hook [camoufox-reverse] launch_browser(headless=False) - [camoufox-reverse] start_network_capture(capture_body=True) + [camoufox-reverse] network_capture(action='start', capture_body=True) [camoufox-reverse] inject_hook_preset(preset="cookie", persistent=True) # 原型链级 步骤 2:导航到目标(首屏有挑战用 pre_inject_hooks) @@ -348,11 +347,12 @@ ## 高级技巧 -### 使用 trace_function 自动记录函数调用 +### 使用 hook_function(mode='trace') 自动记录函数调用 ``` -[camoufox-reverse] trace_function( +[camoufox-reverse] hook_function( function_path="window.encrypt", + mode='trace', log_args=true, log_return=true, log_stack=true, @@ -387,10 +387,10 @@ ``` → 在 XHR.open 调用前输出请求方法和 URL,即使页面 JS 尝试覆盖也不会丢失 -### 使用 freeze_prototype 防止 Hook 被覆盖 +### 使用 hook_function(mode='intercept') 防止 Hook 被覆盖 ``` -[camoufox-reverse] freeze_prototype(className="XMLHttpRequest", methodName="send") +[camoufox-reverse] hook_function(className="XMLHttpRequest", methodName="send", mode='intercept') ``` → 冻结原型方法(configurable:false + writable:false),防止 JSVMP 等覆盖你的 Hook @@ -431,16 +431,16 @@ await navigate(url, pre_inject_hooks=["jsvmp_probe_transparent"]) ### 使用属性访问追踪 ``` -[camoufox-reverse] trace_property_access(target_expression="navigator") +[camoufox-reverse] hook_jsvmp_interpreter(mode='proxy', trackProps=True, target_expression="navigator") 触发操作... [camoufox-reverse] get_property_access_log → 查看 navigator 的哪些属性被读取 ``` → 精确发现 JSVMP 或签名逻辑读取了哪些环境属性 -### 使用 search_code_in_script 精确搜索大文件 +### 使用 search_code(..., script_url=...) 精确搜索大文件 ``` -[camoufox-reverse] search_code_in_script(script_id=3, keyword="sign|token|encrypt") +[camoufox-reverse] search_code(keyword="sign|token|encrypt", script_url=3) ``` → 在指定脚本中搜索,返回前后 3 行上下文,适合 200KB+ 的 JSVMP 文件 @@ -484,10 +484,10 @@ await navigate(url, pre_inject_hooks=["jsvmp_probe_transparent"]) ``` # 1. 先确认是不是 VMP(case_count > 50 基本是) -[camoufox-reverse] find_dispatch_loops(script_url="https://target.com/sdenv-xxx.js", min_case_count=20) +[camoufox-reverse] search_code(keyword='switch', script_url="https://target.com/sdenv-xxx.js", context_chars=500) # 2. 装插桩(AST 模式优先,需 CDN;regex 模式兜底) -[camoufox-reverse] instrument_jsvmp_source( +[camoufox-reverse] instrumentation(action='install', url_pattern="**/sdenv-*.js", # glob 模式匹配多 hash 版本 mode="ast", # 或 "regex" tag="vmp1", # 区分多 VMP 的标签 @@ -498,21 +498,21 @@ await navigate(url, pre_inject_hooks=["jsvmp_probe_transparent"]) ) # 3. 装完后必须重载让插桩生效 -[camoufox-reverse] reload_with_hooks() +[camoufox-reverse] instrumentation(action='reload') # 4. 触发操作后读日志 -[camoufox-reverse] get_instrumentation_log(tag_filter="vmp1", type_filter="tap_get", limit=200) +[camoufox-reverse] instrumentation(action='log', tag_filter="vmp1", type_filter="tap_get", limit=200) → summary.hot_keys 是 VMP 读取的环境属性 top 30(指纹学习金矿) -[camoufox-reverse] get_instrumentation_log(tag_filter="vmp1", type_filter="tap_method", limit=200) +[camoufox-reverse] instrumentation(action='log', tag_filter="vmp1", type_filter="tap_method", limit=200) → summary.hot_methods 是 VMP 调用的方法 top 30(ObjectType.methodName 格式) # 5. 查看插桩状态 -[camoufox-reverse] get_instrumentation_status() +[camoufox-reverse] check_environment() # 6. 完工移除 -[camoufox-reverse] stop_instrumentation(url_pattern="**/sdenv-*.js") -[camoufox-reverse] stop_instrumentation() # 不传参数 = 全部停止 +[camoufox-reverse] instrumentation(action='stop', url_pattern="**/sdenv-*.js") +[camoufox-reverse] instrumentation(action='stop') # 不传参数 = 全部停止 ``` **与 hook_jsvmp_interpreter 的分工**: @@ -520,7 +520,7 @@ await navigate(url, pre_inject_hooks=["jsvmp_probe_transparent"]) | 工具 | 能看到 | 看不到 | |------|--------|--------| | `hook_jsvmp_interpreter` | VMP 通过 `Function.prototype.apply/call/bind` 或 `Reflect.*` 调子函数,读全局对象属性(Proxy) | VMP 在 switch/case 内部直接 `obj[key]` / `fn(args)` 的调用 | -| `instrument_jsvmp_source` | 每一次 member access + 每一次函数调用,无论是否通过可 hook API | 不跨脚本(只改写指定 url_pattern 的那个文件) | +| `instrumentation(action='install', ...)` | 每一次 member access + 每一次函数调用,无论是否通过可 hook API | 不跨脚本(只改写指定 url_pattern 的那个文件) | | **建议**:两者同时开,互相校验 | | | #### mode="ast" 的 v0.5.0 变化(v2.6.0 吸收) @@ -528,15 +528,14 @@ await navigate(url, pre_inject_hooks=["jsvmp_probe_transparent"]) ```python # v2.5.0 / MCP v0.4.x: mode="ast" 会在页面里注入 acorn CDN script tag # v2.6.0 / MCP v0.5.0: mode="ast" 在 MCP 进程里用 esprima-python 解析,零页面依赖 -await instrument_jsvmp_source( - "**/sdenv-*.js", +await instrumentation(action='install', url_pattern="**/sdenv-*.js", mode="ast", # 默认,走 MCP 侧 esprima tag="sdenv", fallback_on_error=True, # 新参数:esprima 挂就回落 regex ) # 查看实际走了哪条路径 -status = await get_instrumentation_status() +status = await check_environment() # status["active_patterns"][0]["last_mode_used"] == "ast" (正常) # or "regex (fallback)" (esprima 对该 VMP 的语法支持不全) # or "ast_page" (你显式用了 deprecated 模式) @@ -546,14 +545,14 @@ status = await get_instrumentation_status() ```python # 仅用于 A/B 对比 v0.4.x 的旧页面内 Acorn 实现,deprecated # 生产不要用,挑战页会因 CDN 被拦静默失败 -await instrument_jsvmp_source("**/vmp.js", mode="ast_page") +await instrumentation(action='install', url_pattern="**/vmp.js", mode="ast_page") ``` ### 使用 Cookie 归因分析(v2.5.0 新增) ``` # 1. 前置条件:网络抓包 + cookie hook 同时开 -[camoufox-reverse] start_network_capture(capture_body=True) +[camoufox-reverse] network_capture(action='start', capture_body=True) [camoufox-reverse] inject_hook_preset(preset="cookie", persistent=True) # 2. 触发场景 @@ -616,15 +615,15 @@ await instrument_jsvmp_source("**/vmp.js", mode="ast_page") | 页面导航 | `navigate` `reload` `go_back` | 导航控制 | | 页面交互 | `click` `type_text` `wait_for` | UI 自动化 | | 页面状态 | `get_page_info` `take_screenshot` `take_snapshot` `get_page_html` | 页面信息 | -| 源码分析 | `list_scripts` `get_script_source` `search_code` `search_code_in_script` `save_script` | JS 逆向核心 | +| 源码分析 | `scripts(action='list')` `scripts(action='get', ...)` `search_code` `search_code(..., script_url=...)` `scripts(action='save', ...)` | JS 逆向核心 | | JS 执行 | `evaluate_js` `evaluate_js_handle` `add_init_script` | 代码执行(支持持久化) | | 调试 | `set_breakpoint_via_hook` `get_breakpoint_data` `get_console_logs` | 伪断点调试(支持持久化) | -| Hook | `trace_function` `get_trace_data` `hook_function` `inject_hook_preset` `remove_hooks` `freeze_prototype` | 函数 Hook(支持持久化 + 防覆盖) | -| 属性追踪 | `trace_property_access` `get_property_access_log` | Proxy 级别属性访问监控 | +| Hook | `hook_function(..., mode='trace', ...)` `get_trace_data` `hook_function` `inject_hook_preset` `remove_hooks` `hook_function(..., mode='intercept', ...)` | 函数 Hook(支持持久化 + 防覆盖) | +| 属性追踪 | `hook_jsvmp_interpreter(mode='proxy', trackProps=True)` `get_property_access_log` | Proxy 级别属性访问监控 | | JSVMP 专项 | `hook_jsvmp_interpreter` `get_jsvmp_log` `dump_jsvmp_strings` `compare_env` | JSVMP 虚拟机保护分析 | -| 网络 | `start_network_capture` `stop_network_capture` `list_network_requests` `get_network_request` `get_request_initiator` `intercept_request` `stop_intercept` | 网络分析(支持响应体捕获) | -| 存储 | `get_cookies` `set_cookies` `delete_cookies` `get_storage` `set_storage` `export_state` `import_state` | 状态管理 | +| 网络 | `network_capture(action='start', ...)` `network_capture(action='stop')` `list_network_requests` `get_network_request` `get_request_initiator` `intercept_request` `stop_intercept` | 网络分析(支持响应体捕获) | +| 存储 | `cookies(action='get', ...)` `cookies(action='set', ...)` `delete_cookies` `get_storage` `set_storage` `export_state` `import_state` | 状态管理 | | 反检测 | `get_fingerprint_info` `check_detection` `bypass_debugger_trap` | 指纹与反检测 | -| 源码级插桩 | `instrument_jsvmp_source` `get_instrumentation_log` `get_instrumentation_status` `stop_instrumentation` `find_dispatch_loops` | **[v0.4.0]** 通用 JSVMP 逆向利器 | +| 源码级插桩 | `instrumentation(action='install', ...)` `instrumentation(action='log', ...)` `check_environment()` `instrumentation(action='stop', ...)` `search_code(keyword='switch', script_url=..., context_chars=500)` | **[v0.4.0]** 通用 JSVMP 逆向利器 | | Cookie 归因 | `analyze_cookie_sources` | **[v0.4.0]** HTTP Set-Cookie + JS 写入融合分析 | -| 导航增强 | `navigate(pre_inject_hooks)` `reload_with_hooks` `get_runtime_probe_log` | **[v0.4.0]** 首屏挑战/hook 重注入/运行时探针 | +| 导航增强 | `navigate(pre_inject_hooks)` `instrumentation(action='reload')` `get_runtime_probe_log` | **[v0.4.0]** 首屏挑战/hook 重注入/运行时探针 | diff --git a/references/mcp-tool-reference.md b/references/mcp-tool-reference.md new file mode 100644 index 0000000..e3ac6bb --- /dev/null +++ b/references/mcp-tool-reference.md @@ -0,0 +1,287 @@ +# MCP 工具分类索引 + v0.9.0 迁移指南 + +> **说明**:SKILL.md 核心层有工具分类索引,这里是详细版 + MCP v0.9.0 的迁移映射 +> +> **版本**:v3.1.0(MCP v0.9.0 工具名迁移) +> +> **当前 MCP 版本**:camoufox-reverse MCP v0.9.0(~50 个工具,从 v0.8.x 的 ~80 个合并而来) + +--- + +## 一、工具分类表 + +### Browser — 浏览器与页面控制 + +| 工具名 | 一行描述 | +|--------|---------| +| `launch_browser` | 启动 Camoufox 反检测浏览器(C++ 引擎级指纹伪装) | +| `close_browser` | 关闭浏览器释放资源 | +| `navigate` | 导航到 URL,支持 pre_inject_hooks / wait_until / collect_response_chain | +| `reload` | 刷新当前页面 | +| `go_back` | 浏览器后退 | + +### Page — 页面交互与信息 + +| 工具名 | 一行描述 | +|--------|---------| +| `click` | 点击页面元素 | +| `type_text` | 输入文本(带真实按键延迟) | +| `wait_for` | 等待元素出现或网络请求匹配 | +| `get_page_info` | 获取当前页面 URL、标题、视口尺寸 | +| `take_screenshot` | 截取页面截图 | +| `take_snapshot` | 获取页面无障碍树(token 高效) | +| `evaluate_js` | 在页面上下文执行任意 JS(最核心工具) | +| `evaluate_js_handle` | 执行 JS 并检查复杂对象属性 | +| `get_page_html` | 获取页面 HTML 或指定元素的 outerHTML | + +### Network — 网络分析 + +| 工具名(v0.9.0) | 一行描述 | +|-------------------|---------| +| `network_capture(action='start'\|'stop')` | 启停网络流量捕获,支持 capture_body=True | +| `list_network_requests` | 列出捕获的请求(支持 URL/method/type/status 过滤) | +| `get_network_request` | 获取请求完整详情(含响应体) | +| `get_request_initiator` | 获取发起请求的 JS 调用栈(黄金路径) | +| `intercept_request` | 拦截请求(log/block/modify/mock) | +| `stop_intercept` | 停止拦截 | +| `search_response_body` | 在所有已捕获响应体中全文搜索 | +| `get_response_body_page` | 分页读取大响应体 | +| `search_json_path` | 按 JSON 路径提取响应数据 | + +### Scripts — 源码分析 + +| 工具名(v0.9.0) | 一行描述 | +|-------------------|---------| +| `scripts(action='list')` | 列出页面加载的所有 JS 脚本 | +| `scripts(action='get', url=...)` | 获取完整 JS 源码 | +| `scripts(action='save', url=..., save_path=...)` | 保存 JS 文件到本地 | +| `search_code` | 在所有已加载 JS 中搜索关键词(全局) | +| `search_code(keyword, script_url=url)` | 在指定脚本中搜索(大文件精确定位) | + +### Hook — 调试与 Hook + +| 工具名(v0.9.0) | 一行描述 | +|-------------------|---------| +| `add_init_script` | 注入页面加载前执行的脚本(支持 persistent) | +| `set_breakpoint_via_hook` | 通过 JS Hook 设置伪断点 | +| `get_breakpoint_data` | 获取伪断点捕获的数据 | +| `get_console_logs` | 获取页面控制台输出 | +| `hook_function(path, mode='trace', ...)` | 追踪函数调用(不暂停执行) | +| `get_trace_data` | 获取追踪数据 | +| `hook_function(path, hook_code=..., position=...)` | 注入自定义 Hook(before/after/replace) | +| `hook_function(path, mode='intercept', non_overridable=True)` | 冻结原型方法防覆盖 | +| `inject_hook_preset` | 一键预设 Hook(xhr/fetch/crypto/websocket/debugger_bypass/cookie/runtime_probe) | +| `get_runtime_probe_log` | 拉取 runtime_probe 日志 | +| `remove_hooks` | 移除所有 Hook | +| `bypass_debugger_trap` | 一键绕过反调试陷阱 | + +### JSVMP — 虚拟机专项分析 + +| 工具名(v0.9.0) | 一行描述 | +|-------------------|---------| +| `hook_jsvmp_interpreter` | 多路径通用探针(proxy/transparent 模式) | +| `hook_jsvmp_interpreter(mode='proxy', trackProps=True)` | Proxy 级别属性访问追踪 | +| `get_jsvmp_log` | 获取 JSVMP 探针日志 | +| `dump_jsvmp_strings` | 提取 JSVMP 脚本中的字符串 | +| `compare_env` | 采集浏览器环境指纹基准 | +| `instrumentation(action='install', url_pattern=..., mode=..., tag=...)` | 源码级插桩(HTTP 层改写 VMP) | +| `instrumentation(action='log', tag=..., type_filter=...)` | 获取源码插桩日志(hot_keys/hot_methods/hot_functions) | +| `instrumentation(action='status')` | 查看当前激活的源码插桩状态 | +| `instrumentation(action='stop', url_pattern=...)` | 停止源码级插桩 | +| `instrumentation(action='reload')` | 重载页面让 hooks 先于 VMP 生效 | +| `analyze_cookie_sources` | Cookie 归因分析(HTTP vs JS 来源) | + +### Session — 域级 Session 档案 + +| 工具名(v0.9.0) | 一行描述 | +|-------------------|---------| +| `start_reverse_session` | 启动新的分析 run | +| `stop_reverse_session` | 结束当前 run | +| `get_session_snapshot` | 获取活跃 session 快照 | +| `list_sessions` | 列出所有域 session 档案 | +| `attach_domain_readonly` | 只读附加已有域档案 | +| `export_session` | 导出域档案为 zip | +| `import_session` | 导入域档案 zip | + +### Assertion — 断言系统 + +| 工具名(v0.9.0) | 一行描述 | +|-------------------|---------| +| `add_assertion` | 注册域级断言 | +| `verify_assertion(assertion_id=...)` | 验证单条断言 | +| `verify_assertion(assertion_id='*')` | 批量验证所有断言 | +| `list_assertions` | 列出域上的所有断言 | +| `remove_assertion` | 软删除断言 | + +### Environment — 存储与指纹 + +| 工具名(v0.9.0) | 一行描述 | +|-------------------|---------| +| `cookies(action='get', ...)` | 获取 Cookie | +| `cookies(action='set', ...)` | 设置 Cookie | +| `delete_cookies` | 删除 Cookie | +| `get_storage` | 获取 localStorage/sessionStorage | +| `set_storage` | 设置 storage | +| `export_state` | 导出浏览器状态(Cookie + Storage) | +| `import_state` | 导入浏览器状态 | +| `get_fingerprint_info` | 检查当前浏览器指纹 | +| `check_detection` | 在反检测站点测试是否被识别 | +| `get_session_info` | 查看当前会话状态全貌 | + +--- + +## 二、v0.8.x → v0.9.0 完整迁移表 + +> MCP v0.9.0 将 ~80 个工具合并为 ~50 个,核心变化是将同类操作合并为带 `action` 参数的统一接口。 + +### 19 个工具合并为 7 个统一接口 + +| v0.8.x 旧名 | v0.9.0 新名 | 说明 | +|-------------|-------------|------| +| `start_network_capture(capture_body=X)` | `network_capture(action='start', capture_body=X)` | 网络捕获启停合并 | +| `stop_network_capture()` | `network_capture(action='stop')` | | +| `search_code_in_script(url, keyword)` | `search_code(keyword, script_url=url)` | 合并到 search_code,script_url 可选参数 | +| `trace_function(path, ...)` | `hook_function(path, mode='trace', ...)` | 追踪合并到 hook_function | +| `instrument_jsvmp_source(url_pattern, ...)` | `instrumentation(action='install', url_pattern=url_pattern, ...)` | 源码插桩统一接口 | +| `get_instrumentation_log(tag=X)` | `instrumentation(action='log', tag=X)` | | +| `get_instrumentation_status()` | `instrumentation(action='status')` | | +| `stop_instrumentation()` | `instrumentation(action='stop')` | | +| `reload_with_hooks()` | `instrumentation(action='reload')` | | +| `get_cookies(...)` | `cookies(action='get', ...)` | Cookie 管理统一接口 | +| `set_cookies(...)` | `cookies(action='set', ...)` | | +| `list_scripts(...)` | `scripts(action='list', ...)` | 脚本管理统一接口 | +| `get_script_source(url)` | `scripts(action='get', url=url)` | | +| `save_script(url, path)` | `scripts(action='save', url=url, save_path=path)` | | +| `reverify_all_assertions_on_domain()` | `verify_assertion(assertion_id='*')` | 批量验证合并到 verify_assertion | +| `find_dispatch_loops(url)` | `search_code(keyword='switch', script_url=url, context_chars=500)` | 合并到 search_code 的高级用法 | +| `trace_property_access(...)` | `hook_jsvmp_interpreter(mode='proxy', trackProps=True)` | 合并到 hook_jsvmp_interpreter | +| `freeze_prototype(class, method)` | `hook_function('class.prototype.method', mode='intercept', ...)` | 合并到 hook_function | +| `get_page_content()` | `evaluate_js("document.documentElement.outerHTML")` | 用 evaluate_js 替代 | + +### 已删除工具及替代方案(12 个) + +| 已删除工具 | 替代方案 | +|-----------|---------| +| `get_property_access_log` | `get_jsvmp_log(type_filter='prop_read')` | +| `get_page_content` | `evaluate_js("document.documentElement.outerHTML")` | +| `find_dispatch_loops` | `search_code(keyword='switch', script_url='', context_chars=500)` | +| `trace_property_access` | `hook_jsvmp_interpreter(mode='proxy', trackProps=True)` | +| `freeze_prototype` | `hook_function('X.prototype.Y', mode='intercept', non_overridable=True)` | +| `check_csp_policy` | `evaluate_js("document.querySelector('meta[http-equiv=Content-Security-Policy]')?.content")` | +| `get_response_chain` | `navigate()` 返回值中的 `redirect_chain` 字段 | +| `get_dual_sign_data` | 组合 `list_network_requests` + `get_network_request` 手动提取 | +| `diff_hot_keys` | 本地 JSON diff 或 `evaluate_js` 实现 | +| `create_session` | `start_reverse_session` | +| `load_session` | `attach_domain_readonly` + `verify_assertion(assertion_id='*')` | +| `update_session` | `stop_reverse_session` + `add_assertion` | + +--- + +## 三、6 个月 Shim 兼容期 + +> MCP v0.9.0 提供 6 个月的 shim 兼容期(至 2027-01-01)。 + +### Shim 行为 + +- 旧工具名仍可调用,MCP 内部自动映射到新接口 +- 每次调用旧名会在 console 输出 deprecation warning +- Shim 期结束后旧名将返回 `ToolNotFound` 错误 + +### 建议迁移时间线 + +| 阶段 | 时间 | 行动 | +|------|------|------| +| 立即 | 现在 | 新代码/新文档全部使用 v0.9.0 名称 | +| 1 个月内 | 2026-08 | 更新所有 cases/*.md 中的工具名 | +| 3 个月内 | 2026-10 | 更新所有 references/*.md 中的工具名 | +| 6 个月内 | 2027-01 | 确认无旧名残留,shim 期结束 | + +--- + +## 四、MCP v0.9.0 关键变化总结 + +### 工具数量 + +- v0.8.x:~80 个独立工具 +- v0.9.0:~50 个工具(19 个合并为 7 个统一接口 + 12 个删除) + +### 设计原则 + +1. **同类操作合并**:start/stop/get/list 合并为带 `action` 参数的统一接口 +2. **功能重叠消除**:`trace_function` 和 `hook_function` 合并(trace 是 hook 的一种 mode) +3. **语义一致性**:`freeze_prototype` 本质是 hook 的 intercept 模式 +4. **减少记忆负担**:7 个统一接口覆盖原来 19 个工具的功能 + +### 不变的核心工具 + +以下工具在 v0.9.0 中保持不变: + +``` +launch_browser / close_browser / navigate / reload / go_back +click / type_text / wait_for / get_page_info +take_screenshot / take_snapshot +evaluate_js / evaluate_js_handle +get_page_html / add_init_script +set_breakpoint_via_hook / get_breakpoint_data +get_console_logs / get_trace_data +hook_function (扩展了 mode 参数) +inject_hook_preset / remove_hooks / bypass_debugger_trap +hook_jsvmp_interpreter / get_jsvmp_log / dump_jsvmp_strings +compare_env / analyze_cookie_sources +get_runtime_probe_log +list_network_requests / get_network_request / get_request_initiator +intercept_request / stop_intercept +search_response_body / get_response_body_page / search_json_path +delete_cookies / get_storage / set_storage +export_state / import_state +get_fingerprint_info / check_detection / get_session_info +start_reverse_session / stop_reverse_session / get_session_snapshot +list_sessions / attach_domain_readonly +export_session / import_session +add_assertion / verify_assertion / list_assertions / remove_assertion +``` + +--- + +## 五、Session/Assertion 工具移除(v3.2.0 新增) + +以下工具在当前 MCP 版本中**不存在**,skill v3.2.0 已移除所有引用: + +| 移除的工具 | 替代 | +|---|---| +| `start_reverse_session` / `stop_reverse_session` | 无替代。经验沉淀到 cases/ 文件 | +| `get_session_snapshot` | 无替代。手动查看 list_network_requests / instrumentation(action='log') | +| `list_sessions` / `attach_domain_readonly` | 无替代。读 cases/README.md 查已有案例 | +| `export_session` / `import_session` | 无替代。case 文件本身是可 commit 的经验 | +| `add_assertion` / `verify_assertion` / `list_assertions` / `remove_assertion` | 在 case 文件的"可验证事实清单"段列事实,AI 手动核对 | +| `verify_against_session(signer_code, sample_filter=...)` | `verify_signer_offline(signer_code, samples=[...])` — 样本由用户提供 | + +`verify_signer_offline` 迁移示例: + +```python +# 步骤 1 — 手动提取样本 +reqs = await list_network_requests(url_filter="api/search") +samples = [] +for req in reqs["requests"][:10]: + full = await get_network_request(req["id"]) + url = full["url"] + xbogus = _parse_url_param(url, "X-Bogus") + if xbogus: + samples.append({ + "id": f"req_{req['id']}", + "input": {"url": _strip_param(url, "X-Bogus")}, + "expected": {"X-Bogus": xbogus}, + }) + +# 步骤 2 — 验证 +r = await verify_signer_offline( + signer_code="(s) => ({'X-Bogus': mySign(s.input.url)})", + samples=samples, + compare_params=["X-Bogus"], +) +``` + +--- + +> **SKILL.md 核心层**有工具分类索引的精简版 +> **各工具的详细用法**:查看工具自身的 docstring diff --git a/references/path-a-four-tools.md b/references/path-a-four-tools.md new file mode 100644 index 0000000..eb76201 --- /dev/null +++ b/references/path-a-four-tools.md @@ -0,0 +1,417 @@ +# 路径 A:算法追踪 — 四板斧完整方法论 + +> **触发条件**:Phase 2 识别出反爬为签名型/行为型且确认用路径 A 时读此文档 +> +> **前置要求**:已完成反爬类型三分法识别(见 SKILL.md「反爬类型识别与工具选择」段) +> +> **版本**:v3.1.0(MCP v0.9.0 工具名迁移) + +--- + +## 一、四板斧总览 + +路径 A 的核心方法论是「从 I/O 两端夹逼 + 中间层插桩 + 源码级全量 tap」。 + +四板斧的关系: +- **第一 / 二 / 三板斧**:诊断"VM 看外界"(入口)和"外界看 VM"(出口)——适合签名通过 CryptoJS/atob/MD5 等可 hook 原语走的 VMP +- **第四板斧**:诊断"VM 看自己"——适合 VMP 算法全部封装在字节码分发循环 `switch(opcode) { case N: obj[key](args); ... }` 里的场景 + +### 四板斧概览表 + +| 板斧 | 名称 | 工具 | 擅长 | 不擅长 | 适用反爬类型 | +|------|------|------|------|--------|-------------| +| 第一斧 | Hook I/O | `inject_hook_preset(xhr/fetch/crypto/cookie)` + `hook_function('X.prototype.Y', mode='intercept', ...)` | 请求链路劫持、动态 Cookie、加密原语入口 | VM 内部自实现的 MD5/AES | 行为型 ✅ / 纯混淆 ✅ / 签名型 ❌ | +| 第二斧 | 插桩解释器 | `hook_function(path, mode='trace', ...)` + `hook_jsvmp_interpreter(mode='proxy', trackProps=True)` | 能识别分发函数名时的调用链追踪 | 匿名 IIFE 包裹 + 高频日志爆炸 | 行为型 ✅ / 纯混淆 ✅ / 签名型 ❌ | +| 第三斧 | 日志分析 | `get_jsvmp_log` + 反向追踪 | 已能捕获签名值 I/O 时反推公式 | 签名完全不出 VM 的黑箱模式 | 所有类型 ✅(纯被动分析,无副作用) | +| 第四斧 | 源码级插桩 | `instrumentation(action='install', ...)` + `instrumentation(action='log', ...)` | VM 内部调度、"全部在 opcode dispatch 循环里发生"的场景,`hot_keys` 直接暴露环境指纹集 | 极限大文件(5MB+)regex 模式覆盖率下降;AST 模式需 CDN | 所有类型 ✅,**签名型首选** | + +### 按反爬类型的适用性 + +| 反爬类型 | 可用板斧 | 说明 | +|----------|----------|------| +| **签名型**(RS / Akamai / Shape) | **仅第四斧** | 前三板斧会改变环境,破坏签名。源码级插桩不动环境,是唯一通用解 | +| **行为型**(短视频平台 / JY) | **全部四斧** | 不校验浏览器原生性,所有工具可用 | +| **纯混淆**(obfuscator.io / 自研 VMP) | **全部四斧** | 只是代码难读,不检测观察者 | + +--- + +## 二、快速路径(推荐优先试) + +> 快速路径足以解决 70%+ 的 RS/Akamai/webmssdk 场景。无法解决时再走手动四板斧流程。 + +### 8 步快速流程 + +``` +步骤 1:确认是否 VMP + search_code(keyword='switch', script_url='', context_chars=500) + + → case_count > 50 基本确认是 VMP + +步骤 2:一键装通用探针 + hook_jsvmp_interpreter(script_url=) + +步骤 3:装出口 Hook + inject_hook_preset("cookie") + inject_hook_preset("xhr", persistent=True) + +步骤 4:装源码级插桩(核心) + instrumentation(action='install', url_pattern="**/", mode="ast", tag="vmp1") + + (AST 模式覆盖率高,页面能联网时优先) + +步骤 5:让所有探针先于 VMP 生效 + instrumentation(action='reload') + + → 清日志,获得干净快照 + +步骤 6:触发目标操作 + evaluate_js / click / type_text → 翻页、搜索、登录等 + +步骤 7:读 hot_keys(30 秒定位环境指纹集) + instrumentation(action='log', tag='vmp1', type_filter='tap_get', limit=300) + + → 看 hot_keys / hot_methods / hot_functions 三个摘要 + +步骤 8:交叉印证 + get_jsvmp_log() + analyze_cookie_sources() +``` + +--- + +## 三、第一板斧:Hook I/O(确定 I/O 边界) + +### 目标 + +确定 JSVMP 的输入(读了什么环境值、接收了什么参数)和输出(生成了什么签名、写了什么 Cookie)。 + +### 详细步骤 + +#### 步骤 0:一键装多路径探针(快速路径,推荐先试) + +``` +MCP 操作: + hook_jsvmp_interpreter(script_url=) + → 自动覆盖 apply/call/bind + Reflect.*/Proxy 全局对象 + timing/random +``` + +#### 步骤 1:Hook 出口 — 请求与 Cookie + +``` +MCP 操作: + inject_hook_preset("xhr", persistent=True) → XHR 请求出口 + inject_hook_preset("fetch", persistent=True) → fetch 请求出口 + inject_hook_preset("cookie", persistent=True) → 原型链级 cookie hook + + hook_function('XMLHttpRequest.prototype.open', mode='intercept', non_overridable=True) + + → 冻结 XHR.open 防止页面 JS 覆盖 Hook + + analyze_cookie_sources() + → 辨识每个 Cookie 是 HTTP Set-Cookie / JS document.cookie / 混合写入 +``` + +#### 步骤 2:Hook 入口 — 加密原语 + +``` +MCP 操作: + inject_hook_preset("crypto") + → 自动捕获 btoa/atob/JSON.stringify I/O + + hook_function( + function_path="String.fromCharCode", + hook_code="console.log('[MCP] fromCharCode:', JSON.stringify([...arguments]))", + position="before" + ) + → 捕获字符编码操作(JSVMP 高频信号) +``` + +#### 步骤 3:关联出入口数据 + +``` +分析逻辑: + - 出口捕获到的签名值(如 sign=abc123) + - 入口捕获到的加密原语调用(如 MD5("timestamp+key")) + - 关联两者 → 推断签名公式 +``` + +--- + +## 四、第二板斧:插桩解释器(追踪执行链路) + +### 目标 + +在 JSVMP 解释器层面追踪执行链路,定位字节码分发函数和关键调用。 + +### 详细步骤 + +#### 步骤 3:定位字节码分发函数 + +``` +MCP 操作: + search_code(keyword='switch', script_url='', context_chars=500) + + → 定位 case_count > 20 的 switch 语句 + → case_count > 50 基本确认是 VMP 解释器 +``` + +#### 步骤 4:分层追踪函数调用 + +``` +MCP 操作(粗粒度 → 中粒度 → 细粒度): + + # 粗粒度:追踪解释器主函数 + hook_function( + function_path="<解释器主函数>", + mode='trace', + log_args=True, log_return=True, log_stack=False, + max_captures=100 + ) + + + # 中粒度:追踪子 handler + hook_function( + function_path="<子handler函数>", + mode='trace', + log_args=True, log_return=True, log_stack=True, + max_captures=50 + ) + + + # 细粒度:追踪特定加密函数 + hook_function( + function_path="<加密函数路径>", + mode='trace', + log_args=True, log_return=True, log_stack=True, + max_captures=30 + ) + + + ⚠️ 必须设置 max_captures 限制日志量,高频调用函数(每秒数千次)会爆炸 +``` + +#### 步骤 5:监控签名容器 + 采集环境基准 + +``` +MCP 操作: + hook_jsvmp_interpreter(mode='proxy', trackProps=True) + + → 监控 navigator.*/screen.*/document.cookie 等签名容器的属性读取 + + compare_env() + → 采集浏览器环境基准数据(navigator/screen/canvas/WebGL/Audio/timing) +``` + +--- + +## 五、第三板斧:日志分析(从海量数据提取签名链路) + +### 目标 + +从多维度日志中提取签名生成的完整链路。 + +### 详细步骤 + +#### 步骤 6:多维度日志采集 + +``` +MCP 操作: + get_trace_data() → 函数追踪数据 + get_jsvmp_log() → JSVMP 探针日志 + get_console_logs() → 控制台输出 + get_runtime_probe_log() → 运行时探针日志 +``` + +#### 步骤 7:反向追踪法 + +``` +分析方法: + 1. 从已知签名值(如 sign=abc123)出发 + 2. 在所有日志中搜索该值首次出现的位置 + 3. 从该位置反向追踪: + - 该值由哪个函数生成? + - 该函数的输入是什么? + - 输入又来自哪里? + 4. 逐层追踪直到找到原始明文输入 + + 这是处理海量日志数据效率最高的方法。 +``` + +#### 步骤 8:验证提取的算法 + +``` +MCP 操作: + evaluate_js("提取的签名函数(已知输入)") + → 对比输出与实际请求中的签名值 + → 一致则算法提取成功 +``` + +--- + +## 六、第四板斧:源码级插桩(通用 VMP 利器) + +> **v2.5.0 新增,签名型反爬的唯一通用解法** + +### 目标 + +在 HTTP 层改写 VMP 源码,对每个 `obj[key]` 读取和 `fn(args)` 调用插入 tap,不改变运行时环境。 + +### 为什么需要第四板斧 + +前三板斧(Hook/插桩/日志)在以下场景失效: +- RS 5/6:VM 完全自包含,不路由到可 hook API +- Akamai sensor_data v2/v3:算法全部在 opcode dispatch 循环内 +- webmssdk:签名逻辑封装在字节码中 +- 这些场景下 `hook_jsvmp_interpreter` 仍然看不到 switch/case 内部的 `opcode_table[code]` 调度 + +### 详细步骤 + +#### 步骤 9:安装源码级插桩 + +``` +MCP 操作: + instrumentation( + action='install', + url_pattern="**/", + mode="ast", + tag="vmp1", + rewrite_member_access=True, + rewrite_calls=True, + csp_bypass=True + ) + + + 模式选择: + - mode="ast"(默认推荐):MCP 侧 esprima 解析,99% 覆盖率,挑战页可用 + - mode="regex":纯正则改写,80% 覆盖率,无 CDN 依赖,大文件安全 + - AST 失败时自动 fallback 到 regex(fallback_on_error=True) +``` + +#### 步骤 10:让插桩先于 VMP 生效 + +``` +MCP 操作: + instrumentation(action='reload') + + → 清空 __mcp_vmp_log / __mcp_jsvmp_log / __mcp_cookie_log + → 获得干净的一次执行捕获 + + ⚠️ 签名型反爬:不要清 cookie!第一次挑战拿到的 cookie 要留下 +``` + +#### 步骤 11:读取插桩日志(指纹学习的金矿) + +``` +MCP 操作: + + # 读取属性访问 hot_keys + instrumentation(action='log', tag='vmp1', type_filter='tap_get', limit=200) + + → summary.hot_keys 告诉你 VMP 读取了哪些属性,按频次倒排 + → 典型 RS 输出:{"userAgent":120, "plugins":98, "webdriver":77, "cookie":43, ...} + → 这就是 VMP 参与签名哈希的完整环境指纹集 + + # 读取方法调用 hot_methods + instrumentation(action='log', tag='vmp1', type_filter='tap_method', limit=200) + + → summary.hot_methods 格式 ObjectType.methodName + → 能否看到 MD5/AES/HMAC 就是算法是否用标准加密的核心判据 + + # 读取函数调用 hot_functions + instrumentation(action='log', tag='vmp1', type_filter='tap_call', limit=200) + + → 看有没有 btoa/atob/encodeURIComponent 等熟识函数 +``` + +#### 步骤 12:完工清理 + +``` +MCP 操作: + instrumentation(action='stop', url_pattern="**/") + + → 关闭源码级 route +``` + +--- + +## 七、路径 A 失败模式与降级 + +### 常见失败模式 + +| 失败表现 | 可能原因 | 应对 | +|----------|----------|------| +| `instrumentation(action='log')` 返回空 | CSP 阻断 / url_pattern 未匹配 | 检查 CSP;确认 url_pattern 匹配到目标脚本 | +| `hot_keys` 只有 < 5 个属性 | regex 模式覆盖率不足 | 切换 mode="ast" 或增大 context_chars | +| 签名值始终不一致 | 环境指纹参与哈希但未补齐 | 根据 hot_keys 逐项补齐环境 | +| `navigate` 反复 412 | 观察者效应——Hook 破坏了签名 | 移除所有 pre_inject_hooks,改用源码级插桩 | +| AST 模式持续 fallback 到 regex | esprima 无法解析目标语法 | 接受 regex 80% 覆盖率,或退到 transparent 模式 | + +### 降级梯度(必须逐级走) + +``` +L1: instrumentation(action='install', mode="ast") + → 失败 +L2: instrumentation(action='install', mode="regex") + → 覆盖率不足 +L3: hook_jsvmp_interpreter(mode="transparent") + → 日志太少 +L4: hook_jsvmp_interpreter(mode="proxy") ← 仅行为型/纯混淆可用! + → 破坏签名 +L5: 路径 B(jsdom 环境伪装) + → 也失败 +L6: 向用户说明情况,建议浏览器自动化或 sdenv +``` + +**关键规则**: +- L1→L2→L3 是标准降级路径,每级必须尝试 +- L3→L4 仅对行为型反爬开放,签名型必须走 L3→L5 +- 到达 L6 前必须在 Session 档案中记录完整降级路径 +- **禁止从 L1 直接跳到 L6** + +--- + +## 八、路径 A 还原策略选择 + +四板斧完成后,根据收集到的信息选择还原策略: + +| 情况 | 策略 | 实现方式 | +|------|------|---------| +| 签名使用标准算法(MD5/HMAC/AES),`get_jsvmp_log` 能看到对应 API 调用 | 纯算法还原 | Node.js `crypto` / Python `hashlib` + `pycryptodome` | +| 签名逻辑是标准算法但拼接规则复杂 | 还原拼接逻辑 + 标准算法 | 提取拼接顺序和格式,手动实现 | +| 签名逻辑完全定制化,但 `hot_keys` 清晰暴露输入域 | 提取最小 JS 片段执行 | Node.js `vm` 沙箱 / Python `execjs` | +| VM 劫持了整个请求链路,`analyze_cookie_sources` 显示 cookie 来自 HTTP Set-Cookie | 纯算法还原不现实 | 转路径 B:jsdom 环境伪装 | +| VM 算法全部内联在 dispatch 循环,即便源码插桩 `hot_keys` 也无法还原 | 加载完整 VM + 最小环境 | 转路径 B:优先 jsdom 运行原始脚本 | + +### 后续还原路径决策 + +根据 `hot_keys` / `hot_methods` 制定策略: + +``` +路径 A-1 — hot_methods 里出现 CryptoJS.MD5 / SubtleCrypto.digest + → 纯算法还原 + +路径 A-2 — hot_methods 里全是自定义 fn 名 + → 提取 VMP 子片段 + Node.js vm 沙箱运行 + +路径 B — hot_keys 里环境指纹很多(navigator/screen/webgl 40+) + + analyze_cookie_sources 显示 cookie 来自 HTTP Set-Cookie + → 走 jsdom 环境伪装(见 SKILL.md 路径 B 段或 references/jsdom-env-patches.md) +``` + +--- + +## 九、JSVMP 核心经验(路径 A 专项) + +1. **VM 解释器本身不是目标,签名函数的 I/O 才是目标** — 不要试图反编译字节码 +2. **先 Hook 出口确定"要什么",再 Hook 入口确定"给了什么"** — 出口驱动分析 +3. **`hook_function(path, mode='trace', ...)` 对高频调用函数日志量可能爆炸** — 必须设置 `max_captures` 限制 +4. **`get_trace_data` 返回的海量数据需要本地过滤** — 用反向追踪法效率最高 +5. **`instrumentation(action='install', mode="regex")` 对带模板字符串、正则字面量的代码可能误改写** — AST 模式更可靠 +6. **前三板斧对签名型反爬不可用** — Hook `Function.prototype.apply` 会改变 toString 原生性;Proxy 在 navigator 上会被检测 +7. **`dump_jsvmp_strings` 前提是字符串未被动态解密** — 看到 `decoded_strings` 全是单字母乱码就是加密的 +8. **`search_code(keyword='while')` 在超大文件(380KB+)会返回大量无关结果** — 应使用 `search_code(keyword, script_url=url)` 配合更精确关键词 +9. **源码级插桩的两种模式都默认 `cache_rewritten=True`** — 避免重复改写 + +--- + +> **完整工作流参考**:SKILL.md「Phase 2.2+ JSVMP 专项分析」段 +> **源码级插桩专项指南**:references/jsvmp-source-instrumentation.md +> **经验案例**:cases/universal-vmp-source-instrumentation.md diff --git a/references/path-b-env-emulation.md b/references/path-b-env-emulation.md new file mode 100644 index 0000000..d9f0c49 --- /dev/null +++ b/references/path-b-env-emulation.md @@ -0,0 +1,579 @@ +# 路径 B:环境伪装 — jsdom/vm 沙箱六步法完整方法论 + +> **触发条件**:Phase 2 选择路径 B(jsdom/sdenv/vm 沙箱环境伪装)时读此文档 +> +> **前置要求**:已完成反爬类型三分法识别 + 路径选择决策树(见 SKILL.md) +> +> **版本**:v3.1.0(MCP v0.9.0 工具名迁移) + +--- + +## 一、核心思想 + +不分析 JSVMP 内部算法,而是在 jsdom 中完整运行原始 JSVMP 字节码,通过「采集 → 对比 → 补丁」使 JSVMP 认为自己运行在真实浏览器中,直接截获生成的签名值。 + +### 适用条件 + +- JSVMP 与浏览器环境深度绑定(环境指纹参与签名哈希) +- JSVMP 劫持了请求链路(XHR/fetch 拦截器),签名在内部完成 +- 算法不可直接提取(字节码保护 + 自定义算法) +- jsdom 可以成功加载并执行 JSVMP 脚本 + +### 典型用时 + +2-4 小时(对比路径 A 的 4-8 小时) + +--- + +## 二、步骤 0.5:确认签名函数入口(v2.7.0 新增) + +> ⚠️ 跳过此步骤可能导致:只 Hook 了 XHR 漏掉 fetch 通道的签名、未传 cacheOpts 导致拦截器不触发、误以为签名函数不存在而走了更复杂的路径。 + +### MCP 操作 + +``` +search_code(keyword="frontierSign", script_url='') + +→ 是否有导出签名函数 + +search_code(keyword="XMLHttpRequest.prototype.open", script_url='') + +→ 是否劫持 XHR + +search_code(keyword="window.fetch", script_url='') + +→ 是否劫持 fetch + +search_code(keyword="cacheOpts", script_url='') + +→ 是否使用新版初始化 +``` + +### 判定结果与后续路径 + +| 判定 | 签名入口类型 | 后续策略 | +|------|-------------|----------| +| 仅 XHR 拦截器 | 单通道截获 | Hook XHR.open 即可 | +| XHR + fetch 双通道拦截 | 双通道截获 | 需同时 Hook 两个通道 | +| 导出函数 + 拦截器 | 优先直接调用 | 优先调用导出函数,拦截器作为备选 | +| 含 cacheOpts 初始化 | 需配置初始化 | 必须传入 cacheOpts 配置才能注册业务路径 | + +--- + +## 三、步骤 1:Camoufox 环境指纹采集 + +### 目标 + +用 Camoufox 反检测浏览器采集真实浏览器的完整环境指纹,作为 jsdom 补丁的基准。 + +### MCP 操作 + +``` +launch_browser({headless: false, os_type: "macos", locale: "zh-CN"}) +navigate({url: "目标页面", wait_until: "domcontentloaded"}) +compare_env() +→ 采集主流环境基准数据(navigator/screen/canvas/WebGL/Audio/timing) +``` + +### 分批采集细粒度环境值(4-5 批次) + +> ⚠️ 单次 evaluate_js 代码太长会报错,必须分批采集 + +#### 批次 A:navigator 属性(24 项) + +``` +evaluate_js(`JSON.stringify({ + userAgent: navigator.userAgent, + platform: navigator.platform, + language: navigator.language, + languages: navigator.languages, + cookieEnabled: navigator.cookieEnabled, + doNotTrack: navigator.doNotTrack, + hardwareConcurrency: navigator.hardwareConcurrency, + maxTouchPoints: navigator.maxTouchPoints, + vendor: navigator.vendor, + vendorSub: navigator.vendorSub, + productSub: navigator.productSub, + appVersion: navigator.appVersion, + appName: navigator.appName, + appCodeName: navigator.appCodeName, + onLine: navigator.onLine, + webdriver: navigator.webdriver, + pdfViewerEnabled: navigator.pdfViewerEnabled, + deviceMemory: navigator.deviceMemory, + plugins_length: navigator.plugins.length, + mimeTypes_length: navigator.mimeTypes.length, + connection: navigator.connection ? { + effectiveType: navigator.connection.effectiveType, + downlink: navigator.connection.downlink, + rtt: navigator.connection.rtt + } : null, + userAgentData: navigator.userAgentData ? { + brands: navigator.userAgentData.brands, + mobile: navigator.userAgentData.mobile, + platform: navigator.userAgentData.platform + } : null, + getBattery: typeof navigator.getBattery, + mediaDevices: typeof navigator.mediaDevices +})`) +``` + +#### 批次 B:screen + window 属性(25 项) + +``` +evaluate_js(`JSON.stringify({ + screen_width: screen.width, + screen_height: screen.height, + screen_availWidth: screen.availWidth, + screen_availHeight: screen.availHeight, + screen_colorDepth: screen.colorDepth, + screen_pixelDepth: screen.pixelDepth, + screen_orientation: screen.orientation ? screen.orientation.type : null, + window_innerWidth: window.innerWidth, + window_innerHeight: window.innerHeight, + window_outerWidth: window.outerWidth, + window_outerHeight: window.outerHeight, + window_devicePixelRatio: window.devicePixelRatio, + window_screenX: window.screenX, + window_screenY: window.screenY, + chrome_exists: typeof window.chrome, + chrome_runtime: window.chrome ? typeof window.chrome.runtime : null, + Notification: typeof window.Notification, + Worker: typeof window.Worker, + SharedWorker: typeof window.SharedWorker, + RTCPeerConnection: typeof window.RTCPeerConnection, + AudioContext: typeof window.AudioContext, + OfflineAudioContext: typeof window.OfflineAudioContext, + fetch_exists: typeof window.fetch, + matchMedia: typeof window.matchMedia, + indexedDB: typeof window.indexedDB +})`) +``` + +#### 批次 C:document + performance + toString + Function.toString(28 项) + +``` +evaluate_js(`JSON.stringify({ + doc_hasFocus: document.hasFocus(), + doc_readyState: document.readyState, + doc_visibilityState: document.visibilityState, + doc_hidden: document.hidden, + doc_characterSet: document.characterSet, + doc_compatMode: document.compatMode, + doc_contentType: document.contentType, + doc_designMode: document.designMode, + doc_referrer: document.referrer, + doc_title: document.title, + doc_domain: document.domain, + doc_cookie_type: typeof document.cookie, + perf_timing: typeof performance.timing, + perf_navigation: typeof performance.navigation, + perf_now: typeof performance.now, + perf_memory: typeof performance.memory, + fn_toString_createElement: document.createElement.toString(), + fn_toString_getElementById: document.getElementById.toString(), + fn_toString_querySelector: document.querySelector.toString(), + fn_toString_addEventListener: EventTarget.prototype.addEventListener.toString(), + fn_toString_fetch: typeof window.fetch === 'function' ? window.fetch.toString() : null, + fn_toString_setTimeout: window.setTimeout.toString(), + obj_toString_document: Object.prototype.toString.call(document), + obj_toString_window: Object.prototype.toString.call(window), + obj_toString_navigator: Object.prototype.toString.call(navigator), + obj_toString_screen: Object.prototype.toString.call(screen), + symbol_toStringTag_doc: document[Symbol.toStringTag], + symbol_toStringTag_screen: screen[Symbol.toStringTag] +})`) +``` + +#### 批次 D:DOM 布局 + Canvas + WebGL + Audio(指纹检测类) + +``` +evaluate_js(`(function(){ + var d = document.createElement('div'); + d.style.cssText = 'width:100px;height:100px;position:absolute;left:-9999px'; + document.body.appendChild(d); + var layout = { + offsetHeight: d.offsetHeight, + offsetWidth: d.offsetWidth, + clientHeight: d.clientHeight, + clientWidth: d.clientWidth, + rect: d.getBoundingClientRect() + }; + document.body.removeChild(d); + + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + ctx.textBaseline = 'top'; + ctx.font = '14px Arial'; + ctx.fillText('test', 2, 2); + var canvasData = canvas.toDataURL(); + + var gl = document.createElement('canvas').getContext('webgl'); + var webglInfo = gl ? { + vendor: gl.getParameter(gl.VENDOR), + renderer: gl.getParameter(gl.RENDERER), + version: gl.getParameter(gl.VERSION) + } : null; + + return JSON.stringify({ + layout: layout, + canvas_hash: canvasData.length, + webgl: webglInfo, + audioContext: typeof AudioContext, + offlineAudioContext: typeof OfflineAudioContext + }); +})()`) +``` + +### 注意事项 + +- Camoufox 的 C++ 引擎级指纹伪装提供「干净基准」,比普通 Chrome 更可靠 +- `compare_env` 不覆盖 Function.prototype.toString / Symbol.toStringTag / DOM 布局等细粒度检测,这些必须通过 evaluate_js 手动采集 +- 采集代码模板参考 cases/ 经验案例中的「浏览器指纹采集方法」段 + +--- + +## 四、步骤 2:jsdom 环境采集 + +### 目标 + +在本地 Node.js 中创建 jsdom 实例,执行与步骤 1 完全相同的采集代码。 + +### 实现 + +```javascript +const { JSDOM } = require('jsdom'); +const dom = new JSDOM('', { + url: '目标URL', + pretendToBeVisual: true, + runScripts: 'dangerously' +}); +const win = dom.window; + +// 在 win.eval() 中运行与步骤 1 完全相同的采集脚本 +// 批次 A +const navResult = win.eval(`JSON.stringify({ + userAgent: navigator.userAgent, + platform: navigator.platform, + // ... 与步骤 1 批次 A 完全相同 +})`); + +// 批次 B、C、D 同理 +``` + +### 关键点 + +- jsdom 实例的 `url` 参数必须设为目标 URL(影响 document.URL / document.domain) +- `pretendToBeVisual: true` 使 `requestAnimationFrame` 可用 +- `runScripts: 'dangerously'` 允许执行脚本(环境伪装必需) + +--- + +## 五、步骤 3:Diff 与严重性分级 + +### 差异分级标准 + +#### 致命级 — 缺失即被服务端拒绝 + +| 检测项 | 浏览器值 | jsdom 默认值 | 影响 | +|--------|----------|-------------|------| +| `Function.prototype.toString` | `function createElement() { [native code] }` | 暴露 jsdom 实际 JS 代码 | 直接暴露非浏览器环境 | +| `navigator.plugins.length` | 5(典型值) | 0 | 插件数为 0 = 非浏览器 | +| `navigator.webdriver` | `false` | `undefined` | webdriver 检测 | +| `document.hasFocus()` | `true` | `false` | 焦点检测 | +| DOM `offsetHeight/Width` | 非零值 | 0 | 布局检测 | + +#### 高危级 — 可能参与指纹哈希 + +| 检测项 | 浏览器值 | jsdom 默认值 | +|--------|----------|-------------| +| `Object.prototype.toString.call(document)` | `[object HTMLDocument]` | `[object Document]` | +| `window.chrome` | `{runtime: {...}, ...}` | `undefined` | +| `navigator.userAgentData` | `{brands: [...], ...}` | `undefined` | +| `performance.timing/navigation` | 完整对象 | 缺失 | +| `Symbol.toStringTag` | 正确标签 | 不正确 | + +#### 中危级 — API 存在性检测(30+ 缺失 API) + +``` +window.Notification / Worker / SharedWorker / RTCPeerConnection +window.AudioContext / OfflineAudioContext / fetch +window.matchMedia / indexedDB / caches / visualViewport +navigator.connection / getBattery / deviceMemory +``` + +### 参考 + +完整的差异表和修复代码模板见 references/jsdom-env-patches.md。 + +--- + +## 六、步骤 4:patchEnvironment() 实现 + +### 修复优先级排序 + +按以下优先级逐项修复,每修一项立即验证: + +#### ① markNative 三层防御(最高优先级) + +``` +核心问题:jsdom 所有 DOM 方法的 toString() 会暴露实际 JS 代码 +解决方案:WeakSet + 源码正则 + 实例覆写 + 50+ 原型链扫描 + +三层防御: + Layer 1: WeakSet 记录所有需要伪装的函数引用 + Layer 2: 源码模式正则匹配(function X() { [native code] }) + Layer 3: 实例级覆写 toString(兜底) + +⚠️ Firefox vs Chrome 格式差异: + Chrome: "function createElement() { [native code] }" + Firefox: "function createElement() {\n [native code]\n}" + → Camoufox 基于 Firefox 内核,必须使用 Firefox 格式 +``` + +#### ② navigator 补丁 + +``` +修复项: + - plugins: 完整 PluginArray/Plugin/MimeType 对象树(length=5) + - webdriver: false + - userAgentData: {brands: [...], mobile: false, platform: "macOS"} + - connection: {effectiveType: "4g", downlink: 10, rtt: 50} + - 每个属性经 markNative 处理 +``` + +#### ③ window 补丁 + +``` +修复项: + - chrome 对象: {runtime: {}, app: {}, ...} + - 30+ API 存根: Notification / Worker / SharedWorker / RTCPeerConnection / + AudioContext / OfflineAudioContext / fetch / matchMedia / indexedDB / caches / + visualViewport 等 + - 每个存根经 markNative 处理 +``` + +#### ④ document + performance 补丁 + +``` +修复项: + - hasFocus(): 始终返回 true + - readyState: "complete" + - visibilityState: "visible" + - hidden: false + - performance.timing: 完整 PerformanceTiming 对象 + - performance.navigation: {type: 0, redirectCount: 0} +``` + +#### ⑤ DOM 布局属性 + +``` +修复项: + - offsetHeight/offsetWidth: 返回非零值 + - clientHeight/clientWidth: 返回非零值 + - getBoundingClientRect(): 返回合理的 DOMRect + - scrollHeight/scrollWidth: 返回非零值 +``` + +#### ⑥ Symbol.toStringTag 全面修复 + +``` +修复项: + - document → "HTMLDocument"(而非 "Document") + - screen → "Screen" + - navigator → "Navigator" + - window → "Window" + - 通过 Object.defineProperty 设置 Symbol.toStringTag +``` + +### 完整代码模板 + +参考 references/jsdom-env-patches.md 获取每个修复模块的完整实现代码。 + +--- + +## 七、步骤 5:内部验证(win.eval) + +### 目标 + +从 jsdom 内部(win.eval)验证所有检测点通过。 + +### 关键 + +验证代码必须在 jsdom 的 window 上下文中执行(win.eval),因为 JSVMP 看到的就是这个上下文。 + +### 验证代码 + +```javascript +const result = win.eval(`JSON.stringify({ + fnToString: document.createElement.toString(), + plugins: navigator.plugins.length, + webdriver: navigator.webdriver, + hasFocus: document.hasFocus(), + chromeExists: typeof window.chrome, + docTag: Object.prototype.toString.call(document), + offsetTest: (function(){ + var d = document.createElement('div'); + d.style.cssText = 'width:100px;height:100px'; + document.body.appendChild(d); + var h = d.offsetHeight; + document.body.removeChild(d); + return h; + })() +})`); + +// 预期结果: +// fnToString 包含 [native code](Firefox 格式含换行) +// plugins = 5 +// webdriver = false +// hasFocus = true +// chromeExists = 'object' +// docTag = '[object HTMLDocument]' +// offsetTest > 0 +``` + +### 验证清单 + +| 检测项 | 预期值 | 不通过时的修复模块 | +|--------|--------|-------------------| +| `document.createElement.toString()` | 含 `[native code]` | markNative 三层防御 | +| `navigator.plugins.length` | 5 | navigator 补丁 | +| `navigator.webdriver` | `false` | navigator 补丁 | +| `document.hasFocus()` | `true` | document 补丁 | +| `typeof window.chrome` | `'object'` | window 补丁 | +| `Object.prototype.toString.call(document)` | `'[object HTMLDocument]'` | Symbol.toStringTag 修复 | +| `div.offsetHeight` | > 0 | DOM 布局属性 | + +--- + +## 八、步骤 6:端到端验证 + +### 目标 + +在 jsdom 中加载完整 JSVMP 脚本,触发签名生成,用截获的签名值发起真实接口请求。 + +### 验证流程 + +``` +1. 在 jsdom 中加载完整 JSVMP 脚本并触发签名生成 +2. 用截获的签名值发起真实接口请求 +3. 确认返回有效数据(非空 body / 非错误码) +4. 连续多次请求验证稳定性(至少 5 次) +``` + +### 注意事项 + +- ⚠️ 服务端可能静默拒绝(返回 HTTP 200 + 空 body,不报错)— 这是环境检测失败的信号 +- 连续 5 次请求全部返回有效数据才算通过 +- 如果间歇性失败,检查是否有时间戳相关的环境值未补齐 + +--- + +## 九、环境伪装还原策略表 + +| 情况 | 策略 | 实现方式 | +|------|------|---------| +| JSVMP 劫持 XHR,在拦截器中追加签名 | 「喂入-截出」(feed-intercept) | jsdom + XHR Hook → 在 jsdom 内发 XHR,拦截器自动追加签名,Hook 截获 | +| JSVMP 导出签名函数到 window | 直接调用导出函数 | jsdom 加载 JSVMP → `win.签名函数(参数)` → 获取签名 | +| JSVMP + 预热初始化(如 SdkGlueInit) | 完整初始化链路 | jsdom 依次加载所有脚本 → 调用初始化函数 → 再触发签名生成 | +| JSVMP + cacheOpts 配置 | 配置驱动初始化 | 传入 cacheOpts 配置 → 注册业务路径 → 拦截器才会触发 | +| 双签名(URL 参数 + header) | 双通道截获 | 同时 Hook XHR.open(URL 签名)+ XHR.setRequestHeader(header 签名) | + +--- + +## 十、Firefox vs Chrome 变体差异 + +> Camoufox 基于 Firefox 内核,与 Chrome 在多个环境值上存在差异。jsdom 补丁必须匹配采集基准浏览器的格式。 + +### 关键差异 + +| 检测项 | Chrome 格式 | Firefox (Camoufox) 格式 | +|--------|------------|------------------------| +| `Function.prototype.toString` (native) | `"function name() { [native code] }"` | `"function name() {\n [native code]\n}"` | +| `navigator.vendor` | `"Google Inc."` | `""` | +| `navigator.vendorSub` | `""` | `""` | +| `navigator.productSub` | `"20030107"` | `"20100101"` | +| `window.chrome` | 完整对象 | `undefined`(需补丁) | +| `navigator.userAgentData` | 完整对象 | `undefined`(Firefox 不支持) | + +### 修复策略 + +- 如果目标站点的 JSVMP 是在 Chrome 环境下开发的,可能期望 Chrome 格式 +- 但 Camoufox 采集的基准是 Firefox 格式 +- **原则**:以 Camoufox 采集的基准为准,因为 Camoufox 的 C++ 引擎级伪装能通过反检测 +- 如果服务端明确校验 Chrome 特征(如 `window.chrome` 必须存在),则需要额外补丁 + +--- + +## 十一、常见踩坑与解决 + +### 坑 1:Canvas 存根不够真实 + +``` +问题:jsdom 的 canvas 返回空白数据,指纹哈希不匹配 +解决:使用 node-canvas 或 @napi-rs/canvas 提供真实 Canvas 实现 + 或者从 Camoufox 采集 canvas 指纹后硬编码返回值 +``` + +### 坑 2:native code 多行格式 + +``` +问题:markNative 输出了 Chrome 单行格式,但 Camoufox 基准是 Firefox 多行格式 +解决:将 markNative 的返回值从 + "function name() { [native code] }" + 改为 + "function name() {\n [native code]\n}" +``` + +### 坑 3:Symbol.toStringTag 遗漏 + +``` +问题:Object.prototype.toString.call(document) 返回 [object Document] 而非 [object HTMLDocument] +解决:Object.defineProperty(Document.prototype, Symbol.toStringTag, { + get: () => 'HTMLDocument', configurable: true + }) +``` + +### 坑 4:jsdom 环境补丁必须在 JSVMP 脚本加载前完成 + +``` +问题:JSVMP 在加载时就读取环境值,补丁打晚了无效 +解决:确保 patchEnvironment() 在任何 JSVMP 脚本 eval 之前执行 + XHR Hook 的安装顺序也决定能否截获最终 URL +``` + +### 坑 5:服务端静默拒绝 + +``` +问题:返回 HTTP 200 + 空 body(不报错),看起来请求成功但没数据 +信号:环境检测失败,签名格式正确但环境指纹不匹配 +解决:回到步骤 3 重新 diff,检查是否有遗漏的致命级差异 +``` + +### 坑 6:cacheOpts 未传入 + +``` +问题:拦截器不触发,签名不生成 +信号:旧版只需 bdms.paths,新版必须同时传入 cacheOpts +解决:检查 _SdkGlueInit 调用是否包含 cacheOpts 字段 + 确认 cacheOpts.paths 覆盖目标 API 路径 +``` + +--- + +## 十二、JSVMP 核心经验(路径 B 专项) + +1. **环境伪装优先于算法追踪** — 路径 B 通常比路径 A 快 10 倍 +2. **Function.prototype.toString 是 jsdom 环境伪装的第一杀手** — 三层防御必须到位 +3. **环境对比要分批采集** — 单次 evaluate_js 代码太长会报错 +4. **jsdom 环境补丁必须在 JSVMP 脚本加载前完成** — 加载顺序决定成败 +5. **服务端静默拒绝是环境检测失败的信号** — HTTP 200 + 空 body +6. **Firefox 格式 native code 与 Chrome 不同** — 必须匹配采集基准浏览器的格式 +7. **cacheOpts 是新版 SDK 初始化的必传项** — 缺少会导致拦截器不触发 +8. **环境伪装前先确认签名函数入口** — 步骤 0.5 不可跳过 + +--- + +> **环境补丁代码模板**:references/jsdom-env-patches.md +> **经验案例**:cases/jsvmp-xhr-interceptor-env-emulation.md、cases/jsvmp-dual-sign-xhr-intercept-cacheOpts-jsdom-firefox.md +> **完整工作流**:SKILL.md「路径 B:环境伪装」段 diff --git a/references/phase-details.md b/references/phase-details.md new file mode 100644 index 0000000..3d7d688 --- /dev/null +++ b/references/phase-details.md @@ -0,0 +1,586 @@ +# Phase 0-5 详细操作手册 + +> **触发条件**:执行某个 Phase 不确定具体怎么做时读 +> +> **前置要求**:已完成硬约束 Checklist 五项复述(见 SKILL.md 顶部) +> +> **版本**:v3.1.0(MCP v0.9.0 工具名迁移) + +--- + +## Phase 0:任务理解与调试环境搭建 + +### 0.1 任务理解 + +收到用户的目标 URL 和分析需求后: + +1. **明确分析目标**:需要还原哪些加密参数、目标数据是什么 +2. **接口分析**: + - 梳理请求的 URL、Method、Headers、Params、Body + - 识别并标记签名/动态参数(如 `sign`、`token`、`timestamp`、`nonce`) + - 根据参数特征(长度、字符集、结构)给出算法初步判断 + - 以简洁表格汇总分析结果,与用户确认 + +### 0.2 浏览器搭建 + +根据用户是否提供 Cookie,选择对应路径: + +#### 路径 A:带 Cookie 启动 + +``` +MCP 操作: + launch_browser(headless=false) + → 启动反检测浏览器(有头模式,方便用户观察) + + navigate(url="目标URL") + → 导航到目标页面 + + cookies(action='set', cookies=[...]) + + → 写入 Cookie + + reload() + → 刷新使 Cookie 生效 + + evaluate_js(expression="document.cookie") + → 验证写入成功 +``` + +#### 路径 B:直接启动(无 Cookie) + +``` +MCP 操作: + launch_browser(headless=false) + navigate(url="目标URL") + → 直接进入后续分析阶段 +``` + +### 0.3 Cookie 写入方法 + +用户提供的 Cookie 可能有多种格式: + +| 格式 | 处理方式 | +|------|---------| +| Cookie 字符串 `"k1=v1; k2=v2"` | 拆分后构造 `[{name:"k1", value:"v1", domain:".example.com", path:"/"}]` | +| JSON 格式(EditThisCookie 导出) | 直接传入 `cookies(action='set', ...)` | +| Request Headers 中的 Cookie 字段 | 按 `"; "` 拆分后构造数组 | + +### 0.4 项目目录创建 + +以目标网站/功能命名,结构参考 `templates/` 下的模板: + +**Node.js 项目:** +``` +project_name/ +├── config/ +│ ├── encrypt.js # 提取的原始 JS 加密代码 +│ ├── keys.json # 密钥、IV、Salt 等配置 +│ └── headers.json # 请求头模板 +├── utils/ +│ ├── encrypt.js # 加密参数生成函数 +│ └── request.js # 请求封装 +├── main.js # 主脚本 +├── package.json +└── README.md +``` + +**Python 项目:** +``` +project_name/ +├── config/ +│ ├── sign_logic.js # 提取的签名逻辑代码 +│ ├── keys.json # 密钥配置 +│ └── headers.json # 请求头模板 +├── utils/ +│ ├── sign.py # 签名函数封装 +│ └── request.py # 请求封装 +├── main.py +├── requirements.txt +└── README.md +``` + +--- + +## Phase 0.5:经验库命中验证 + +> ⚠️ 本步骤在硬约束 Checklist 的 [CHECK-2] + [CHECK-3] 中已经预执行过。 +> 这里是 Phase 0 完成后做二次确认和深入。 + +### 四分支决策 + +``` +┌─ [CHECK-2] 命中案例? +│ └─ YES → 分支 1:命中 cases/ +│ +├─ [CHECK-2] 命中工作区 site_* 目录? +│ └─ YES → 分支 2:命中 site_* +│ +├─ [CHECK-3] 历史 session 存在? +│ └─ YES → 分支 3:有历史 session +│ +└─ 全部未命中 → 分支 4:全未命中(30 秒指纹采集) +``` + +### 分支 1:命中 cases/ + +``` +MCP 操作: + readFile("cases/<命中案例>.md") + → 详读已验证定位路径 + → 按该案例的 Phase 1-5 执行(不要自创流程) + → 加 1-2 条本次站点特有的断言到 session(区别已有变体) +``` + +### 分支 2:命中 site_* 目录 + +``` +MCP 操作: + listDirectory("site_/") + → 识别 sign_service.js / cookie_fetcher.js / parser.js / keys.json 等文件 + + 若有 sign_service.js(已有协议方案): + → 直接基于 sign_service.js 建新目录复用 + → 不要从零写新方案 + → 不要用 Playwright/Camoufox 过挑战(违反红线 3) + + 若只有 cookie_fetcher.js(浏览器兜底方案): + → 警告:这是违反红线 3 的历史残留 + → 本次目标应是替换为协议方案 +``` + +### 分支 3:有历史 session + +``` +MCP 操作: + attach_domain_readonly("") # 在 CHECK-3 已做 + verify_assertion(assertion_id='*') # 批量验证所有断言 + + + 根据 triage.interpretation: + "all assertions still hold" + → 跳到 Phase 5 直接验证旧协议代码 + + "Minor update" + → 工作范围 = failed_ids,只修这几条 + + "Moderate L4 change" + → Phase 1-4 走一遍,但 passed 的是 invariants + + "Major L4 upheaval" + → 重做,但 passed 断言仍是不变量 +``` + +### 分支 4:全未命中(30 秒指纹采集) + +``` +MCP 操作(这些命令在 CHECK-2 时已跑过,这里是深入): + + # JSVMP / SDK 特征识别 + search_code(keyword="webmssdk") # webmssdk 家族 + search_code(keyword="byted_acrawler") # webmssdk 家族 + search_code(keyword="_sdkGlueInit") # 抖音特征 + search_code(keyword="cacheOpts") # TikTok 特征 + search_code(keyword="sdenv") # 瑞数 RS 特征 + search_code(keyword="meta-12") # RS 6 特征 + search_code(keyword="FSSBBIl1UgzbN7N") # RS cookie 特征 + + # 签名参数特征识别 + search_code(keyword="a_bogus") # 抖音 + search_code(keyword="X-Bogus") # TikTok 国际版 + search_code(keyword="X-Gnarly") # TikTok 国际版 + search_code(keyword="acw_sc__v2") # Aliyun WAF + + # 环境检测 SDK + search_code(keyword="acmescripts") # Akamai + + # 反爬类型关键字 + get_network_request(request_id=) + list_network_requests(resource_type="script") +``` + +采集完后,**必须**在本次分析结束时沉淀新案例: +1. 按 `cases/_template.md` 格式建立 `cases/<新案例名>.md` +2. 更新 `cases/README.md` 的"高频站点速查表"追加一行 +3. 更新 `cases/README.md` 的"变体关系图"(如有兄弟案例) + +--- + +## Phase 1:目标侦察 + +### 1.1 确认调试页面状态 + +``` +MCP 操作: + take_screenshot() + → 截取当前页面视觉状态,确认页面正常 + + 如需导航到特定子页面: + navigate(url="目标子页面") + + 如果涉及登录态,确认 Cookie 已写入且页面内容正确 +``` + +### 1.2 网络请求捕获 + +``` +MCP 操作: + network_capture(action='start') + + → 开始捕获网络流量 + + evaluate_js / click / type_text + → 触发翻页/交互,产生请求 + + list_network_requests() + → 获取捕获的请求列表(支持过滤) + + get_network_request(request_id=N) + → 获取关键接口的详细信息 + + get_request_initiator(request_id=N) + → 获取发起请求的 JS 调用栈(黄金路径!) + + 重点关注: + - Request URL、Method + - Request Headers(Cookie、自定义签名头) + - Query Params / Request Body(识别加密参数) + - Response 数据结构 + - Initiator Stack(直接定位加密函数) +``` + +### 1.3 加密参数识别 + +对比多次请求,分析每个参数: + +| 参数类型 | 特征 | 处理方式 | +|----------|------|---------| +| 固定值 | 每次请求相同 | 直接硬编码或从页面提取 | +| 动态值 | 有规律变化 | 判断变化因子(时间戳、页码、随机数、自增) | +| 加密值 | 看似随机 | 根据长度、字符集、格式判断算法类型 | + +### 1.4 输出侦察报告 + +``` +📋 目标信息 +━━━━━━━━━━━━━━━━━━━━━━━━ +目标网站:[URL] +分析目标:[需要还原的加密逻辑] +数据接口:[API endpoint] + +🔗 接口参数分析 +━━━━━━━━━━━━━━━━━━━━━━━━ +URL:[完整请求URL] +Method:GET/POST +Headers: + - Cookie: [关键字段及示例] + - [自定义头]: [示例值] +加密参数: + - 参数名: [名称] | 示例值: [值] | 长度: [N] | 字符集: [hex/base64/...] | 初步猜测: [算法] + +📊 响应数据样本 +━━━━━━━━━━━━━━━━━━━━━━━━ +[前2-3条数据] + +🧠 技术分析要点 +━━━━━━━━━━━━━━━━━━━━━━━━ +本目标涉及的签名分析技术点: + 1. [如:OB混淆还原] + 2. [如:动态Cookie生成] + 3. [如:AES-CBC加密] +``` + +--- + +## Phase 2:源码分析 + +### 2.1 关键词搜索定位 + +``` +MCP 操作: + search_code(keyword="加密参数名") + → 直接在已加载的 JS 源码中搜索 + + search_code(keyword="encrypt|sign|token|md5|sha|aes|des|rsa|hmac|btoa|atob|CryptoJS") + search_code(keyword="XMLHttpRequest|$.ajax|fetch|beforeSend") + search_code(keyword="document.cookie") + + 根据搜索结果: + scripts(action='get', url='<脚本URL>') + + → 读取包含加密逻辑的源码片段 + + scripts(action='save', url='<脚本URL>', save_path='./config/target.js') + + → 保存关键脚本到本地分析 +``` + +### 2.2 代码混淆识别 + +| 混淆类型 | 特征 | 还原策略 | +|---------|------|---------| +| OB 混淆 | `_0x` 前缀变量、十六进制字符串数组 | 字符串解密 + 变量重命名 | +| 控制流平坦化 | `switch-case` 状态机、`while(true)` 循环 | 追踪状态转移还原执行顺序 | +| eval/Function 打包 | `eval(...)` 或 `new Function(...)` 包裹 | Hook eval/Function 拦截源码 | +| JSVMP | 200KB+ 文件、自定义解释器 | 不反编译,走路径 A 或路径 B | + +### 2.2+ JSVMP 专项分析 + +当识别到 JSVMP 时,**严禁尝试反编译字节码**。 + +#### JSVMP 识别标志 + +- 超大 JS 文件(200KB+),函数/变量名完全无意义 +- 包含自定义解释器循环:`while(true) { switch(opcode) { ... } }` +- 改写或劫持浏览器原生 API(XHR / fetch / Cookie) +- 超大数组(字节码)+ 指针变量 + 栈操作 + 跳转指令 + +#### 路径选择决策树 + +``` +决策树: + ├─ JSVMP 是否劫持了请求链路(XHR/fetch 拦截器)? + │ ├─ YES + 算法与环境指纹深度绑定(如 a_bogus) + │ │ → 优先选路径 B(环境伪装) + │ │ → 路径 B 失败时回退路径 A + │ └─ YES 但签名逻辑相对独立 + │ → 路径 A(算法追踪),提取签名函数 + │ + ├─ JSVMP 仅生成签名参数(不劫持请求)? + │ ├─ Hook 确认使用标准算法 → 路径 A,纯算法还原 + │ └─ 算法完全自定义 + 环境依赖重 → 路径 B + │ + └─ 无法判断 → 先快速测试路径 B(30 分钟),不行再走路径 A +``` + +#### 反爬类型前置判断 + +``` +先判断 JSVMP 所在反爬类型: + +┌─ navigate(url) 不加任何 hook,看 redirect_chain +│ +├─ redirect_chain 反复 412 然后才到 200 +│ → 签名型 JSVMP +│ → 路径 A 只能走第四板斧(源码级插桩) +│ → 前三板斧禁用(会破坏签名) +│ +├─ redirect_chain 直接 200 但页面后续 XHR 带签名参数 +│ → 行为型 JSVMP +│ → 路径 A 四板斧全开 +│ +└─ redirect_chain 直接 200 无特殊签名参数 + → 不是签名型也不是 JSVMP,走混淆还原 +``` + +#### 调用链追踪 + +``` +MCP 操作: + inject_hook_preset(preset="xhr") → 一键注入 XHR Hook + inject_hook_preset(preset="fetch") → 一键注入 Fetch Hook + reload() → 刷新页面触发 Hook + get_request_initiator(request_id=N) → 获取 JS 调用栈(黄金路径) + → 从调用栈中逐层定位:请求发送 → 参数构造 → 加密函数 → 密钥/明文来源 + set_breakpoint_via_hook(target_function="加密函数名") + get_breakpoint_data() → 获取捕获的参数、返回值和调用栈 +``` + +#### 提取核心逻辑 + +``` +MCP 操作: + scripts(action='save', url='<脚本URL>', save_path='./config/target.js') + + → 保存完整脚本 + + 手动提取关键函数到 config/encrypt.js + 用中文注释标注每个函数的作用、输入输出 +``` + +### 2.2++ 静态分析关键判断清单 + +在源码分析阶段,必须确认以下内容: + +- [ ] 参数是单独加密还是整条请求链被接管(URL 重写 / 请求劫持) +- [ ] 页码、时间戳、随机数、Cookie、UA、环境变量是否参与运算 +- [ ] 是否存在响应解密(接口返回加密字符串而非明文 JSON) +- [ ] 是否存在运行时代码生成(`eval` / `new Function`) +- [ ] 是否有前置请求(预热接口、Token 获取接口) +- [ ] 是否有请求链改写(拦截 XHR/fetch 添加签名头) + +--- + +## Phase 3:动态验证 + +对静态分析的结论,在调试浏览器页面上进行运行时验证。 + +### 3.1 Hook 注入验证 + +``` +MCP 操作: + inject_hook_preset(preset="xhr") → XHR Hook + inject_hook_preset(preset="fetch") → Fetch Hook + inject_hook_preset(preset="crypto") → 加密函数 Hook(btoa/atob/JSON.stringify) + + hook_function( + function_path="自定义目标", + hook_code="...", + position="before|after|replace" + ) + → 自定义 Hook + + reload() + → 刷新触发 Hook + + get_console_logs() + → 读取 Hook 输出 +``` + +### 3.2 断点与追踪确认 + +``` +MCP 操作: + set_breakpoint_via_hook(target_function="加密函数路径") + → 通过 JS Hook 设置伪断点 + + hook_function( + function_path="加密函数路径", + mode='trace', + log_args=True, log_return=True, log_stack=True + ) + + → 追踪函数调用(不暂停执行) + + 触发目标操作: + evaluate_js / click / type_text → 模拟交互 + + get_breakpoint_data() → 获取伪断点捕获的参数和返回值 + get_trace_data() → 获取函数追踪数据 + + 重点确认: + - 加密算法的具体模式(AES 的 ECB/CBC、填充方式、密钥长度) + - 参数拼接顺序和格式 + - 时间戳精度(秒 vs 毫秒) + - 密钥/IV 的来源(硬编码 vs 服务端返回 vs 动态计算) +``` + +### 3.3 多次请求对比 + +``` +MCP 操作: + evaluate_js / click → 触发多次数据请求 + list_network_requests() → 收集捕获的网络请求 + → 对比加密参数变化规律,确认变化因子 +``` + +--- + +## Phase 4:算法还原 + +### 4.1 语言选择策略 + +| 维度 | 选 Node.js | 选 Python | +|------|-----------|-----------| +| 加密逻辑复杂度 | 自定义逻辑可直接用 `vm` 沙箱执行原始 JS | 标准算法可直接用 Python 库还原 | +| 团队技术栈 | 用户/团队偏好 Node.js | 用户/团队偏好 Python | +| JSVMP 场景 | VM 沙箱可直接加载整个 VM | 需 `execjs` 桥接 | +| TLS 指纹需求 | 需额外配置 | `curl_cffi` 一行搞定 | + +### 4.2 解法模式选择 + +| 模式 | 适用场景 | Node.js 模板 | Python 模板 | +|------|---------|-------------|-------------| +| A: 纯算法还原 | 加密逻辑可完整提取,无浏览器环境依赖 | `templates/node-request/` | `templates/python-request/` | +| B: 沙箱执行 JS | 服务端返回混淆 JS 用于生成 Cookie/Token | `templates/vm-sandbox/` | `templates/python-request/`(用 `execjs`) | +| C: WASM 加载还原 | 加密逻辑在 WebAssembly 中实现 | `templates/wasm-loader/` | — | +| D: 浏览器自动化 | TLS 指纹检测、复杂环境依赖、无法脱离浏览器 | `templates/browser-auto/` | — | +| E: jsdom 环境伪装 | JSVMP 深度绑定环境指纹、算法不可提取 | jsdom + references/jsdom-env-patches.md | — | + +### 4.3 编码原则 + +1. **先通后全**:先成功请求到第 1 页/第 1 条数据,验证加密正确后再扩展 +2. **优先纯算法**:标准算法 Node.js 用 `crypto` / `crypto-js`,Python 用 `hashlib` / `pycryptodome` / `hmac` +3. **中间值对比**:打印关键中间值(明文、密文、签名),与浏览器抓包值逐一比对 +4. **配置外置**:密钥、Headers 模板等写入独立配置文件 +5. **错误处理**:包含重试机制、频率控制、异常告警 +6. **逐步验证**:每次只增加一个参数的实现,确保每步可独立验证 +7. **代码可运行**:提供的代码必须是可直接复制运行的,不留占位符 +8. **分析产物持久化**:长参数值、Cookie、JS 代码片段、请求样本等,第一时间写入 `config/` 目录 + +### 4.4 配置文件策略 + +| 产物类型 | 存放位置 | 说明 | +|---------|---------|------| +| Cookie 字符串 | `config/cookies.txt` 或 `config/cookies.json` | 完整 Cookie | +| 长参数样本 | `config/params_sample.json` | 捕获的请求参数样本(含签名值) | +| 提取的 JS 代码 | `config/sign_logic.js` / `config/encrypt.js` | 签名/加密函数及其依赖 | +| Headers 模板 | `config/headers.json` | 完整的请求头 | +| 响应样本 | `config/response_sample.json` | 接口返回的数据样本 | +| 密文样本 | `config/ciphertext_samples.txt` | 多次请求的加密参数值 | + +**核心原则**:分析过程中产生的任何长文本都应该立即持久化到 `config/` 文件中。后续代码只需「读取文件」而非「内联长字符串」。 + +--- + +## Phase 5:断言驱动交付(v2.9.0 升级) + +### 5.1 运行验证 + +``` +MCP 操作: + 1. 运行 main.js / main.py,确认输出正确数据 + 2. 与浏览器实际数据交叉验证(≥ 5 次请求,确认签名稳定性) +``` + +### 5.2 写入/更新域级 Session 档案 + +``` +MCP 操作: + a. 将本次分析的断言集写入 assertions.json + 至少包含:script_exists / anti_crawl_type / cookie_names + + b. 更新 session.json 的 hot_keys_snapshot 和 cookie_attribution_summary + + c. 更新 fingerprint.json(如有新的指纹采集数据) + + d. 更新 assertions_passed / assertions_failed / assertions_total + + e. 输出:"域级 Session 档案已更新,N 条断言已写入,下次分析同域时将自动复用" +``` + +### 5.3 生成 README.md + +记录以下内容: +- 目标信息与接口分析 +- 加密逻辑还原过程 +- 涉及的签名分析技术点 +- 运行方式与依赖说明 + +### 5.4 经验沉淀 + +**主动询问用户是否沉淀经验**到 cases/(按 cases/_template.md 格式)。 + +沉淀内容包括: +- 反爬类型判定过程 +- 关键技术点和踩坑记录 +- 已验证的定位路径 +- 环境补丁清单(如走路径 B) + +### 5.5 断言驱动交付清单 + +| 交付项 | 必须 | 说明 | +|--------|------|------| +| 可运行的 main.js / main.py | ✅ | 纯协议脚本,无浏览器依赖 | +| config/ 目录 | ✅ | 密钥、Headers、JS 代码等配置 | +| README.md | ✅ | 项目说明 + 接口分析记录 | +| Session 档案更新 | ✅ | 断言集 + 指纹快照 + Cookie 归因 | +| cases/ 经验沉淀 | 推荐 | 主动询问用户 | +| ≥ 5 次请求验证 | ✅ | 确认签名稳定性 | + +--- + +> **硬约束 Checklist**:SKILL.md 顶部 +> **路径 A 详细操作**:SKILL.md「路径 A:算法追踪(四板斧)」段 +> **路径 B 详细操作**:SKILL.md「路径 B:环境伪装(jsdom/vm 沙箱执行原始 JSVMP)」段 +> **错误处理准则**:SKILL.md「错误处理准则」段 +> **降级梯度表**:SKILL.md「AI 想放弃时的降级梯度表」段