# 浏览器 Hook 模板 本文件提供授权调试时用于定位加密参数的最小 Hook 模板。原则:只观察、不篡改、不批量请求;命中后用调用栈回到源码确认。 ## 使用规则 - 只在用户授权的目标页面中使用。 - Hook 前先记录目标 API、参数名和页面 URL。 - Hook 代码只输出调用栈和关键值,不修改请求内容。 - 命中后尽快移除 Hook,避免影响目标页面逻辑。 - 不用 Hook 绕过登录、验证码、MFA 或风控。 ## fetch ```javascript (() => { const targetParam = "sign"; const apiKeyword = "/api/"; const rawFetch = window.fetch; window.fetch = function patchedFetch(input, init) { const url = typeof input === "string" ? input : (input && input.url) || ""; const body = init && init.body; if (url.includes(apiKeyword) || String(url).includes(targetParam) || String(body || "").includes(targetParam)) { console.group("[fetch 命中]", url); console.log("input =", input); console.log("init =", init); console.trace("fetch 调用栈"); console.groupEnd(); debugger; } return rawFetch.apply(this, arguments); }; })(); ``` ## XMLHttpRequest ```javascript (() => { const targetParam = "sign"; const apiKeyword = "/api/"; const rawOpen = XMLHttpRequest.prototype.open; const rawSend = XMLHttpRequest.prototype.send; const rawSetHeader = XMLHttpRequest.prototype.setRequestHeader; XMLHttpRequest.prototype.open = function patchedOpen(method, url) { this.__reverse_method = method; this.__reverse_url = String(url || ""); return rawOpen.apply(this, arguments); }; XMLHttpRequest.prototype.setRequestHeader = function patchedSetRequestHeader(name, value) { if (String(name).includes(targetParam) || String(value).includes(targetParam)) { console.group("[XHR Header 命中]", this.__reverse_url); console.log(name, value); console.trace("setRequestHeader 调用栈"); console.groupEnd(); debugger; } return rawSetHeader.apply(this, arguments); }; XMLHttpRequest.prototype.send = function patchedSend(body) { const url = this.__reverse_url || ""; if (url.includes(apiKeyword) || url.includes(targetParam) || String(body || "").includes(targetParam)) { console.group("[XHR send 命中]", this.__reverse_method, url); console.log("body =", body); console.trace("send 调用栈"); console.groupEnd(); debugger; } return rawSend.apply(this, arguments); }; })(); ``` ## URLSearchParams / FormData ```javascript (() => { const targetParam = "sign"; for (const Ctor of [URLSearchParams, FormData].filter(Boolean)) { for (const method of ["append", "set"]) { const raw = Ctor.prototype[method]; if (typeof raw !== "function") continue; Ctor.prototype[method] = function patchedParamWrite(name, value) { if (String(name) === targetParam || String(value).includes(targetParam)) { console.group(`[${Ctor.name}.${method} 命中]`, name); console.log("value =", value); console.trace("参数写入调用栈"); console.groupEnd(); debugger; } return raw.apply(this, arguments); }; } } })(); ``` ## Cookie ```javascript (() => { const targetParam = "sign"; const proto = Document.prototype; const desc = Object.getOwnPropertyDescriptor(proto, "cookie"); if (!desc || !desc.set || !desc.get) return; Object.defineProperty(document, "cookie", { configurable: true, enumerable: desc.enumerable, get() { const value = desc.get.call(document); if (String(value).includes(targetParam)) { console.trace("document.cookie 读取命中"); } return value; }, set(value) { if (String(value).includes(targetParam)) { console.group("[cookie 写入命中]"); console.log(value); console.trace("cookie 写入调用栈"); console.groupEnd(); debugger; } return desc.set.call(document, value); } }); })(); ``` ## localStorage / sessionStorage ```javascript (() => { const keys = ["token", "sign", "device", "fingerprint"]; for (const storageName of ["localStorage", "sessionStorage"]) { const storage = window[storageName]; if (!storage) continue; for (const method of ["getItem", "setItem", "removeItem"]) { const raw = storage[method]; storage[method] = function patchedStorage(key, value) { if (keys.some(k => String(key).includes(k))) { console.group(`[${storageName}.${method} 命中]`, key); console.log("value =", value); console.trace("Storage 调用栈"); console.groupEnd(); } return raw.apply(this, arguments); }; } } })(); ``` ## 时间、随机数和动态代码 ```javascript (() => { for (const [owner, name] of [[Date, "now"], [Math, "random"]]) { const raw = owner[name]; owner[name] = function patchedTimeRandom() { const ret = raw.apply(this, arguments); console.log(`[${name}]`, ret); console.trace(`${name} 调用栈`); return ret; }; } const rawGetRandomValues = crypto && crypto.getRandomValues; if (rawGetRandomValues) { crypto.getRandomValues = function patchedGetRandomValues(arr) { const ret = rawGetRandomValues.apply(this, arguments); console.log("[crypto.getRandomValues]", Array.from(arr)); console.trace("crypto 随机数调用栈"); return ret; }; } const rawFunction = Function; window.Function = new Proxy(rawFunction, { construct(target, args) { console.log("[new Function]", args); console.trace("动态函数调用栈"); debugger; return Reflect.construct(target, args); }, apply(target, thisArg, args) { console.log("[Function]", args); console.trace("动态函数调用栈"); debugger; return Reflect.apply(target, thisArg, args); } }); })(); ``` ## WebSocket、事件和 postMessage ```javascript (() => { const rawWS = window.WebSocket; if (rawWS) { window.WebSocket = new Proxy(rawWS, { construct(target, args) { console.log("[WebSocket 创建]", args); console.trace("WebSocket 调用栈"); return Reflect.construct(target, args); } }); } const rawPostMessage = window.postMessage; if (rawPostMessage) { window.postMessage = function patchedPostMessage(message, targetOrigin, transfer) { console.log("[postMessage]", message, targetOrigin); console.trace("postMessage 调用栈"); return rawPostMessage.apply(this, arguments); }; } })(); ``` ## 证据输出要求 命中 Hook 后至少记录: - 命中的 API 或参数名。 - 调用栈截图或文本。 - 所在 JS 文件、行列号、chunk 名。 - 写入前后请求对象。 - 是否可复现。 - 是否需要 sourcemap 或动态 chunk。