Files
hello_js_reverse_skill/tcaptcha/chrome_fetch.js
T
yml2213 1ef819b48d feat(纯算): 以成功样本明文为模板的纯 Python 出包管线 + 证据账本 v2
做了什么
- 从「签到成功-1.har」提取该次会话的 tdc.js, 提取密钥解密出
  errorCode=0 那次 collect 的完整明文 (1360 字节), 存为
  tcaptcha/models/success_collect_template.b64
- 新增 generate_collect_success(): 以成功样本为结构模板, Python 只回填
  轨迹/当前时间戳/随机盐, 其余环境字段 (canvas/WebGL/屏幕/语言/sd.ft) 原样保留
- 新增 _success_style_trajectory(): 按成功样本实测统计生成轨迹
  (61 点, dt 均值 30.6ms, 50% 在 0~16ms, 途经 3 个目标, 驻留抖动)
  —— 修正了统计正则不允许负增量的 bug (真实 61 点, 此前误数成 18)
- 发现成功样本用 \x00 填充 (不是空格), sd 多一个 ft 字段
- telemetry_mode=python_keyed 默认走该路径; Node 仅提取每构建 XTEA 密钥
- ANALYSIS.md 第七节重写为 v2 证据账本, 显式撤回 4 个无证据结论

验证
- 纯 Python 密文 == 真实 tdc.js 密文 (逐字节)
- 回归: test_xtea_key 11 项、test_glyph_solver 8/8
- 端到端 1ms 实测: 仍 errorCode=12 (诚实记录, 未解决)

结论(见 ANALYSIS.md 7.5-7.7)
- 12 不是"检测到机器人": 同 IP 有成功样本、参数逐字段一致、
  真 Chrome 网络栈/真人点击/CDP 均排除
- 剩余假设: H1 需已登录业务会话(未用 auth_token 调 captcha/params 复测)
            H3 需有效 qcloud.com cookie(过期 skey 复测不能定论)
- 下一步: 用登录态 auth_token 调 /api/v1/captcha/params,
  对比返回是否多出鉴权字段 —— 这是唯一未做且成本低的关键实验
2026-10-11 19:12:23 +08:00

110 lines
4.6 KiB
JavaScript

#!/usr/bin/env node
/**
* 用真实 Chrome 的网络栈发一个请求 (TLS/HTTP2/header 顺序全是浏览器的)
*
* 用途: 验证服务端风控是否在按「客户端网络指纹」判定。
* 从真实验证码域 https://turing.captcha.gtimg.com/ 发起 fetch,
* Origin/Referer/Sec-Fetch-* 都由浏览器自己填。
*
* 用法:
* echo '<json body>' | node chrome_fetch.js <url> [originUrl]
*/
const { spawn } = require('child_process');
const fs = require('fs');
const os = require('os');
const path = require('path');
const CHROME = process.env.REA_BROWSER_EXECUTABLE ||
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
async function launchChrome() {
const profile = fs.mkdtempSync(path.join(os.tmpdir(), 'chrome-fetch-'));
const proc = spawn(CHROME, [
'--headless=new', '--remote-debugging-port=0', '--no-first-run',
'--no-default-browser-check', '--disable-gpu', '--disable-dev-shm-usage',
'--disable-web-security', '--disable-features=IsolateOrigins,site-per-process',
`--user-data-dir=${profile}`, '--window-size=1280,800', 'about:blank',
], { stdio: ['ignore', 'pipe', 'pipe'] });
const portFile = path.join(profile, 'DevToolsActivePort');
let port = null;
for (let i = 0; i < 120; i++) {
if (fs.existsSync(portFile)) {
port = parseInt(fs.readFileSync(portFile, 'utf8').split('\n')[0], 10);
if (port) break;
}
await sleep(100);
}
if (!port) { proc.kill('SIGKILL'); throw new Error('Chrome 未就绪'); }
return { proc, port, profile };
}
class Cdp {
constructor(ws) { this.ws = ws; this.id = 0; this.waits = new Map(); }
static async connect(url) {
const ws = new WebSocket(url);
await new Promise((res, rej) => { ws.onopen = res; ws.onerror = rej; });
const c = new Cdp(ws);
ws.onmessage = ev => {
const m = JSON.parse(ev.data);
if (m.id && c.waits.has(m.id)) {
const { res, rej } = c.waits.get(m.id);
c.waits.delete(m.id);
m.error ? rej(new Error(JSON.stringify(m.error))) : res(m.result);
}
};
return c;
}
send(method, params = {}, sessionId) {
const id = ++this.id;
return new Promise((res, rej) => {
this.waits.set(id, { res, rej });
this.ws.send(JSON.stringify({ id, method, params, sessionId }));
});
}
}
async function main() {
const url = process.argv[2];
const origin = process.argv[3] || 'https://turing.captcha.gtimg.com/';
const body = fs.readFileSync(0, 'utf8').trim();
const { proc, port, profile } = await launchChrome();
try {
const ver = await (await fetch(`http://127.0.0.1:${port}/json/version`)).json();
const cdp = await Cdp.connect(ver.webSocketDebuggerUrl);
const { targetId } = await cdp.send('Target.createTarget', { url: origin });
const { sessionId } = await cdp.send('Target.attachToTarget', { targetId, flatten: true });
await cdp.send('Page.enable', {}, sessionId);
await sleep(2500); // 等页面(真实验证码域)加载完
const loc = await cdp.send('Runtime.evaluate', { expression: 'location.href', returnByValue: true }, sessionId);
process.stderr.write('[page] ' + loc.result.value + '\n');
const expr = `(async () => {
const r = await fetch(${JSON.stringify(url)}, ${JSON.stringify(body)}
? { method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
body: ${JSON.stringify(body)}, credentials: 'include' }
: { method: 'GET', credentials: 'include' });
const t = await r.text();
return JSON.stringify({ status: r.status, text: t });
})()`;
const out = await cdp.send('Runtime.evaluate',
{ expression: expr, awaitPromise: true, returnByValue: true }, sessionId);
if (out.exceptionDetails) {
console.log(JSON.stringify({ _exception: out.exceptionDetails.text,
desc: (out.exceptionDetails.exception || {}).description }));
} else if (out.result && out.result.value !== undefined) {
console.log(out.result.value);
} else {
console.log(JSON.stringify({ _raw: out }));
}
cdp.ws.close();
} finally {
proc.kill('SIGKILL');
try { fs.rmSync(profile, { recursive: true, force: true }); } catch (_) {}
}
}
main().catch(e => { console.error('ERR', e.message); process.exit(1); });