Files
hello_js_reverse_skill/tcaptcha/tdc_worker.js
T

333 lines
11 KiB
JavaScript

/**
* 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 };