v2.7.0: dual-sign case + experience library upgrade + MCP v0.6.0 alignment

- case: add jsvmp-dual-sign-xhr-intercept-cacheOpts-jsdom-firefox (behavior type)
  Fingerprints: webmssdk/byted_acrawler/cacheOpts/X-Bogus+X-Gnarly dual sign
  Core tech: jsdom Firefox env emulation + XHR/fetch dual-channel intercept + cacheOpts init
  Pitfalls: 12 gotchas documented

- SKILL.md: 9 changes
  - env emulation step 0.5: confirm sign function entry before patching
  - experience rules expanded 32 -> 40
  - scene 10/11: navigate params + CSP pre-check
  - core weapon: MCP v0.5.0 -> v0.6.0 (68 tools)
  - error handling: 3 new failure patterns

- cases/_template.md: add anti-bot type + key experience sections
- cases/README.md: add fingerprint matching table + variant diagram
- sanitize: remove all site names from all files
This commit is contained in:
白夜
2026-04-18 04:00:05 +08:00
parent 00388541d4
commit 2ae0c35a6e
8 changed files with 554 additions and 24 deletions
+38 -1
View File
@@ -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 条经验)
---
```
+30
View File
@@ -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. [经验标题]
[经验描述]
@@ -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 采集的环境保持一致。
+1 -1
View File
@@ -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 方案。
```
@@ -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)` |
---