// ============================================================================
// X讯滑块验证码 (X-Sec) 纯算解密脚本
// ============================================================================
// 功能特性:
// 1. 实现 TEA (微型加密算法),使用提取的静态密钥。
// 2. 生成 'collect' 指纹 (模拟/随机化数据)。
// 3. 生成符合流程的有效 'ans' 轨迹参数。
// 4. 零依赖 (仅需原生 Node.js)。
// ============================================================================
// --- 配置区域 ---
// 从 WASM/VMP 虚拟机中提取的静态加密密钥
const TEA_KEY = [1411332183, 1699234621, 1733643088, 1466843985];
const TEA_DELTA = 0x9E3779B9;
// --- 加密核心 (Crypto Core) ---
/**
* 标准 TEA 加密轮次
* 加密一个 64-bit 数据块 (2个 32-bit 整数)
*/
function encryptBlock(v, k) {
let v0 = v[0];
let v1 = v[1];
let sum = 0;
// 进行 32 轮加密
for (let i = 0; i < 32; i++) {
sum = (sum + TEA_DELTA) | 0;
v0 = (v0 + ((((v1 << 4) + k[0]) ^ (v1 + sum)) ^ ((v1 >>> 5) + k[1]))) | 0;
v1 = (v1 + ((((v0 << 4) + k[2]) ^ (v0 + sum)) ^ ((v0 >>> 5) + k[3]))) | 0;
}
return [v0, v1];
}
/**
* 使用 TEA 算法加密字符串负载
*/
function encryptData(strVal) {
const nums = stringToInts(strVal);
const encrypted = [];
for (let i = 0; i < nums.length; i += 2) {
let block = [nums[i], nums[i + 1] || 0];
block = encryptBlock(block, TEA_KEY);
encrypted.push(block[0], block[1]);
}
return intsToBase64(encrypted);
}
// 辅助函数:字符串转 Int32 数组
function stringToInts(str) {
const len = str.length;
const res = [];
for (let i = 0; i < len; i += 4) {
res.push(
(str.charCodeAt(i) | 0) |
((str.charCodeAt(i + 1) | 0) << 8) |
((str.charCodeAt(i + 2) | 0) << 16) |
((str.charCodeAt(i + 3) | 0) << 24)
);
}
return res;
}
// 辅助函数:Int32 数组转 Base64
function intsToBase64(ints) {
const bytes = [];
for (let n of ints) {
bytes.push(n & 0xFF, (n >> 8) & 0xFF, (n >> 16) & 0xFF, (n >>> 24) & 0xFF);
}
return Buffer.from(bytes).toString('base64');
}
// --- Payload 生成器 ---
/**
* 生成 'collect' 指纹数据。
* 包含增强的随机化逻辑,以模拟多样的真实用户环境。
*/
function getMockCollect() {
// 1. 随机 Chrome 版本 (115-125)
const ver = Math.floor(Math.random() * 11) + 115;
// 2. 随机操作系统 (Win10 vs Win11)
const osVer = Math.random() > 0.5 ? "10.0" : "11.0";
// 3. 构造 User-Agent 字符串
const ua = `Mozilla/5.0 (Windows NT ${osVer}; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${ver}.0.0.0 Safari/537.36`;
// 4. 随机屏幕分辨率 & 视口
const resolutions = [
{ w: 1920, h: 1080 }, { w: 2560, h: 1440 }, { w: 1366, h: 768 },
{ w: 1536, h: 864 }, { w: 1440, h: 900 }
];
const res = resolutions[Math.floor(Math.random() * resolutions.length)];
const scrStr = `${res.w}x${res.h}`;
// 5. 随机语言 & 时区
// 混合中文 (CN) 和英文 (EN) 用户环境
const isZh = Math.random() > 0.3; // 70% 概率为中文环境
const lang = isZh ? "zh-CN" : "en-US";
const tz = isZh ? -480 : Math.random() > 0.5 ? -300 : -420; // 北京时间 或 美国时间
// 6. 随机插件列表 (乱序且数量随机)
const pluginPool = [
"PDF Viewer", "Chrome PDF Viewer", "Chromium PDF Viewer",
"Microsoft Edge PDF Viewer", "WebKit built-in PDF",
"Native Client", "Widevine Content Decryption Module",
"Tencent SSO Platform", "NPAQQMail"
];
// 使用 Durstenfeld shuffle 算法进行更彻底的乱序
for (let i = pluginPool.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[pluginPool[i], pluginPool[j]] = [pluginPool[j], pluginPool[i]];
}
// 随机截取: 每次随机展示 3 到 7 个插件
const count = Math.floor(Math.random() * (pluginPool.length - 2)) + 3;
const plugins = pluginPool.slice(0, count).join(",");
// 7. 随机填充 (Nonce)
// 产生随机长度的填充,确保每次加密后的 tlg (长度) 都不同
const padding = "0".repeat(Math.floor(Math.random() * 15));
return JSON.stringify({
"ua": ua,
"browser_language": lang,
"screen": scrStr,
"timezone_offset": tz,
"plugins": plugins,
"random": Math.random(),
"nonce": padding,
"timestamp": Date.now()
});
}
/**
* 生成 'ans' 轨迹参数
* 结构: 包含在 JSON 数组中的 "x,y" 字符串
*/
function generateAns(elemId, finalX, finalY) {
const dataStr = `${Math.floor(finalX)},${Math.floor(finalY)}`;
const dataItem = {
elem_id: elemId,
type: "DynAnswerType_POS",
data: dataStr
};
return JSON.stringify([dataItem]);
}
/**
* 构建完整的验证 Payload
* @param {string} sess - Session ID (会话ID)
* @param {number} x - 目标 X 坐标
* @param {number} y - 目标 Y 坐标
*/
function solve(sess, x, y) {
const collectRaw = getMockCollect();
const collectEncrypted = encryptData(collectRaw);
// Eks 通常来自 tdc.js 中的 window.XHlQDhinWFaEckbkJKGOAUPJaWCVOTam
// 在此版本中,它似乎是一个静态常量。
const eksMock = "0pX57BVR9X2RQxFwBskJY1DZjN3F0q5y2Hidn93ydVfZPZD095CnbBb+9Jt1iQLWGJ8nP0b3vmUO+1ojICc1H4m5ZwrimSv4HHHbdhbu6sh1FlBzJrbR7/DwdiO7eJFgpDw8l/U9E2pCKX4lwitzuETts6yMFtmLKvT5yWn1xqF4sRaFOrbFYF6/xWMqXRjPEVs+MHS0byG5XZUPMUyBGQ==";
const ans = generateAns("tcaptcha_iframe_dy", x, y);
return {
collect: collectEncrypted, // 加密后的指纹
tlg: collectEncrypted.length, // 指纹长度
eks: eksMock, // 静态令牌
sess: sess, // 会话ID
ans: ans // 轨迹/坐标数据
};
}
// --- 命令行执行 (CLI) ---
if (require.main === module) {
const args = process.argv.slice(2);
// 如果没有提供参数,则默认使用用户提供的 Session ID
const sess = args[0] || "s0bvTrwr3nyAvgT2NzbSASZuFao2hyM2TXBlurC_GGF7NleWDFS8g1N72hSYcq3pjjMP1LLGOzEX4cL6UYfa4wHcF9h73LktbkJxc-cG7ifb0n3h6ksV9tYlXco3n7Pz1zwdsFlzg0C2QTt6TgpQn7ccw-5haihcH8_I0kHJASesoAWB4xhJzCJIf2LEQrqKFkCo0bJvipWjIHX3SfGo5WKPxt6x1pPLjNmkTsJcAtyVskRYhhbpyDVPUhnYAQtNAdBpthh8drE7pirJ0Q2DjJ0xF82juuaANfS99QIkGxcavkhN4ncaIzvc69hXxLx0R6Ht47Wrr9c_PjeE7yGVsWVqwn-W1kV4lHqEBfQLOLmp-nHBhBWeC7d7q-s1xepWURGJGVuSvYCCvJIEUMo6GIPQBcC9KxUs0QArMW1ozRHt0*";
// 这里的 x, y 即为"滑块图片的正确坐标" (缺口位置)
const x = parseInt(args[1] || "150", 10);
const y = parseInt(args[2] || "56", 10);
const payload = solve(sess, x, y);
console.log(JSON.stringify(payload, null, 2));
}
module.exports = { solve, encryptData };