/** * Tencent TDC.js Sandboxed Worker & Mouse Trajectory Generator * Runs TDC.js in an isolated mock DOM environment to generate authentic * encrypted telemetry (`collect`) and session encryption key (`eks`). */ const fs = require('fs'); const vm = require('vm'); class Element { constructor(tagName) { this.tagName = (tagName || 'div').toUpperCase(); this.children = []; this.style = {}; this.attributes = {}; } appendChild(child) { this.children.push(child); return child; } removeChild(child) { const idx = this.children.indexOf(child); if (idx !== -1) this.children.splice(idx, 1); return child; } insertBefore(newChild, refChild) { const idx = this.children.indexOf(refChild); if (idx !== -1) this.children.splice(idx, 0, newChild); else this.children.push(newChild); return newChild; } cloneNode(deep) { const el = new Element(this.tagName); el.style = { ...this.style }; return el; } setAttribute(k, v) { this.attributes[k] = String(v); } getAttribute(k) { return this.attributes[k] || null; } hasAttribute(k) { return k in this.attributes; } removeAttribute(k) { delete this.attributes[k]; } getBoundingClientRect() { return { top: 0, left: 0, width: 360, height: 360, bottom: 360, right: 360 }; } } class CanvasElement extends Element { constructor() { super('CANVAS'); this.width = 300; this.height = 150; } getContext(type) { if (type.includes('webgl')) { return { getSupportedExtensions: () => [ 'ANGLE_instanced_arrays', 'EXT_blend_minmax', 'EXT_texture_filter_anisotropic', 'OES_element_index_uint', 'OES_standard_derivatives', 'OES_texture_float', 'OES_texture_half_float', 'OES_vertex_array_object', 'WEBGL_compressed_texture_s3tc', 'WEBGL_debug_renderer_info', 'WEBGL_depth_texture', 'WEBGL_lose_context' ], getExtension: (ext) => { if (ext === 'WEBGL_debug_renderer_info') { return { UNMASKED_VENDOR_WEBGL: 37445, UNMASKED_RENDERER_WEBGL: 37446 }; } return null; }, getParameter: (p) => { if (p === 37445) return 'Apple'; if (p === 37446) return 'Apple M1'; if (p === 7936) return 'WebKit'; if (p === 7937) return 'WebKit WebGL'; return 0; }, createBuffer: () => ({}), bindBuffer: () => {}, bufferData: () => {}, createProgram: () => ({}), createShader: () => ({}), shaderSource: () => {}, compileShader: () => {}, attachShader: () => {}, linkProgram: () => {}, useProgram: () => {}, drawArrays: () => {}, viewport: () => {}, clearColor: () => {}, clear: () => {} }; } return { fillRect: () => {}, fillText: () => {}, getImageData: () => ({ data: new Uint8ClampedArray(4) }), measureText: (txt) => ({ width: txt.length * 10 }), beginPath: () => {}, arc: () => {}, fill: () => {}, stroke: () => {} }; } toDataURL() { return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='; } } /** * Generate human-like mouse movement trajectory using cubic Bézier curves and Gaussian tremor. */ function generateBezierTrajectory(p0, p1, steps = 25) { // Control points with natural curve deviation const dx = p1.x - p0.x; const dy = p1.y - p0.y; const dist = Math.hypot(dx, dy); // Lateral deviation perpendicular to direct vector const perpX = -dy / (dist || 1); const perpY = dx / (dist || 1); const curvature = (Math.random() - 0.5) * Math.min(60, dist * 0.4); const cp1 = { x: p0.x + dx * 0.3 + perpX * curvature + (Math.random() - 0.5) * 10, y: p0.y + dy * 0.3 + perpY * curvature + (Math.random() - 0.5) * 10, }; const cp2 = { x: p0.x + dx * 0.7 + perpX * curvature * 0.6 + (Math.random() - 0.5) * 10, y: p0.y + dy * 0.7 + perpY * curvature * 0.6 + (Math.random() - 0.5) * 10, }; const points = []; for (let i = 0; i <= steps; i++) { const t = i / steps; // Ease in-out parameterization const easeT = t * t * (3 - 2 * t); const invT = 1 - easeT; const bx = invT * invT * invT * p0.x + 3 * invT * invT * easeT * cp1.x + 3 * invT * easeT * easeT * cp2.x + easeT * easeT * easeT * p1.x; const by = invT * invT * invT * p0.y + 3 * invT * invT * easeT * cp1.y + 3 * invT * easeT * easeT * cp2.y + easeT * easeT * easeT * p1.y; // Subtle tremor (except start and end) const tremor = (i === 0 || i === steps) ? 0 : (Math.random() - 0.5) * 1.5; points.push({ x: Math.round(bx + tremor), y: Math.round(by + tremor) }); } return points; } function runTdcCode(rawCode, clickPoints) { const listeners = []; function addL(type, fn) { listeners.push({ type, fn }); } const docBody = new Element('BODY'); const docElement = new Element('HTML'); const baseWindow = { location: { href: 'https://turing.captcha.qcloud.com/cap_union_new_show?aid=2039519451', protocol: 'https:', host: 'turing.captcha.qcloud.com', hostname: 'turing.captcha.qcloud.com', pathname: '/cap_union_new_show', search: '?aid=2039519451', hash: '' }, document: { createElement: (tag) => { if ((tag || '').toLowerCase() === 'canvas') return new CanvasElement(); return new Element(tag); }, addEventListener: addL, removeEventListener: () => {}, body: docBody, documentElement: docElement, referrer: 'https://app.rainyun.com/', cookie: '', getElementsByTagName: (t) => [t === 'body' ? docBody : docElement], getElementById: () => null, title: '安全验证', head: new Element('HEAD') }, navigator: { userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36', platform: 'MacIntel', languages: ['zh-CN', 'zh', 'en'], language: 'zh-CN', cookieEnabled: true, plugins: [{ name: 'PDF Viewer' }], mimeTypes: [] }, screen: { width: 1920, height: 1080, availWidth: 1920, availHeight: 1055, colorDepth: 24, pixelDepth: 24 }, performance: { now: () => Date.now(), timing: { navigationStart: Date.now() - 2500, loadEventEnd: Date.now() - 800 } }, history: { length: 2 }, indexedDB: {}, sessionStorage: { getItem: () => null, setItem: () => {} }, localStorage: { getItem: () => null, setItem: () => {} }, Date: Date, Math: Math, setTimeout: setTimeout, clearTimeout: clearTimeout, setInterval: setInterval, clearInterval: clearInterval, console: { log: () => {}, error: () => {}, warn: () => {} }, innerWidth: 360, innerHeight: 360, outerWidth: 1920, outerHeight: 1080, devicePixelRatio: 2, addEventListener: addL, removeEventListener: () => {} }; baseWindow.window = baseWindow; baseWindow.self = baseWindow; baseWindow.top = baseWindow; baseWindow.parent = baseWindow; // Execute TDC.js vm.runInNewContext(rawCode, baseWindow); // Map 672x480 coordinates to 360x360 iframe display space const scale = 320.0 / 672.0; const targetPoints = clickPoints.map(p => ({ x: Math.round(20 + p[0] * scale), y: Math.round(60 + p[1] * scale) })); // Generate path: entrance -> P1 -> P2 -> P3 let currentPoint = { x: Math.round(150 + (Math.random() - 0.5) * 60), y: Math.round(320 + (Math.random() - 0.5) * 20) }; const mmListener = listeners.find(l => l.type === 'mousemove'); let currentTime = Date.now() - 1500; if (mmListener) { for (const target of targetPoints) { // Leg trajectory const steps = Math.floor(18 + Math.random() * 12); const leg = generateBezierTrajectory(currentPoint, target, steps); for (const pt of leg) { currentTime += Math.floor(12 + Math.random() * 16); mmListener.fn({ clientX: pt.x, clientY: pt.y, pageX: pt.x, pageY: pt.y, screenX: pt.x + 300, screenY: pt.y + 200, timeStamp: currentTime, type: 'mousemove', isTrusted: true }); } currentPoint = target; // Dwell pause on icon (150ms ~ 350ms) const dwellSteps = Math.floor(4 + Math.random() * 5); for (let i = 0; i < dwellSteps; i++) { currentTime += Math.floor(25 + Math.random() * 30); mmListener.fn({ clientX: currentPoint.x + Math.round((Math.random() - 0.5) * 2), clientY: currentPoint.y + Math.round((Math.random() - 0.5) * 2), pageX: currentPoint.x, pageY: currentPoint.y, screenX: currentPoint.x + 300, screenY: currentPoint.y + 200, timeStamp: currentTime, type: 'mousemove', isTrusted: true }); } } } const collect = baseWindow.TDC.getData(); const info = baseWindow.TDC.getInfo(); const eks = info ? info.info : ''; return { collect, tlg: collect ? collect.length : 0, eks }; } function runTdc(tdcFilePath, clickPoints) { const rawCode = fs.readFileSync(tdcFilePath, 'utf8'); return runTdcCode(rawCode, clickPoints); } if (require.main === module) { const tdcPath = process.argv[2]; const pointsArg = process.argv[3]; const points = pointsArg ? JSON.parse(pointsArg) : [[626, 253], [120, 120], [438, 33]]; if (tdcPath === '-') { let input = ''; process.stdin.setEncoding('utf8'); process.stdin.on('data', chunk => { input += chunk; }); process.stdin.on('end', () => { const result = runTdcCode(input, points); console.log(JSON.stringify(result)); }); } else { const result = runTdc(tdcPath, points); console.log(JSON.stringify(result)); } } module.exports = { runTdc, runTdcCode };