feat(tcaptcha): 建立AST解包、TDC虚拟机反汇编与深度Proxy语义探针工具链
- 解包 tcaptcha-frame bundle,圈定 122 个模块中的核心加签与通信模块 - 还原 __TENCENT_CHAOS_VM 架构:映射全部 70 个操作码语义并实现字符串折叠反汇编 - 提取 246 个核心字符串,全量排查并枚举 95 项环境检测与自动化探针特征 - 实现零浏览器依赖的深度 Proxy 探针,原生生成与会话自洽的 collect 与 eks
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* 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 probeTdc(tdcFilePath, options = {}) {
|
||||
const tdcCode = fs.readFileSync(tdcFilePath, "utf-8");
|
||||
const accesses = new Set();
|
||||
|
||||
// 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 fakeDoc = {
|
||||
referrer: options.referrer || "https://1ms.run/console/personal/benefits/check-in",
|
||||
characterSet: "UTF-8",
|
||||
documentElement: { clientWidth: 360, clientHeight: 360 },
|
||||
createElement: (tag) => {
|
||||
if (tag.toLowerCase() === "canvas") return fakeCanvas;
|
||||
return { appendChild: () => {}, style: {}, setAttribute: () => {} };
|
||||
},
|
||||
getElementById: () => null,
|
||||
cookie: "",
|
||||
};
|
||||
|
||||
const fakeLoc = {
|
||||
href: "https://turing.captcha.gtimg.com/1/template/drag_ele.html?rand=1519713624347",
|
||||
hostname: "turing.captcha.gtimg.com",
|
||||
protocol: "https:",
|
||||
};
|
||||
|
||||
const baseWindow = {
|
||||
screen: realScreen,
|
||||
navigator: realNavigator,
|
||||
document: fakeDoc,
|
||||
location: fakeLoc,
|
||||
innerWidth: 360,
|
||||
innerHeight: 360,
|
||||
devicePixelRatio: 2,
|
||||
Date: Date,
|
||||
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);
|
||||
|
||||
console.log(`Running TDC inside Semantic Probe...`);
|
||||
vm.runInContext(tdcCode, sandbox);
|
||||
|
||||
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(),
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
const tdcFile = process.argv[2] || "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 };
|
||||
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* Tencent Turing Captcha Chaos VM (TDC VM) Disassembler
|
||||
* --------------------------------------------------------
|
||||
* 依据 AGENTS.md §3.3 规范,将 TDC 混淆字节码反汇编为可读指令流与结构化操作。
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const parser = require("@babel/parser");
|
||||
const generate = require("@babel/generator").default;
|
||||
|
||||
const OP_NAMES = {
|
||||
0: "PUSH_STR_CHAR",
|
||||
1: "IS_EXCEPTION",
|
||||
2: "DUP",
|
||||
3: "XOR",
|
||||
4: "EQ_STRICT",
|
||||
5: "MOD",
|
||||
6: "CALL_FUNC",
|
||||
8: "GET_GLOBAL_PROP",
|
||||
9: "MAKE_PROP_REF",
|
||||
10: "TYPEOF",
|
||||
11: "DELETE",
|
||||
12: "LOGICAL_NOT",
|
||||
13: "EQ_LOOSE",
|
||||
14: "SET_PROP",
|
||||
15: "GREATER_THAN",
|
||||
17: "SWAP",
|
||||
18: "IN",
|
||||
19: "RET_TRUE",
|
||||
20: "GREATER_EQ",
|
||||
22: "PUSH_IMM",
|
||||
23: "MUL",
|
||||
25: "LOAD_REG_INDIRECT",
|
||||
26: "ITER_NEXT",
|
||||
28: "BIND_THIS",
|
||||
29: "PUSH_NULL",
|
||||
30: "BITWISE_AND",
|
||||
31: "PUSH_IMM_ARRAY",
|
||||
32: "MAKE_REG_PROP_REF",
|
||||
33: "SUB",
|
||||
34: "CALL_METHOD",
|
||||
35: "SHR",
|
||||
36: "SHL",
|
||||
37: "JUMP_IF_TRUE",
|
||||
38: "FOR_IN_KEYS",
|
||||
39: "ADD",
|
||||
41: "NEW_METHOD",
|
||||
42: "PUSH_UNDEFINED",
|
||||
45: "PUSH_FALSE",
|
||||
46: "STORE_REG",
|
||||
47: "ALLOC_REG",
|
||||
48: "POP_TRY",
|
||||
49: "THROW",
|
||||
51: "POP",
|
||||
52: "DIV",
|
||||
53: "SET_STACK_LEN",
|
||||
54: "REVERSE_TOP2",
|
||||
55: "PUSH_EMPTY_STR",
|
||||
56: "PUSH_TRY",
|
||||
57: "CREATE_CLOSURE",
|
||||
59: "GET_PROP",
|
||||
60: "JUMP",
|
||||
61: "NEW_FUNC",
|
||||
62: "BITWISE_OR",
|
||||
64: "REPLACE_TOP_IMM",
|
||||
65: "SHR_UNSIGNED",
|
||||
66: "PUSH_TRUE",
|
||||
68: "LOAD_REG_IMM",
|
||||
69: "CLEAR_EXCEPTION",
|
||||
};
|
||||
|
||||
function extractBytecode(tdcPath) {
|
||||
const code = fs.readFileSync(tdcPath, "utf-8");
|
||||
const ast = parser.parse(code, { sourceType: "script" });
|
||||
const fn = ast.program.body[3].declarations[0].init.callee;
|
||||
const iFuncCode = generate(fn.body.body[1]).code;
|
||||
const returnStmt = fn.body.body[2];
|
||||
const vmCall = returnStmt.argument.expressions ? returnStmt.argument.expressions[1] : returnStmt.argument;
|
||||
const zCall = vmCall.arguments[1];
|
||||
const zCallCode = generate(zCall).code;
|
||||
|
||||
const runner = `
|
||||
${iFuncCode}
|
||||
(${zCallCode});
|
||||
`;
|
||||
return eval(runner);
|
||||
}
|
||||
|
||||
function disassemble(z) {
|
||||
let H = 0;
|
||||
const rawInstructions = [];
|
||||
const total = z.length;
|
||||
|
||||
while (H < total) {
|
||||
const pc = H;
|
||||
const op = z[H++];
|
||||
const opName = OP_NAMES[op] || `UNKNOWN_${op}`;
|
||||
const args = [];
|
||||
|
||||
switch (op) {
|
||||
case 0: // PUSH_STR_CHAR
|
||||
case 22: // PUSH_IMM
|
||||
case 31: // PUSH_IMM_ARRAY
|
||||
case 47: // ALLOC_REG
|
||||
case 53: // SET_STACK_LEN
|
||||
case 64: // REPLACE_TOP_IMM
|
||||
case 68: // LOAD_REG_IMM
|
||||
args.push(z[H++]);
|
||||
break;
|
||||
case 6: // CALL_FUNC
|
||||
case 17: // SWAP
|
||||
case 34: // CALL_METHOD
|
||||
case 41: // NEW_METHOD
|
||||
case 61: // NEW_FUNC
|
||||
args.push(`args=${z[H++]}`);
|
||||
break;
|
||||
case 37: // JUMP_IF_TRUE
|
||||
case 60: // JUMP
|
||||
args.push(`target=@${z[H++]}`);
|
||||
break;
|
||||
case 56: // PUSH_TRY
|
||||
args.push(`catchTarget=@${z[H++]}`);
|
||||
args.push(`catchVarReg=${z[H++]}`);
|
||||
break;
|
||||
case 57: { // CREATE_CLOSURE
|
||||
const targetPc = z[H++];
|
||||
const capCount = z[H++];
|
||||
const paramCount = z[H++];
|
||||
const captures = [];
|
||||
for (let m = 0; m < capCount; m++) {
|
||||
captures.push(`r${z[H++]}=r${z[H++]}`);
|
||||
}
|
||||
const params = [];
|
||||
for (let m = 0; m < paramCount; m++) {
|
||||
params.push(`p${m}->r${z[H++]}`);
|
||||
}
|
||||
args.push(`target=@${targetPc}`);
|
||||
args.push(`caps=[${captures.join(",")}]`);
|
||||
args.push(`params=[${params.join(",")}]`);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// no extra operand
|
||||
break;
|
||||
}
|
||||
|
||||
rawInstructions.push({ pc, op, opName, args });
|
||||
}
|
||||
|
||||
// Pass 2: Fold PUSH_EMPTY_STR + PUSH_STR_CHAR ... into PUSH_STR "..."
|
||||
const folded = [];
|
||||
for (let i = 0; i < rawInstructions.length; i++) {
|
||||
const ins = rawInstructions[i];
|
||||
if (ins.opName === "PUSH_EMPTY_STR") {
|
||||
let str = "";
|
||||
let j = i + 1;
|
||||
while (j < rawInstructions.length && rawInstructions[j].opName === "PUSH_STR_CHAR") {
|
||||
str += String.fromCharCode(rawInstructions[j].args[0]);
|
||||
j++;
|
||||
}
|
||||
if (j > i + 1) {
|
||||
folded.push({
|
||||
pc: ins.pc,
|
||||
op: 0,
|
||||
opName: "PUSH_STR",
|
||||
args: [JSON.stringify(str)],
|
||||
});
|
||||
i = j - 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
folded.push(ins);
|
||||
}
|
||||
|
||||
return folded;
|
||||
}
|
||||
|
||||
// CLI usage
|
||||
if (require.main === module) {
|
||||
const tdcFile = process.argv[2] || "tcaptcha/core_js/tdc_sample.js";
|
||||
console.log(`Extracting and disassembling ${tdcFile}...`);
|
||||
const z = extractBytecode(tdcFile);
|
||||
console.log(`Bytecode size: ${z.length} bytes`);
|
||||
const insts = disassemble(z);
|
||||
console.log(`Total instructions: ${insts.length}`);
|
||||
|
||||
const outFile = tdcFile.replace(/\.js$/, ".disasm.txt");
|
||||
const lines = insts.map(
|
||||
(ins) => `@${ins.pc.toString().padStart(5, "0")}: ${ins.opName.padEnd(18)} ${ins.args.join(" ")}`
|
||||
);
|
||||
fs.writeFileSync(outFile, lines.join("\n"), "utf-8");
|
||||
console.log(`Disassembly written to ${outFile}`);
|
||||
|
||||
// Preview first 30 instructions
|
||||
console.log("\n--- Preview first 30 instructions ---");
|
||||
lines.slice(0, 30).forEach((l) => console.log(l));
|
||||
}
|
||||
|
||||
module.exports = { extractBytecode, disassemble, OP_NAMES };
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Webpack Bundle Unpacker & Module Extractor
|
||||
* -------------------------------------------
|
||||
* 依据 AGENTS.md §2.2 L1/L2 规范,将 Webpack 单文件打包解包为独立可读的模块文件。
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const parser = require("@babel/parser");
|
||||
const generate = require("@babel/generator").default;
|
||||
|
||||
function unpackBundle(bundlePath, outputDir) {
|
||||
const code = fs.readFileSync(bundlePath, "utf-8");
|
||||
console.log(`Parsing bundle ${bundlePath} (${code.length} bytes)...`);
|
||||
const ast = parser.parse(code, { sourceType: "script" });
|
||||
|
||||
const call = ast.program.body[0].expression.argument;
|
||||
const modulesArray = call.arguments[0].elements;
|
||||
console.log(`Found ${modulesArray.length} modules.`);
|
||||
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
|
||||
const index = [];
|
||||
|
||||
modulesArray.forEach((modNode, idx) => {
|
||||
if (!modNode) {
|
||||
index.push({ id: idx, empty: true });
|
||||
return;
|
||||
}
|
||||
const modCode = generate(modNode).code;
|
||||
const modFile = path.join(outputDir, `module_${idx.toString().padStart(3, "0")}.js`);
|
||||
fs.writeFileSync(modFile, modCode, "utf-8");
|
||||
|
||||
// Search keywords in module to create index
|
||||
const keywords = [];
|
||||
if (/verify/i.test(modCode)) keywords.push("verify");
|
||||
if (/pow/i.test(modCode)) keywords.push("pow");
|
||||
if (/tdc/i.test(modCode)) keywords.push("tdc");
|
||||
if (/collect/i.test(modCode)) keywords.push("collect");
|
||||
if (/getsig/i.test(modCode)) keywords.push("getsig");
|
||||
if (/prehandle/i.test(modCode)) keywords.push("prehandle");
|
||||
if (/subcapclass/i.test(modCode)) keywords.push("subcapclass");
|
||||
if (/ticket/i.test(modCode)) keywords.push("ticket");
|
||||
|
||||
index.push({
|
||||
id: idx,
|
||||
size: modCode.length,
|
||||
file: `module_${idx.toString().padStart(3, "0")}.js`,
|
||||
keywords,
|
||||
});
|
||||
});
|
||||
|
||||
const indexFile = path.join(outputDir, "manifest.json");
|
||||
fs.writeFileSync(indexFile, JSON.stringify(index, null, 2), "utf-8");
|
||||
console.log(`Manifest written to ${indexFile}`);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
const bundle = process.argv[2] || "tcaptcha/core_js/tcaptcha-frame.ba387dd1.js";
|
||||
const outDir = process.argv[3] || "tcaptcha/core_js/frame_modules";
|
||||
unpackBundle(bundle, outDir);
|
||||
}
|
||||
|
||||
module.exports = { unpackBundle };
|
||||
Reference in New Issue
Block a user