#!/usr/bin/env node /** * 在真实浏览器里用目标 aid 实例化验证码, 抓出它自己发出的真实请求。 * * 与 tdc_worker / 纯协议实现的区别: 这里 Origin 是真业务域, SDK 是腾讯当前版本, * 请求参数完全由官方前端构造 —— 用来和我们的协议实现做逐字段对照。 * * 用法: node probe_captcha.js [waitMs] */ 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'; const ORIGIN = process.argv[2] || 'https://1ms.run/'; const AID = process.argv[3] || '195672344'; const WAIT = parseInt(process.argv[4] || '12000', 10); const sleep = ms => new Promise(r => setTimeout(r, ms)); async function launchChrome() { const profile = fs.mkdtempSync(path.join(os.tmpdir(), 'probe-cap-')); const proc = spawn(CHROME, [ '--headless=new', '--remote-debugging-port=0', '--no-first-run', '--no-default-browser-check', '--disable-gpu', '--disable-dev-shm-usage', `--user-data-dir=${profile}`, '--window-size=1280,900', 'about:blank', ], { stdio: ['ignore', 'pipe', 'pipe'] }); const portFile = path.join(profile, 'DevToolsActivePort'); let port = null; for (let i = 0; i < 150; 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(); this.on = () => {}; } 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); } else if (m.method) c.on(m); }; 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 { proc, port, profile } = await launchChrome(); const captured = []; let sessionId = null; 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 }); ({ sessionId } = await cdp.send('Target.attachToTarget', { targetId, flatten: true })); cdp.on = m => { if (m.method === 'Network.requestWillBeSent') { const r = m.params.request; if (/turing\.captcha|cap_union/.test(r.url)) { captured.push({ url: r.url, method: r.method, headers: r.headers, postData: (r.postData || '').slice(0, 4000) }); } } else if (m.method === 'Runtime.consoleAPICalled') { const t = (m.params.args || []).map(a => a.value).join(' '); if (t) process.stderr.write('[console] ' + t.slice(0, 300) + '\n'); } }; await cdp.send('Network.enable', {}, sessionId); await cdp.send('Page.enable', {}, sessionId); await cdp.send('Runtime.enable', {}, sessionId); await sleep(3000); const loc = await cdp.send('Runtime.evaluate', { expression: 'location.href', returnByValue: true }, sessionId); process.stderr.write('[page] ' + loc.result.value + '\n'); // 注入官方 SDK 并实例化目标 aid 的验证码 const inject = `(async () => { window.__capcb = null; await new Promise((res, rej) => { const s = document.createElement('script'); s.src = 'https://turing.captcha.qcloud.com/TJCaptcha.js'; s.onload = res; s.onerror = () => rej(new Error('SDK 加载失败')); document.head.appendChild(s); }); if (typeof TencentCaptcha !== 'function') return 'TencentCaptcha 未定义: ' + Object.keys(window).filter(k=>/captcha/i.test(k)).join(','); window.__cap = new TencentCaptcha(${JSON.stringify(AID)}, function (res) { window.__capcb = JSON.stringify(res); }); window.__cap.show(); return 'shown'; })()`; const r = await cdp.send('Runtime.evaluate', { expression: inject, awaitPromise: true, returnByValue: true }, sessionId); process.stderr.write('[inject] ' + JSON.stringify(r.result.value || r.exceptionDetails) + '\n'); await sleep(WAIT); // 如果验证码已经自己完成 (拖动/点选由我们后续处理), 这里先看回调 const cb = await cdp.send('Runtime.evaluate', { expression: 'window.__capcb || ""', returnByValue: true }, sessionId); if (cb.result.value) process.stderr.write('[callback] ' + cb.result.value + '\n'); console.log(JSON.stringify({ captured }, null, 1)); 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); });