Files
hello_js_reverse_skill/tools/tdc_probe.js
T
yml2213 9286f77661 docs(账本)+feat(神谕): 完全还原 XTEA 密钥构造算法 (纯 Python 复算与 Node 逐字一致)
做了什么:
- tdc_probe.js 神谕增强: 逐字追踪"某值首次出现的 PC"(__WP), DUMP_PC 支持记录多次命中
- PROGRESS.md 已知表新增 14-18 条: 密钥字构造算法 / 盐折叠 / 纯Python复算一致 /
  运行时链路位置 / extract_xtea_key 正确性验证

为什么:
- 用"运行时神谕 + 反汇编阅读"替代逐条模拟(打地鼠), 定位并读懂密钥构造

怎么验证(硬结果):
- 密钥字算法(反汇编读出 + 运行时寄存器佐证):
    word = Σ ((charCodeAt(src,k) + off) & 0xFF) << (8k)
  四个 (源串, 偏移) = ("IA_t",-8) ("9:%!",+49) ("nTLS",-9) ("7XYf",+3)
- 运行时逐字首次出现: 7740/7526/7895/8852; 数组组装 @8886; 进XTEA @9030; 入口 @9109
- 纯 Python 复算 base=[1817655617,1381395306,1245924197,1767660346]
  + 折盐(855311->word0, 723726->word3)
  = [1818510928,1381395306,1245924197,1768384072] == extract_xtea_key  ✅
2026-10-11 22:27:29 +08:00

390 lines
13 KiB
JavaScript

