Tätä skriptiä ei tulisi asentaa suoraan. Se on kirjasto muita skriptejä varten sisällytettäväksi metadirektiivillä // @require https://update.greasyforks.org/scripts/548365/1654734/DeepQuery%20Secure%20Core%20%28page-world%20only%29.js
.
// ==UserScript==
// @name DeepQuery Secure Core (page-world only)
// @namespace dq.secure.v2
// @version 2.0.0
// @description 深度查询(Shadow DOM/iframe)核心执行器(页面上下文)。仅接受带签名消息,页面代码无法直接调用。
// @author you
// @match http://*/*
// @match https://*/*
// @include about:blank
// @run-at document-start
// @grant none
// @inject-into page
// ==/UserScript==
(function () {
'use strict';
/************** 配置 **************/
// 通道名(随便换成很长的随机串,增加混淆)
const CHANNEL = '__DQ_SECURE_V2__';
// HMAC 的密钥以“片段+重组”方式藏在闭包里,页面拿不到;你可以把片段改成你自己的随机串
// 强烈建议自行换成至少 32 字节以上随机 base64 的若干段
const KEY_PARTS = [
'dW5hcmFuZG9tLWtleS1zZWVkLQ', // 示例片段(请务必替换)
'tZXBsZWFzZS1yZXBsYWNlLW1l', // 示例片段(请务必替换)
'LXdpdGgtYS1wcm9wZXItb25l' // 示例片段(请务必替换)
];
// 允许的时间偏差(ms)与 nonce 存活(ms)
const MAX_SKEW = 60_000; // 60s
const NONCE_TTL = 10 * 60_000; // 10min
// --- 工具:base64 与 HMAC ---
const te = new TextEncoder();
const td = new TextDecoder();
function b64ToU8(b64) {
// 补 '='
const pad = b64.length % 4 ? (4 - b64.length % 4) : 0;
const s = b64 + '='.repeat(pad);
const bin = atob(s.replace(/-/g, '+').replace(/_/g, '/'));
const u8 = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) u8[i] = bin.charCodeAt(i);
return u8;
}
function u8eq(a, b) {
if (a.byteLength !== b.byteLength) return false;
let v = 0;
for (let i = 0; i < a.byteLength; i++) v |= (a[i] ^ b[i]);
return v === 0;
}
async function sha256U8(u8) {
const buf = await crypto.subtle.digest('SHA-256', u8);
return new Uint8Array(buf);
}
async function hmacSignRaw(key, u8) {
const k = await crypto.subtle.importKey(
'raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
);
const sig = await crypto.subtle.sign('HMAC', k, u8);
return new Uint8Array(sig);
}
const KEY_U8 = (() => {
// 简单拼接再 sha256,避免直接把明文 key 出现在脚本文本中
const joined = KEY_PARTS.join('');
const u8 = b64ToU8(joined);
return u8;
})();
// --- 防重放 ---
const seen = new Map(); // nonce -> ts
function sweep() {
const now = Date.now();
for (const [n, t] of seen) if (now - t > NONCE_TTL) seen.delete(n);
}
setInterval(sweep, 30_000);
async function verifySig(msg) {
// msg: {id, ts, nonce, spec, sigB64}
if (!msg || typeof msg !== 'object') return false;
const { id, ts, nonce, spec, sigB64 } = msg;
if (!id || typeof ts !== 'number' || !nonce || !sigB64) return false;
const skew = Math.abs(Date.now() - ts);
if (skew > MAX_SKEW) return false;
if (seen.has(nonce)) return false; // 重放
// 绑定消息体,防止被改内容重放
const specJson = JSON.stringify(spec || {});
const payload = te.encode(id + '\n' + ts + '\n' + nonce + '\n');
const bodyHash = await sha256U8(te.encode(specJson));
const toSign = new Uint8Array(payload.length + bodyHash.length);
toSign.set(payload, 0);
toSign.set(bodyHash, payload.length);
const expect = await hmacSignRaw(KEY_U8, toSign);
const got = b64ToU8(sigB64);
const ok = u8eq(expect, got);
if (ok) {
seen.set(nonce, ts);
}
return ok;
}
/************** 查询核心(与早先版本一致) **************/
const SHADOW_KEY = Symbol.for('__dq_shadow__');
const isTop = (window === window.top);
function safeQueryAll(root, sel) {
try { return Array.from(root.querySelectorAll(sel)); } catch { return []; }
}
function firstByIndex(list, idx) {
if (!list || !list.length) return null;
return typeof idx === 'number' ? (list[idx] || null) : list[0];
}
(function hookAttachShadowEarly() {
try {
const orig = Element.prototype.attachShadow;
if (!orig || orig.__dq_hooked__) return;
Object.defineProperty(Element.prototype, 'attachShadow', {
configurable: true, enumerable: false, writable: true,
value: function (init) {
const root = orig.call(this, init);
try {
Object.defineProperty(this, SHADOW_KEY, {
configurable: true, enumerable: false, writable: false, value: root
});
} catch {}
return root;
}
});
Element.prototype.attachShadow.__dq_hooked__ = true;
} catch {}
})();
function ensureOpenShadowReference(host) {
try {
if (!host) return;
if (host[SHADOW_KEY]) return;
if (host.shadowRoot) {
Object.defineProperty(host, SHADOW_KEY, {
configurable: true, enumerable: false, writable: false, value: host.shadowRoot
});
}
} catch {}
}
function parseChain(chain) {
if (typeof chain === 'string') {
const parts = chain.split('>>>').map(s => s.trim()).filter(Boolean);
const steps = [];
parts.forEach((seg, i) => {
if (i === 0) steps.push({ find: seg });
else steps.push({ shadow: true }, { find: seg });
});
return steps;
}
return (Array.isArray(chain) ? chain : []);
}
function toPlainRect(r) {
return 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 } : null;
}
function pickInfo(el, pick = {}) {
if (!el) return { ok: false, error: 'ELEMENT_NOT_FOUND' };
const res = { ok: true, tag: el.tagName, exists: true };
if (pick.attr) res.attr = el.getAttribute(pick.attr);
if (pick.prop) try { res.prop = el[pick.prop]; } catch { res.prop = undefined; }
if (pick.text) res.text = el.textContent;
if (pick.html) res.html = el.innerHTML;
if (pick.outerHTML) res.outerHTML = el.outerHTML;
if (pick.value) res.value = (el.value !== undefined ? el.value : undefined);
if (pick.rect) res.rect = toPlainRect(el.getBoundingClientRect ? el.getBoundingClientRect() : null);
if (pick.styles && Array.isArray(pick.styles) && pick.styles.length) {
const cs = getComputedStyle(el);
const map = {};
pick.styles.forEach(k => { map[k] = cs.getPropertyValue(k); });
res.styles = map;
}
if (pick.dataset) {
const dst = {};
(Array.isArray(pick.dataset) ? pick.dataset : [pick.dataset]).forEach(k => { dst[k] = el.dataset ? el.dataset[k] : undefined; });
res.dataset = dst;
}
return res;
}
function deepQueryOnce(rootLike, chainSteps) {
let root = (rootLike instanceof ShadowRoot || rootLike instanceof Document) ? rootLike : document;
let current = root;
let lastEl = null;
for (const step of chainSteps) {
if (step.shadow) {
if (!lastEl) return null;
ensureOpenShadowReference(lastEl);
const sr = lastEl.shadowRoot || lastEl[SHADOW_KEY];
if (!sr) return null;
current = sr;
continue;
}
if (step.find) {
const idx = (typeof step.index === 'number') ? step.index : null;
const list = safeQueryAll(current, step.find);
lastEl = firstByIndex(list, idx);
if (!lastEl) return null;
continue;
}
return null;
}
return lastEl;
}
async function waitForDeepQuery(rootLike, chainSteps, timeout = 0) {
const start = Date.now();
const tryOnce = () => deepQueryOnce(rootLike, chainSteps);
let el = tryOnce();
if (el || !timeout) return el;
return new Promise((resolve) => {
const limit = start + timeout;
const observer = new MutationObserver(() => {
el = tryOnce();
if (el) { observer.disconnect(); resolve(el); }
else if (Date.now() > limit) { observer.disconnect(); resolve(null); }
});
observer.observe(document, { childList: true, subtree: true, attributes: true });
const t = setTimeout(() => { try { observer.disconnect(); } catch {} resolve(null); }, timeout);
const tick = () => {
el = tryOnce();
if (el) { clearTimeout(t); try { observer.disconnect(); } catch {} resolve(el); }
else if (Date.now() <= limit) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
});
}
// frame 选择
function findTargetFrames(step) {
const cfg = (typeof step === 'string') ? { selector: step } : (step || {});
const sel = cfg.selector || 'iframe';
const all = safeQueryAll(document, sel).filter(n => n && n.tagName === 'IFRAME');
if (!all.length) return [];
if (typeof cfg.index === 'number') {
const one = all[cfg.index];
return (one && one.contentWindow) ? [one.contentWindow] : [];
}
return all.map(el => el.contentWindow).filter(Boolean);
}
/************** 消息收发(带签名) **************/
const pending = new Map(); // id -> resolver
function rid() {
return (Date.now().toString(36) + Math.random().toString(36).slice(2, 10)).toUpperCase();
}
function sendMessage(targetWin, payload) {
try { targetWin.postMessage({ [CHANNEL]: payload }, '*'); } catch {}
}
async function forwardOrExec(msg, sourceWin) {
const { id, spec = {} } = msg;
const framePath = Array.isArray(spec.framePath) ? spec.framePath : [];
if (framePath.length > 0) {
const [step, ...rest] = framePath;
const targets = findTargetFrames(step);
if (!targets.length) {
sendMessage(sourceWin || window.parent, { cmd: 'RESP', id, res: { ok: false, error: 'FRAME_NOT_FOUND' } });
return;
}
// fan-out:向匹配目标逐一转发,取第一个成功的结果
const subResults = await Promise.all(targets.map(t => new Promise(async (resolve) => {
const subId = rid();
pending.set(subId, { resolve });
// 用相同的 spec 但缩短 framePath;**重新签名**
const subMsg = {
cmd: 'REQ',
id: subId,
ts: Date.now(),
nonce: rid() + Math.random().toString(36).slice(2),
spec: { ...spec, framePath: rest }
};
// 签名
const payload = te.encode(subMsg.id + '\n' + subMsg.ts + '\n' + subMsg.nonce + '\n');
const bodyHash = await sha256U8(te.encode(JSON.stringify(subMsg.spec)));
const toSign = new Uint8Array(payload.length + bodyHash.length);
toSign.set(payload, 0); toSign.set(bodyHash, payload.length);
const sigU8 = await hmacSignRaw(KEY_U8, toSign);
const sigB64 = btoa(String.fromCharCode(...sigU8));
sendMessage(t, { ...subMsg, sigB64 });
// 超时保护
setTimeout(() => {
if (pending.has(subId)) {
pending.delete(subId);
resolve({ ok: false, error: 'TIMEOUT_FORWARD' });
}
}, typeof spec.timeout === 'number' ? Math.max(200, spec.timeout) : 5000);
})));
const ok = subResults.find(r => r && r.ok);
const res = ok || subResults[0] || { ok: false, error: 'NO_RESULT' };
sendMessage(sourceWin || window.parent, { cmd: 'RESP', id, res });
return;
}
// 在本帧执行
try {
const chain = parseChain(spec.chain);
const el = await waitForDeepQuery(document, chain, typeof spec.timeout === 'number' ? spec.timeout : 0);
const res = pickInfo(el, spec.pick || {});
sendMessage(sourceWin || window.parent, { cmd: 'RESP', id, res });
} catch (err) {
sendMessage(sourceWin || window.parent, { cmd: 'RESP', id, res: { ok: false, error: 'EXEC_ERROR', message: String(err && err.message || err) } });
}
}
window.addEventListener('message', async (e) => {
const msg = e.data && e.data[CHANNEL];
if (!msg) return;
// 响应回传(promise 归还)
if (msg.cmd === 'RESP' && msg.id && pending.has(msg.id)) {
const { resolve } = pending.get(msg.id);
pending.delete(msg.id);
resolve(msg.res);
return;
}
// 进入点:必须验签
if (msg.cmd === 'REQ') {
const ok = await verifySig(msg);
if (!ok) {
sendMessage(e.source || window.parent, { cmd: 'RESP', id: msg.id, res: { ok: false, error: 'UNAUTHORIZED' } });
return;
}
// 验签通过,执行/转发
await forwardOrExec(msg, e.source || window.parent);
}
}, false);
if (isTop) {
try { console.debug('[DeepQuery Secure Core] ready. Listening on', CHANNEL); } catch {}
}
})();