Files
hello_js_reverse_skill/tools/tdc_keytrace.js
T
yml2213 5c949090c1 feat(vm): 神谕钩子 —— 在真 VM 中按 PC 快照寄存器 (定位/校验任意值)
做了什么:
- tools/tdc_probe.js 注入自包含钩子(window.__H): 记录 PC 轨迹, 并在指定 PC
  dump 指定寄存器 (默认 9109 / E[7]), 通过 report.__xtea / report.__pc_len 回传
- tools/tdc_keytrace.js 同步支持 TARGET_PC 钩子

为什么:
- 需要"某个值在哪段字节码诞生"的运行时神谕(AGENTS.md §3.2 第3条: 与运行时观测对齐)
- 钩子自包含写在沙箱 window 上(沙箱内没有 globalThis)

怎么验证(本轮的关键结论):
- 命中: @9109 入口 E[7] = [[1817655617,1381395306,1245924197,1767660346]];
  其后 PC 序列 tail = [...9021,9022,9023,9024,9026,9028,9030,9109]
  -> 说明 XTEA 闭包是在 @9030 CALL_FUNC[2] 被调用的
- 交叉验证: 用 extract_xtea_key 的密钥解密 probe 的 collect -> {"cd":[0,10,"top",...
  (真明文) => extract_xtea_key 正确;
  而 E[7] 解不开 => E[7] 是"基字", 不是生效密钥
- 结构确认: real[0]-cap[0]=855311, real[3]-cap[3]=723726 (XTEA 轮里的可见立即数)
  => 有效密钥 = 脚本基字 + 两个轮加盐
2026-10-11 22:21:53 +08:00

145 lines
6.7 KiB
JavaScript

#!/usr/bin/env node
/**
* 用**真 VM** 当神谕: 定位「XTEA 密钥数组」在哪段字节码被造出来。
*
* 思路(绕开"用 Python 复刻 JS 运行时"的泥潭):
* 给真 VM 的分发器打钩子, 每执行一条指令就扫一遍当前 E(寄存器区+栈),
* 一旦出现「4 个元素、且每个元素 4 字节都是可打印 ASCII 的 32bit 数」
* —— 那就是 XTEA 密钥数组(已确证形态) —— 立刻 dump 最近 N 条 PC。
* 那段 PC 窗口就是密钥例程, 之后手工提升成 Python 即可。
*
* 用法: node tools/tdc_keytrace.js <tdc.js> [--window=400]
*/
const fs = require("fs");
const file = process.argv[2] || "tcaptcha/core_js/tdc_sample.js";
const winArg = process.argv.find((a) => a.startsWith("--window="));
const WINDOW = winArg ? parseInt(winArg.split("=")[1], 10) : 400;
let src = fs.readFileSync(file, "utf8");
const needle = "M=G[z[H++]]()";
if (!src.includes(needle)) { console.error("[!] 未找到分发器"); process.exit(2); }
globalThis.__PC = [];
globalThis.__HITS = [];
// 目标: 密钥 4 个 ASCII 打包字对应的字符串 (由分析确定, 可用环境变量覆盖)
const KEY_ASCII = process.env.KEY_ASCII || "ldFPRVkjJCKeigfH";
const CHUNKS = [KEY_ASCII.slice(0, 4), KEY_ASCII.slice(4, 8), KEY_ASCII.slice(8, 12), KEY_ASCII.slice(12, 16)];
function printablePacked(v) {
if (!Number.isInteger(v)) return false;
const b = [(v >>> 24) & 255, (v >>> 16) & 255, (v >>> 8) & 255, v & 255];
return b.every((x) => x >= 32 && x < 127);
}
function isKeyArray(a) {
if (!a || typeof a !== "object" || typeof a.length !== "number" || a.length !== 4) return false;
for (let i = 0; i < 4; i++) if (!printablePacked(a[i])) return false;
return true;
}
function asciiOf(a) {
return a.map((x) => String.fromCharCode((x >>> 24) & 255, (x >>> 16) & 255, (x >>> 8) & 255, x & 255)).join("");
}
// 命中判定: 4-int 数组 / 含任意 key chunk 的字符串 / 恰好16字符的字符串
function detect(v, depth, seen) {
if (typeof v === "string") {
if (v.indexOf(KEY_ASCII) >= 0) return "str-full:" + v;
for (const c of CHUNKS) if (v.indexOf(c) >= 0) return "str-chunk:" + v;
return null;
}
if (v && typeof v === "object") {
if (depth > 3 || seen.has(v)) return null;
seen.add(v);
if (typeof v.length === "number" && isKeyArray(v)) return "arr4:" + asciiOf(v);
// 数组元素
if (typeof v.length === "number") {
for (let i = 0; i < v.length; i++) { const d = detect(v[i], depth + 1, seen); if (d) return "[" + i + "]." + d; }
}
// 对象属性 (含字符串键)
let ks = [];
try { ks = Object.keys(v); } catch (e) { ks = []; }
for (const k of ks) {
let x; try { x = v[k]; } catch (e) { continue; }
const d = detect(x, depth + 1, seen);
if (d) return "." + k + ":" + d;
}
}
return null;
}
globalThis.__SCAN = function (E) {
try {
for (let i = 0; i < E.length; i++) {
const cell = E[i];
if (!cell) continue;
// 寄存器单元可能是 [值], 也可能本身就是那个值; 两种都扫
const cands = [cell];
if (Array.isArray(cell)) { cands.push(cell[0]); if (Array.isArray(cell[0])) cands.push(cell[0]); }
let d = null, v = null;
for (const c of cands) { d = detect(c, 0, new Set()); if (d) { v = c; break; } }
if (d) {
globalThis.__HITS.push({
reg: i, how: d, value: typeof v === "string" ? v : (Array.isArray(v) ? Array.from(v) : String(v)),
pc: globalThis.__PC[globalThis.__PC.length - 1],
window: globalThis.__PC.slice(-WINDOW),
});
return;
}
}
} catch (e) { /* ignore */ }
};
const TARGET_PC = parseInt(process.env.TARGET_PC || "9109", 10);
globalThis.__XTEA = null;
globalThis.__HOOK = function (H, E) {
if (H === TARGET_PC && !globalThis.__XTEA) {
const cell = E[7];
globalThis.__XTEA = {
at: H,
r7_cell: (Array.isArray(cell) ? JSON.stringify(cell).slice(0, 400) : String(cell)),
r7_val: (cell && typeof cell[0] === "object") ? JSON.stringify(cell[0]).slice(0, 400) : String(cell && cell[0]),
prev_pcs: globalThis.__PC.slice(-300),
};
}
};
src = src.replace(needle, "M=(globalThis.__PC.push(H),globalThis.__HOOK(H,E),G[z[H++]]())");
globalThis.window = globalThis;
globalThis.document = { cookie: "", referrer: "", createElement: () => ({ getContext: () => null, toDataURL: () => "", style: {} }), getElementById: () => null };
globalThis.navigator = { userAgent: "Mozilla/5.0", platform: "MacIntel", languages: ["zh-CN"], language: "zh-CN", hardwareConcurrency: 10, cookieEnabled: true };
globalThis.screen = { width: 2560, height: 1440, availWidth: 2560, availHeight: 1345, colorDepth: 24, pixelDepth: 24 };
globalThis.location = { href: "https://turing.captcha.gtimg.com/1/template/drag_ele.html", hostname: "turing.captcha.gtimg.com", protocol: "https:" };
globalThis.localStorage = { getItem: () => null, setItem: () => {} };
globalThis.sessionStorage = { getItem: () => null, setItem: () => {} };
globalThis.requestAnimationFrame = (f) => setTimeout(f, 0);
globalThis.addEventListener = () => {}; globalThis.removeEventListener = () => {};
globalThis.__TENCENT_CHAOS_STACK = {};
let err = null;
try { eval(src); } catch (e) { err = String((e && e.stack) || e); }
// 顶层只"注册模块"; 真正算密钥要等宿主调用 -> 手动驱动
const names = Object.keys(globalThis).filter((k) => /^(TDC|TCaptcha|__|tcaptcha)/i.test(k));
process.stderr.write("[keytrace] globalThis 候选: " + JSON.stringify(names.slice(0, 20)) + "\n");
try {
if (globalThis.TDC && typeof globalThis.TDC.getData === "function") {
process.stderr.write("[keytrace] 调用 TDC.getData(true) 驱动...\n");
globalThis.__PC.push(-1);
const c = globalThis.TDC.getData(true);
globalThis.__COLLECT = c;
process.stderr.write(`[keytrace] getData 返回长度=${c ? c.length : 0} 头部=${String(c).slice(0,90)}\n`);
} else {
process.stderr.write("[keytrace] 未找到 TDC.getData\n");
}
} catch (e) { process.stderr.write("[keytrace] 驱动异常: " + String(e).slice(0, 200) + "\n"); }
process.stderr.write(`[keytrace] 执行 PC=${globalThis.__PC.length} 命中=${globalThis.__HITS.length}${err ? " 顶层抛错: " + err.slice(0, 120) : ""}\n`);
let out = globalThis.__HITS[0] || null;
if (out) {
process.stderr.write(`[keytrace] 命中寄存器 r${out.reg}, 密钥=${JSON.stringify(out.key)}, ASCII=${out.ascii}, 命中时 PC=@${out.pc}\n`);
}
const pcs=globalThis.__PC;
const uniq=new Set(pcs);
const inRange=[...uniq].filter(x=>x>=9000&&x<=9600);
process.stdout.write(JSON.stringify({ key_hit: out, xtea: globalThis.__XTEA,
pc_total: pcs.length, pc_unique: uniq.size, min: Math.min(...pcs), max: Math.max(...pcs),
has_9109: uniq.has(9109), range9000_9600: inRange.sort((a,b)=>a-b).slice(0,40) }));