/**
* Tencent Turing Captcha TDC Deep Proxy Runtime Probe
* ---------------------------------------------------
* 依据 AGENTS.md §4.3 与 §2.3 规范,通过深度 Proxy 探针拦截并记录 TDC 执行时
* 实际读取的全部环境属性清单与调用时序,彻底告别盲目补环境。
*/
const fs = require("fs");
const path = require("path");
const vm = require("vm");
function createDeepProxy(target, prefix, logSet) {
return new Proxy(target, {
get(obj, prop, receiver) {
if (typeof prop === "symbol" || prop === "inspect" || prop.startsWith("_")) {
return Reflect.get(obj, prop, receiver);
}
const BUILTIN_TYPES = new Set([
"String", "Number", "Boolean", "Array", "Object", "Function",
"RegExp", "Date", "Math", "JSON", "Error", "TypeError",
"Uint8Array", "Uint8ClampedArray", "parseInt", "parseFloat",
"encodeURIComponent", "decodeURIComponent", "setTimeout",
"clearTimeout", "setInterval", "clearInterval"
]);
if (BUILTIN_TYPES.has(String(prop))) {
return Reflect.get(obj, prop, receiver);
}
const fullName = `${prefix}.${String(prop)}`;
logSet.add(`GET: ${fullName}`);
const val = Reflect.get(obj, prop, receiver);
if (val !== null && typeof val === "object" && !(val instanceof RegExp)) {
return createDeepProxy(val, fullName, logSet);
}
if (typeof val === "function") {
const fnWrapper = function (...args) {
logSet.add(`CALL: ${fullName}(${args.map((a) => JSON.stringify(a)).join(", ")})`);
return val.apply(this === receiver ? obj : this, args);
};
fnWrapper.prototype = val.prototype;
return fnWrapper;
}
return val;
},
set(obj, prop, value, receiver) {
const fullName = `${prefix}.${String(prop)}`;
logSet.add(`SET: ${fullName}`);
return Reflect.set(obj, prop, value, receiver);
},
});
}
function createVirtualDate(initialTime) {
let currentTime = initialTime || (Date.now() - 3200);
const RealDate = Date;
function FakeDate(...args) {
if (!(this instanceof FakeDate)) {
return new RealDate(currentTime).toString();
}
if (args.length === 0) {
return new RealDate(currentTime);
}
return new RealDate(...args);
}
FakeDate.prototype = Object.create(RealDate.prototype);
FakeDate.now = () => currentTime;
FakeDate.parse = RealDate.parse;
FakeDate.UTC = RealDate.UTC;
return {
FakeDate,
advance: (ms) => {
currentTime += ms;
return currentTime;
},
now: () => currentTime,
};
}
function probeTdc(tdcFilePath, options = {}) {
let tdcCode = options.tdcCode || fs.readFileSync(tdcFilePath, "utf-8");
// ---- 神谕钩子(自包含, 写在沙箱内的 window 上): 记录 PC, 并在进入 XTEA 例程时 dump 密钥寄存器 ----
const TARGET_PC = parseInt(process.env.XTEA_PC || "9109", 10);
const BASE = (process.env.BASE_KEY || "1817655617,1381395306,1245924197,1767660346");
const HOOK = "window.__PC=window.__PC||[];window.__W=[" + BASE + "];"
+ "window.__H=function(H,E){window.__PC.push(H);"
+ "if(H<=" + TARGET_PC + "&&!window.__ARR){"
+ "for(var i=0;i<E.length;i++){var c=E[i];var v=(c&&c.length===1)?c[0]:c;"
+ "if(v&&v.length===4&&v[0]===window.__W[0]&&v[1]===window.__W[1]&&v[2]===window.__W[2]&&v[3]===window.__W[3])"
+ "{window.__ARR={at:H,reg:i,prev:window.__PC.slice(-120)};break;}}}"
+ "if(!window.__WP){window.__WP=[null,null,null,null];}"
+ "for(var q=0;q<4;q++){if(window.__WP[q])continue;"
+ "for(var i=0;i<E.length;i++){var c=E[i];var v=(c&&c.length===1)?c[0]:c;"
+ "if(typeof v==='number'&&v===window.__W[q]){window.__WP[q]={at:H,reg:i};break;}}}"
+ "if(H<=" + TARGET_PC + "&&!window.__WRD){var f=-1;"
+ "for(var i=0;i<E.length;i++){var c=E[i];var v=(c&&c.length===1)?c[0]:c;"
+ "if(typeof v==='number'&&v===window.__W[0]){f=i;break;}}"
+ "if(f>=0)window.__WRD={at:H,reg:f,prev:window.__PC.slice(-120)};}"
+ "};";
// 可选: 在指定 PC dump 全部寄存器 (神谕二分/定位用)
const DUMP_PC = parseInt(process.env.DUMP_PC || "-1", 10);
if (DUMP_PC >= 0) {
tdcCode = "window.__DPC=" + DUMP_PC + ";window.__DUMP=[];(function(){var _h=window.__H;"
+ "window.__H=function(H,E){_h(H,E);if(H===window.__DPC&&window.__DUMP.length<8){var o=[];"
+ "for(var i=0;i<E.length;i++){var c=E[i];var v=(c&&c.length===1)?c[0]:c;"
+ "o.push(typeof v==='string'?'str:'+v:(typeof v==='number'?'num:'+v:String(v)));}"
+ "window.__DUMP.push(o);}}})();" + tdcCode;
}
const NEEDLE = "M=G[z[H++]]()";
if (tdcCode.includes(NEEDLE)) {
tdcCode = HOOK + tdcCode.replace(NEEDLE, "M=(window.__H(H,E),G[z[H++]]())");
}
const accesses = new Set();
let mouseHandler = null;
// Initialize Virtual Clock (defaults to ~3.2s before real Date.now())
const vClock = createVirtualDate(options.startTime);
// Mock Realistic Environment (based on macOS Chrome success capture)
const realScreen = {
width: options.screenWidth || 2560,
height: options.screenHeight || 1440,
availWidth: options.screenWidth || 2560,
availHeight: options.screenAvailHeight || 1345,
colorDepth: 24,
pixelDepth: 24,
};
const realNavigator = {
userAgent:
options.userAgent ||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/155.0.0.0 Safari/537.36",
platform: "MacIntel",
languages: ["zh-CN", "zh", "fr", "de", "en"],
language: "zh-CN",
hardwareConcurrency: 10,
cookieEnabled: true,
};
const fakeCanvas = {
getContext: (type) => {
if (type.includes("webgl")) {
return {
getExtension: (name) => {
if (name === "WEBGL_debug_renderer_info") {
return { UNMASKED_VENDOR_WEBGL: 37445, UNMASKED_RENDERER_WEBGL: 37446 };
}
return null;
},
getParameter: (param) => {
if (param === 37445) return "Google Inc. (Apple)";
if (param === 37446) {
return "ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)";
}
return "";
},
getSupportedExtensions: () => ["WEBGL_debug_renderer_info"],
};
}
return {
getImageData: () => ({ data: new Uint8ClampedArray(16) }),
toDataURL: () => "data:image/png;base64,GgoAAAANSUhEUgAAASwAAACWCAYAAATf3v8bh+ryWcfSBSLkmN3N1PQinPL7Bi3TG0qE2CogAAAABJRU5ErkJggg==",
};
},
toDataURL: () => "data:image/png;base64,GgoAAAANSUhEUgAAASwAAACWCAYAAATf3v8bh+ryWcfSBSLkmN3N1PQinPL7Bi3TG0qE2CogAAAABJRU5ErkJggg==",
};
const fakeLoc = {
href: "https://turing.captcha.gtimg.com/1/template/drag_ele.html?rand=1519713624347",
hostname: "turing.captcha.gtimg.com",
protocol: "https:",
};
const fakeDoc = {
referrer: options.referrer || "https://1ms.run/console/personal/benefits/check-in",
characterSet: "UTF-8",
documentElement: { clientWidth: 360, clientHeight: 360 },
createElement: (tag) => {
if (String(tag).toLowerCase() === "canvas") return fakeCanvas;
return { appendChild: () => {}, style: {}, setAttribute: () => {} };
},
getElementById: () => null,
cookie: "",
};
Object.defineProperty(fakeDoc, "onmousemove", {
set(fn) {
mouseHandler = fn;
},
get() {
return mouseHandler;
},
});
const baseWindow = {
screen: realScreen,
navigator: realNavigator,
document: fakeDoc,
location: fakeLoc,
innerWidth: 360,
innerHeight: 360,
devicePixelRatio: 2,
Date: vClock.FakeDate,
Math: Math,
String: String,
Number: Number,
Boolean: Boolean,
Array: Array,
Object: Object,
Function: Function,
RegExp: RegExp,
JSON: JSON,
Error: Error,
TypeError: TypeError,
Uint8Array: Uint8Array,
Uint8ClampedArray: Uint8ClampedArray,
parseInt: parseInt,
parseFloat: parseFloat,
encodeURIComponent: encodeURIComponent,
decodeURIComponent: decodeURIComponent,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setInterval: setInterval,
clearInterval: clearInterval,
addEventListener: () => {},
removeEventListener: () => {},
};
// Attach window.top to simulate running in an iframe
baseWindow.top = {};
baseWindow.window = baseWindow;
baseWindow.self = baseWindow;
const proxiedWindow = createDeepProxy(baseWindow, "window", accesses);
// Execute in isolated V8 Context
const sandbox = vm.createContext(proxiedWindow);
if (options.verbose) console.log(`Running TDC inside Semantic Probe...`);
vm.runInContext(tdcCode, sandbox);
// Dispatch simulated human trajectory if clickPoints provided
const clickPoints = options.clickPoints || options.trajectory || [];
if (mouseHandler && clickPoints.length > 0) {
const scale = 320.0 / 672.0;
// 1. Initial human observation hesitation (400 ~ 650ms)
vClock.advance(Math.floor(420 + Math.random() * 200));
// Starting mouse position (natural bottom-ish area: x~140, y~300)
let curX = Math.round(130 + (Math.random() - 0.5) * 30);
let curY = Math.round(290 + (Math.random() - 0.5) * 20);
for (let pIdx = 0; pIdx < clickPoints.length; pIdx++) {
const pt = clickPoints[pIdx];
// Target coordinates in iframe frame (left offset ~20, top ~60)
const targetX = Math.round(20 + pt[0] * scale + (Math.random() - 0.5) * 4);
const targetY = Math.round(60 + pt[1] * scale + (Math.random() - 0.5) * 4);
const startStepX = curX;
const startStepY = curY;
// 15 ~ 22 movement steps per target point
const steps = Math.floor(15 + Math.random() * 6);
for (let s = 1; s <= steps; s++) {
const t = s / steps;
// Human kinematics: Ease-in-out cubic curve
const ease = t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
const jitterX = s < steps ? (Math.random() - 0.5) * 1.6 : 0;
const jitterY = s < steps ? (Math.random() - 0.5) * 1.6 : 0;
curX = Math.round(startStepX + (targetX - startStepX) * ease + jitterX);
curY = Math.round(startStepY + (targetY - startStepY) * ease + jitterY);
// Advance 16 ~ 18ms per frame (60Hz vsync sampling)
vClock.advance(16 + (Math.random() < 0.25 ? 1 : 0));
mouseHandler({
type: "mousemove",
clientX: curX,
clientY: curY,
pageX: curX,
pageY: curY,
offsetX: curX,
offsetY: curY,
});
}
// Pause at target (click dwell time: 90 ~ 160ms)
const dwellMs = Math.floor(90 + Math.random() * 70);
vClock.advance(dwellMs);
mouseHandler({
type: "mousemove",
clientX: curX,
clientY: curY,
pageX: curX,
pageY: curY,
offsetX: curX,
offsetY: curY,
});
}
// Post-click think & submit delay (200 ~ 350ms)
vClock.advance(Math.floor(220 + Math.random() * 120));
}
if (options.verbose) console.log(`TDC loaded. Calling TDC.getData(!0)...`);
let collect = null;
let eks = null;
if (sandbox.TDC && sandbox.TDC.getData) {
collect = sandbox.TDC.getData(true);
if (sandbox.TDC.getInfo) {
const info = sandbox.TDC.getInfo();
eks = info ? info.info : null;
}
}
const result = {
success: !!collect,
collect: collect,
eks: eks,
collectLen: collect ? collect.length : 0,
collectPreview: collect ? collect.slice(0, 80) : null,
eksPreview: eks ? eks.slice(0, 80) : null,
accessCount: accesses.size,
accesses: Array.from(accesses).sort(),
__pc_len: (sandbox.__PC || []).length,
__xtea: sandbox.__XT || null,
__arr: sandbox.__ARR || null,
__wrd: sandbox.__WRD || null,
__dump: sandbox.__DUMP || null,
__wp: sandbox.__WP || null,
};
return result;
}
if (require.main === module) {
const arg2 = process.argv[2];
if (arg2 === "-") {
// Pipeline mode for Python / external callers
let tdcCode = "";
process.stdin.setEncoding("utf-8");
process.stdin.on("data", (chunk) => {
tdcCode += chunk;
});
process.stdin.on("end", () => {
try {
const opts = process.argv[3] ? JSON.parse(process.argv[3]) : {};
// Temporary write to memory / execute directly
const report = probeTdc(null, { ...opts, tdcCode });
process.stdout.write(
JSON.stringify({
success: report.success,
collect: report.collect,
tlg: String(report.collectLen),
eks: report.eks,
source: "probe",
}) + "\n"
);
} catch (err) {
process.stdout.write(
JSON.stringify({ success: false, error: String(err) }) + "\n"
);
}
});
} else {
const tdcFile = arg2 || "tcaptcha/core_js/tdc_sample.js";
const report = probeTdc(tdcFile);
console.log("\n--- TDC Execution Result ---");
console.log(`Success: ${report.success}`);
console.log(`Collect Length: ${report.collectLen}`);
console.log(`Collect Preview: ${report.collectPreview}`);
console.log(`EKS Preview: ${report.eksPreview}`);
console.log(`Total Environment Accesses: ${report.accessCount}`);
console.log("\n--- Top 35 Environment Accesses Logged by Probe ---");
report.accesses.slice(0, 35).forEach((a) => console.log(a));
const outReport = tdcFile.replace(/\.js$/, ".probe_report.json");
fs.writeFileSync(outReport, JSON.stringify(report, null, 2), "utf-8");
console.log(`\nDetailed probe report written to ${outReport}`);
}
}
module.exports = { probeTdc };