Hook 的核心模式
JavaScript 函数 Hook 的通用模式:
const original = window.targetFunction;
window.targetFunction = function (...args) {
// 前置处理
console.log('before call:', args);
const result = original.apply(this, args);
// 后置处理
console.log('after call:', result);
return result;
};
apply(this, args) 保持原始的 this 上下文,...args 透传所有参数。
Hook fetch
const rawFetch = window.fetch;
window.fetch = async function (...args) {
console.log('[fetch]', args[0]);
const response = await rawFetch.apply(this, args);
// clone() 避免 body 被消费后无法再读
const clone = response.clone();
clone.text().then(body => {
console.log('[fetch response]', body.slice(0, 200));
});
return response;
};
Hook XMLHttpRequest
const RawXHR = window.XMLHttpRequest;
window.XMLHttpRequest = function () {
const xhr = new RawXHR();
const rawOpen = xhr.open.bind(xhr);
xhr.open = function (method, url, ...args) {
console.log('[XHR]', method, url);
return rawOpen(method, url, ...args);
};
const rawSend = xhr.send.bind(xhr);
xhr.send = function (body) {
xhr.addEventListener('load', function () {
console.log('[XHR response]', xhr.responseText.slice(0, 200));
});
return rawSend(body);
};
return xhr;
};
Hook Function.prototype.call(监听所有函数调用)
(function () {
const rawCall = Function.prototype.call;
Function.prototype.call = function (...args) {
// this 就是被调用的函数
if (this.name === 'targetFunction') {
console.log('[call hook]', this.name, args);
}
return rawCall.apply(this, args);
};
})();
影响范围极大,生产环境谨慎使用,建议只在调试时临时注入。
Hook 某个对象的方法
const original = window.api.submit;
window.api.submit = function (...args) {
console.log('[hook api.submit]', args);
const result = original.apply(this, args);
console.log('[result]', result);
return result;
};
Hook setInterval(拦截 debugger)
部分网页在 setInterval 里运行 debugger 语句来阻止调试:
(() => {
const rawSetInterval = window.setInterval;
const rawClearInterval = window.clearInterval;
const fakeTimers = new Set();
let fakeId = 1_000_000;
function shouldBlock(fn) {
try {
let code = typeof fn === 'function'
? Function.prototype.toString.call(fn)
: String(fn);
if (/\bdebugger\b/.test(code)) {
console.warn('[blocked debugger interval]');
return true;
}
} catch (e) {}
return false;
}
window.setInterval = function (fn, delay, ...args) {
if (shouldBlock(fn)) {
fakeTimers.add(++fakeId);
return fakeId;
}
return rawSetInterval(fn, delay, ...args);
};
window.clearInterval = function (id) {
if (fakeTimers.has(id)) {
fakeTimers.delete(id);
return;
}
return rawClearInterval(id);
};
})();
fakeId 返回一个虚假的 timer ID,避免调用 clearInterval 时报错。
Hook WebAssembly.instantiate
const rawInstantiate = WebAssembly.instantiate;
WebAssembly.instantiate = async function (...args) {
const result = await rawInstantiate.apply(this, args);
// 拦截导出函数
const exports = result.instance.exports;
for (const key of Object.keys(exports)) {
if (typeof exports[key] === 'function') {
const original = exports[key];
exports[key] = function (...callArgs) {
console.log(`[wasm] ${key}(`, callArgs, ')');
return original.apply(this, callArgs);
};
}
}
return result;
};
油猴中的注入时机
// ==UserScript==
// @run-at document-start
// ==/UserScript==
document-start 是最早的时机,在页面任何脚本执行之前运行,适合 hook 全局 API。如果用 document-end 或 document-idle,页面原始代码可能已经运行了。
