feat(captcha): 增加全题型图片落盘、字形一致性强校验与防秒换拟人交互机制
- 题目图片自动下载: 所有题型原图统一落盘至 scratch/captcha_debug,文字点选自动生成带编号红圈标注图便于肉眼核验 - 字形严格一致性校验: 在 SolveResult 增加 text_match_ok 检查,对图字不符或混淆干扰题直接拒答换题,避免错答引发 errorCode=12 - 防秒换与拟人延时: 遇到非预期题目换题等待 2.8~4.2s (对齐 HAR 真实抓包),求解失败等待 2.5~3.8s,作答前模拟交互 5.5~7.5s - 代理容错增强: 在 OneMsAdapter 捕获代理断流与鉴权异常,支持动态轮换新代理重试
This commit is contained in:
+160
-23
@@ -50,9 +50,42 @@ function createDeepProxy(target, prefix, logSet) {
|
||||
});
|
||||
}
|
||||
|
||||
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 = {}) {
|
||||
const tdcCode = fs.readFileSync(tdcFilePath, "utf-8");
|
||||
const tdcCode = options.tdcCode || fs.readFileSync(tdcFilePath, "utf-8");
|
||||
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 = {
|
||||
@@ -103,23 +136,32 @@ function probeTdc(tdcFilePath, options = {}) {
|
||||
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 (tag.toLowerCase() === "canvas") return fakeCanvas;
|
||||
if (String(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:",
|
||||
};
|
||||
Object.defineProperty(fakeDoc, "onmousemove", {
|
||||
set(fn) {
|
||||
mouseHandler = fn;
|
||||
},
|
||||
get() {
|
||||
return mouseHandler;
|
||||
},
|
||||
});
|
||||
|
||||
const baseWindow = {
|
||||
screen: realScreen,
|
||||
@@ -129,7 +171,7 @@ function probeTdc(tdcFilePath, options = {}) {
|
||||
innerWidth: 360,
|
||||
innerHeight: 360,
|
||||
devicePixelRatio: 2,
|
||||
Date: Date,
|
||||
Date: vClock.FakeDate,
|
||||
Math: Math,
|
||||
String: String,
|
||||
Number: Number,
|
||||
@@ -165,10 +207,75 @@ function probeTdc(tdcFilePath, options = {}) {
|
||||
// Execute in isolated V8 Context
|
||||
const sandbox = vm.createContext(proxiedWindow);
|
||||
|
||||
console.log(`Running TDC inside Semantic Probe...`);
|
||||
if (options.verbose) console.log(`Running TDC inside Semantic Probe...`);
|
||||
vm.runInContext(tdcCode, sandbox);
|
||||
|
||||
console.log(`TDC loaded. Calling TDC.getData(!0)...`);
|
||||
// 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) {
|
||||
@@ -194,21 +301,51 @@ function probeTdc(tdcFilePath, options = {}) {
|
||||
}
|
||||
|
||||
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}`);
|
||||
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));
|
||||
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}`);
|
||||
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 };
|
||||
|
||||
Reference in New Issue
Block a user