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