reverse-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok609hecl1pq6qi
2026-10-20 02:17:15 +03:00
commit da24c2bd20
2227 changed files with 443931 additions and 0 deletions

View File

@@ -0,0 +1,488 @@
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
function parseArgs(argv) {
const args = {
types: 'canvas,webgl,dom-geometry',
out: '',
apiPattern: '',
chunkSize: 3000,
deprecatedMaxDataUrlLength: null,
};
for (let i = 2; i < argv.length; i++) {
const a = argv[i];
if (a === '--types') args.types = argv[++i] || args.types;
else if (a === '--out') args.out = argv[++i] || '';
else if (a === '--api-pattern') args.apiPattern = argv[++i] || '';
else if (a === '--chunk-size') args.chunkSize = Number(argv[++i] || args.chunkSize);
else if (a === '--max-data-url-length') {
// 兼容旧参数名:历史版本用它裁剪 dataURL;当前版本严禁裁剪,只把它当作“分片大小”。
args.deprecatedMaxDataUrlLength = Number(argv[++i] || args.chunkSize);
args.chunkSize = args.deprecatedMaxDataUrlLength;
}
else if (a === '--help' || a === '-h') args.help = true;
else throw new Error(`未知参数:${a}`);
}
if (!Number.isFinite(args.chunkSize) || args.chunkSize < 512) args.chunkSize = 3000;
return args;
}
function usage() {
return `用法:
node scripts/generate_fingerprint_hook.js --types canvas,webgl,dom-geometry --out case/hooks/fingerprint-hook.js
node scripts/generate_fingerprint_hook.js --types canvas,webgl,webgpu,audio,dom-geometry --chunk-size 3000
说明:
生成浏览器侧指纹终端 API 采样 Hook。该脚本只用于前置取证,不得进入最终 result/ 交付目录。
长指纹值不会被裁剪为 4000 / 4096 字符;Canvas / WebGL / WebGPU / Audio 等大结果会完整保存或按 chunks 分片保存。
旧参数 --max-data-url-length 已废弃裁剪语义,仅兼容为分片大小,不能用于截断最终值。`;
}
function q(v) { return JSON.stringify(String(v)); }
function header(args) {
return `// 指纹终端 API 采样 Hook,仅用于授权前置取证。
// 使用方式:在用户已确认的取证工具中注入,触发最少量业务动作后执行:
// copy(JSON.stringify(await window.__WEB_JS_ENV_PATCHER_FINALIZE_FINGERPRINT__(), null, 2))
// 如果控制台不支持 await,可先执行:
// window.__WEB_JS_ENV_PATCHER_FINALIZE_FINGERPRINT__().then(v => copy(JSON.stringify(v, null, 2)))
// 注意:长指纹值必须完整保存或分片保存,不得裁剪为 4000 / 4096 字符,也不得把 Trace 可见片段当最终值。
// 不要把本 Hook 放入最终 result/ 交付目录。
(function installFingerprintSampler() {
if (window.__WEB_JS_ENV_PATCHER_FINGERPRINT_HOOKED__) return;
Object.defineProperty(window, "__WEB_JS_ENV_PATCHER_FINGERPRINT_HOOKED__", { value: true, configurable: true });
const chunkSize = ${Number(args.chunkSize) || 3000};
const apiPattern = ${q(args.apiPattern)};
const store = window.__WEB_JS_ENV_PATCHER_FINGERPRINT__ = window.__WEB_JS_ENV_PATCHER_FINGERPRINT__ || {
version: 2,
source: {
pageUrl: location.href,
userAgent: navigator.userAgent,
language: navigator.language,
languages: Array.from(navigator.languages || []),
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "",
capturedAt: new Date().toISOString(),
capturedBy: "真实浏览器指纹采样 Hook",
traceStatus: "browser-complete-sampled",
apiPattern
},
canvas: { toDataURL: [], toBlob: [], measureText: [], getImageData: [] },
webgl: { getParameter: [], getSupportedExtensions: null, getExtension: [], getShaderPrecisionFormat: [], readPixels: [] },
webgpu: { requestAdapter: [] },
audio: { startRendering: [], getChannelData: [] },
domGeometry: { getBoundingClientRect: [], getClientRects: [], offset: [] }
};
function stack() { try { return new Error().stack || ""; } catch(e) { return ""; } }
function chunkString(value, encoding) {
const text = String(value == null ? "" : value);
const chunks = [];
for (let i = 0; i < text.length; i += chunkSize) chunks.push(text.slice(i, i + chunkSize));
return {
encoding,
valueLength: text.length,
chunkSize,
chunks,
truncated: false,
sha256: "",
sha256Of: encoding === "base64-chunks" ? "base64-string" : "string",
hashStatus: "pending-finalize"
};
}
function b64FromArrayLike(arr) {
try {
const bytes = new Uint8Array(arr.buffer || arr, arr.byteOffset || 0, arr.byteLength || arr.length || 0);
let s = "";
const step = 0x8000;
for (let i = 0; i < bytes.length; i += step) {
const sub = bytes.subarray(i, Math.min(i + step, bytes.length));
s += String.fromCharCode.apply(null, sub);
}
return btoa(s);
} catch(e) { return ""; }
}
function chunkArrayLikeAsBase64(arr) {
const encoded = b64FromArrayLike(arr);
const out = chunkString(encoded, "base64-chunks");
try {
const bytes = new Uint8Array(arr.buffer || arr, arr.byteOffset || 0, arr.byteLength || arr.length || 0);
out.rawByteLength = bytes.byteLength;
} catch(e) {}
return out;
}
function clonePlain(value, depth) {
if (depth <= 0) return String(value);
if (value == null) return value;
if (typeof value === "string") return value.length >= 3900 ? chunkString(value, "string-chunks") : value;
if (typeof value === "number" || typeof value === "boolean") return value;
if (Array.isArray(value)) return value.map(v => clonePlain(v, depth - 1));
if (value instanceof Set) return Array.from(value).map(v => clonePlain(v, depth - 1));
if (value instanceof Map) return Array.from(value.entries()).map(([k, v]) => [clonePlain(k, depth - 1), clonePlain(v, depth - 1)]);
if (typeof value === "object") {
const out = {};
for (const key of Object.keys(value)) {
try { out[key] = clonePlain(value[key], depth - 1); } catch(e) { out[key] = "<read-error>"; }
}
return out;
}
return String(value);
}
function selectorOf(el) {
try {
if (!el) return "";
if (el.id) return "#" + el.id;
if (el.className && typeof el.className === "string") return "." + el.className.trim().split(/\\s+/)[0];
return String(el.tagName || "").toLowerCase();
} catch(e) { return ""; }
}
async function sha256Text(text) {
const input = new TextEncoder().encode(String(text));
const digest = await crypto.subtle.digest("SHA-256", input);
return Array.from(new Uint8Array(digest)).map(b => b.toString(16).padStart(2, "0")).join("");
}
async function fillChunkHashes(node) {
if (!node || typeof node !== "object") return;
if ((node.encoding === "string-chunks" || node.encoding === "base64-chunks") && Array.isArray(node.chunks)) {
const text = node.chunks.join("");
node.valueLength = text.length;
node.truncated = false;
try {
node.sha256 = await sha256Text(text);
node.hashStatus = "ok";
} catch(e) {
node.hashStatus = "failed:" + (e && e.message || String(e));
}
}
if (Array.isArray(node)) {
for (const item of node) await fillChunkHashes(item);
} else {
for (const key of Object.keys(node)) await fillChunkHashes(node[key]);
}
}
window.__WEB_JS_ENV_PATCHER_FINALIZE_FINGERPRINT__ = async function finalizeFingerprint() {
await fillChunkHashes(store);
store.source.finalizedAt = new Date().toISOString();
store.source.finalizeNote = "长指纹值已完整保存或分片保存,sha256 针对完整字符串或完整 base64 字符串计算,truncated 必须为 false。";
return store;
};
`;
}
function footer() {
return ` console.info("[web-js-env-patcher] 指纹采样 Hook 已安装。触发目标动作后执行:copy(JSON.stringify(await window.__WEB_JS_ENV_PATCHER_FINALIZE_FINGERPRINT__(), null, 2))");
})();`;
}
function canvasSnippet() {
return `
// ===== Canvas 终端 API 采样 =====
if (window.HTMLCanvasElement) {
const rawToDataURL = HTMLCanvasElement.prototype.toDataURL;
if (rawToDataURL && !rawToDataURL.__wjep_hooked__) {
HTMLCanvasElement.prototype.toDataURL = function patchedToDataURL(type, quality) {
const result = rawToDataURL.apply(this, arguments);
store.canvas.toDataURL.push({
match: { width: this.width, height: this.height, type: type || "image/png" },
result: chunkString(result, "string-chunks"),
source: { capturedBy: "真实浏览器 Hook", tool: "用户选择的取证浏览器", traceStatus: "browser-complete-sampled" },
stack: stack()
});
return result;
};
HTMLCanvasElement.prototype.toDataURL.__wjep_hooked__ = true;
}
const rawToBlob = HTMLCanvasElement.prototype.toBlob;
if (rawToBlob && !rawToBlob.__wjep_hooked__) {
HTMLCanvasElement.prototype.toBlob = function patchedToBlob(callback, type, quality) {
const width = this.width, height = this.height, callStack = stack();
return rawToBlob.call(this, function(blob) {
store.canvas.toBlob.push({
match: { width, height, type: type || (blob && blob.type) || "image/png" },
result: { type: blob && blob.type || type || "", size: blob && blob.size || 0 },
source: { capturedBy: "真实浏览器 Hook", tool: "用户选择的取证浏览器", traceStatus: "browser-complete-sampled" },
stack: callStack
});
return callback && callback.apply(this, arguments);
}, type, quality);
};
HTMLCanvasElement.prototype.toBlob.__wjep_hooked__ = true;
}
}
if (window.CanvasRenderingContext2D) {
const p = CanvasRenderingContext2D.prototype;
const rawMeasureText = p.measureText;
if (rawMeasureText && !rawMeasureText.__wjep_hooked__) {
p.measureText = function patchedMeasureText(text) {
const ret = rawMeasureText.apply(this, arguments);
store.canvas.measureText.push({
match: { text: String(text), font: this.font },
result: {
width: ret.width,
actualBoundingBoxLeft: ret.actualBoundingBoxLeft,
actualBoundingBoxRight: ret.actualBoundingBoxRight,
actualBoundingBoxAscent: ret.actualBoundingBoxAscent,
actualBoundingBoxDescent: ret.actualBoundingBoxDescent,
fontBoundingBoxAscent: ret.fontBoundingBoxAscent,
fontBoundingBoxDescent: ret.fontBoundingBoxDescent
},
source: { capturedBy: "真实浏览器 Hook", tool: "用户选择的取证浏览器", traceStatus: "browser-complete-sampled" },
stack: stack()
});
return ret;
};
p.measureText.__wjep_hooked__ = true;
}
const rawGetImageData = p.getImageData;
if (rawGetImageData && !rawGetImageData.__wjep_hooked__) {
p.getImageData = function patchedGetImageData(sx, sy, sw, sh) {
const ret = rawGetImageData.apply(this, arguments);
store.canvas.getImageData.push({
match: { sx, sy, sw, sh },
result: { width: ret.width, height: ret.height, dataBase64: chunkArrayLikeAsBase64(ret.data) },
source: { capturedBy: "真实浏览器 Hook", tool: "用户选择的取证浏览器", traceStatus: "browser-complete-sampled" },
stack: stack()
});
return ret;
};
p.getImageData.__wjep_hooked__ = true;
}
}
`;
}
function webglSnippet() {
return `
// ===== WebGL 终端 API 采样 =====
for (const Ctor of [window.WebGLRenderingContext, window.WebGL2RenderingContext].filter(Boolean)) {
const p = Ctor.prototype;
if (p.getParameter && !p.getParameter.__wjep_hooked__) {
const raw = p.getParameter;
p.getParameter = function patchedGetParameter(pname) {
const ret = raw.apply(this, arguments);
store.webgl.getParameter.push({
match: { pname },
result: clonePlain(ret, 3),
source: { capturedBy: "真实浏览器 Hook", tool: "用户选择的取证浏览器", traceStatus: "browser-complete-sampled" },
stack: stack()
});
return ret;
};
p.getParameter.__wjep_hooked__ = true;
}
if (p.getSupportedExtensions && !p.getSupportedExtensions.__wjep_hooked__) {
const raw = p.getSupportedExtensions;
p.getSupportedExtensions = function patchedGetSupportedExtensions() {
const ret = raw.apply(this, arguments);
store.webgl.getSupportedExtensions = {
result: Array.from(ret || []),
source: { capturedBy: "真实浏览器 Hook", tool: "用户选择的取证浏览器", traceStatus: "browser-complete-sampled" },
stack: stack()
};
return ret;
};
p.getSupportedExtensions.__wjep_hooked__ = true;
}
if (p.getExtension && !p.getExtension.__wjep_hooked__) {
const raw = p.getExtension;
p.getExtension = function patchedGetExtension(name) {
const ret = raw.apply(this, arguments);
store.webgl.getExtension.push({
match: { name: String(name) },
result: ret ? { exists: true, keys: Object.keys(ret).slice(0, 50) } : null,
source: { capturedBy: "真实浏览器 Hook", tool: "用户选择的取证浏览器", traceStatus: "browser-complete-sampled" },
stack: stack()
});
return ret;
};
p.getExtension.__wjep_hooked__ = true;
}
if (p.getShaderPrecisionFormat && !p.getShaderPrecisionFormat.__wjep_hooked__) {
const raw = p.getShaderPrecisionFormat;
p.getShaderPrecisionFormat = function patchedGetShaderPrecisionFormat(shaderType, precisionType) {
const ret = raw.apply(this, arguments);
store.webgl.getShaderPrecisionFormat.push({
match: { shaderType, precisionType },
result: ret ? { rangeMin: ret.rangeMin, rangeMax: ret.rangeMax, precision: ret.precision } : null,
source: { capturedBy: "真实浏览器 Hook", tool: "用户选择的取证浏览器", traceStatus: "browser-complete-sampled" },
stack: stack()
});
return ret;
};
p.getShaderPrecisionFormat.__wjep_hooked__ = true;
}
if (p.readPixels && !p.readPixels.__wjep_hooked__) {
const raw = p.readPixels;
p.readPixels = function patchedReadPixels(x, y, width, height, format, type, pixels) {
const ret = raw.apply(this, arguments);
store.webgl.readPixels.push({
match: { x, y, width, height, format, type },
result: { dataBase64: pixels ? chunkArrayLikeAsBase64(pixels) : chunkString("", "base64-chunks") },
source: { capturedBy: "真实浏览器 Hook", tool: "用户选择的取证浏览器", traceStatus: "browser-complete-sampled" },
stack: stack()
});
return ret;
};
p.readPixels.__wjep_hooked__ = true;
}
}
`;
}
function webgpuSnippet() {
return `
// ===== WebGPU 终端 API 采样 =====
if (navigator.gpu && navigator.gpu.requestAdapter && !navigator.gpu.requestAdapter.__wjep_hooked__) {
const rawRequestAdapter = navigator.gpu.requestAdapter.bind(navigator.gpu);
navigator.gpu.requestAdapter = async function patchedRequestAdapter(options) {
const adapter = await rawRequestAdapter(options);
const info = adapter && adapter.info ? clonePlain(adapter.info, 4) : null;
store.webgpu.requestAdapter.push({
match: { options: clonePlain(options || {}, 4) },
result: {
info,
features: adapter && adapter.features ? Array.from(adapter.features) : [],
limits: adapter && adapter.limits ? clonePlain(adapter.limits, 3) : {}
},
source: { capturedBy: "真实浏览器 Hook", tool: "用户选择的取证浏览器", traceStatus: "browser-complete-sampled" },
stack: stack()
});
return adapter;
};
navigator.gpu.requestAdapter.__wjep_hooked__ = true;
}
`;
}
function audioSnippet() {
return `
// ===== Audio 指纹终端 API 采样 =====
if (window.AudioBuffer && AudioBuffer.prototype.getChannelData && !AudioBuffer.prototype.getChannelData.__wjep_hooked__) {
const rawGetChannelData = AudioBuffer.prototype.getChannelData;
AudioBuffer.prototype.getChannelData = function patchedGetChannelData(channel) {
const ret = rawGetChannelData.apply(this, arguments);
try {
store.audio.getChannelData.push({
match: { channel, length: this.length, sampleRate: this.sampleRate, numberOfChannels: this.numberOfChannels },
result: { channelBase64: chunkArrayLikeAsBase64(ret) },
source: { capturedBy: "真实浏览器 Hook", tool: "用户选择的取证浏览器", traceStatus: "browser-complete-sampled" },
stack: stack()
});
} catch(e) {}
return ret;
};
AudioBuffer.prototype.getChannelData.__wjep_hooked__ = true;
}
if (window.OfflineAudioContext && OfflineAudioContext.prototype.startRendering && !OfflineAudioContext.prototype.startRendering.__wjep_hooked__) {
const rawStartRendering = OfflineAudioContext.prototype.startRendering;
OfflineAudioContext.prototype.startRendering = function patchedStartRendering() {
const callStack = stack();
const ret = rawStartRendering.apply(this, arguments);
if (ret && typeof ret.then === "function") {
return ret.then(buffer => {
try {
const data = buffer.getChannelData(0);
store.audio.startRendering.push({
match: { length: buffer.length, sampleRate: buffer.sampleRate, numberOfChannels: buffer.numberOfChannels },
result: { channel0Base64: chunkArrayLikeAsBase64(data) },
source: { capturedBy: "真实浏览器 Hook", tool: "用户选择的取证浏览器", traceStatus: "browser-complete-sampled" },
stack: callStack
});
} catch(e) {}
return buffer;
});
}
return ret;
};
OfflineAudioContext.prototype.startRendering.__wjep_hooked__ = true;
}
`;
}
function domSnippet() {
return `
// ===== DOM 几何 / 字体探测终端 API 采样 =====
if (window.Element) {
const p = Element.prototype;
const rawRect = p.getBoundingClientRect;
if (rawRect && !rawRect.__wjep_hooked__) {
p.getBoundingClientRect = function patchedGetBoundingClientRect() {
const ret = rawRect.apply(this, arguments);
store.domGeometry.getBoundingClientRect.push({
match: { selector: selectorOf(this) },
result: { x: ret.x, y: ret.y, width: ret.width, height: ret.height, top: ret.top, left: ret.left, right: ret.right, bottom: ret.bottom },
source: { capturedBy: "真实浏览器 Hook", tool: "用户选择的取证浏览器", traceStatus: "browser-complete-sampled" },
stack: stack()
});
return ret;
};
p.getBoundingClientRect.__wjep_hooked__ = true;
}
const rawRects = p.getClientRects;
if (rawRects && !rawRects.__wjep_hooked__) {
p.getClientRects = function patchedGetClientRects() {
const ret = rawRects.apply(this, arguments);
store.domGeometry.getClientRects.push({
match: { selector: selectorOf(this) },
result: Array.from(ret || []).map(r => ({ x: r.x, y: r.y, width: r.width, height: r.height, top: r.top, left: r.left, right: r.right, bottom: r.bottom })),
source: { capturedBy: "真实浏览器 Hook", tool: "用户选择的取证浏览器", traceStatus: "browser-complete-sampled" },
stack: stack()
});
return ret;
};
p.getClientRects.__wjep_hooked__ = true;
}
for (const key of ["offsetWidth", "offsetHeight", "scrollWidth", "scrollHeight", "clientWidth", "clientHeight"]) {
const desc = Object.getOwnPropertyDescriptor(HTMLElement.prototype, key) || Object.getOwnPropertyDescriptor(Element.prototype, key);
if (!desc || !desc.get || desc.get.__wjep_hooked__) continue;
Object.defineProperty(HTMLElement.prototype, key, {
get: function patchedGeometryGetter() {
const value = desc.get.call(this);
store.domGeometry.offset.push({
match: { selector: selectorOf(this) },
result: { [key]: value },
source: { capturedBy: "真实浏览器 Hook", tool: "用户选择的取证浏览器", traceStatus: "browser-complete-sampled" },
stack: stack()
});
return value;
},
enumerable: desc.enumerable,
configurable: true
});
try { Object.getOwnPropertyDescriptor(HTMLElement.prototype, key).get.__wjep_hooked__ = true; } catch(e) {}
}
}
`;
}
function build(args) {
const types = new Set(String(args.types || '').split(',').map(s => s.trim().toLowerCase()).filter(Boolean));
const parts = [header(args)];
if (types.has('canvas')) parts.push(canvasSnippet());
if (types.has('webgl')) parts.push(webglSnippet());
if (types.has('webgpu')) parts.push(webgpuSnippet());
if (types.has('audio')) parts.push(audioSnippet());
if (types.has('dom') || types.has('dom-geometry') || types.has('font') || types.has('fonts')) parts.push(domSnippet());
parts.push(footer());
return parts.join('\n');
}
try {
const args = parseArgs(process.argv);
if (args.help) { console.log(usage()); process.exit(0); }
const code = build(args);
if (args.out) {
const out = path.resolve(args.out);
fs.mkdirSync(path.dirname(out), { recursive: true });
fs.writeFileSync(out, code, 'utf8');
const compatNote = args.deprecatedMaxDataUrlLength ? '\n- 兼容提醒:--max-data-url-length 已按分片大小处理,没有裁剪任何值。' : '';
console.log(`# 指纹采样 Hook 已生成\n- 输出文件:${out}\n- 类型:${args.types}\n- 分片大小:${args.chunkSize}\n- 提醒:该 Hook 只用于前置取证,不得放入最终 result/ 目录;长指纹值必须 finalize 后完整分片保存。${compatNote}`);
} else {
process.stdout.write(code + '\n');
}
} catch (err) {
console.error(err.message || String(err));
console.error(usage());
process.exit(1);
}