JS 逆向 Hook 与代码片段大全
Hook 的核心原理是利用 JavaScript 弱类型语言特性,用自定义函数替换原生函数,在调用时插入 debugger 或日志,从而定位加密逻辑。 两种核心实现方式: Hook 注入方式 浏览器 DevTools 场景 Node.js 补环境场景 参考 监控某个对象上指定属性的读取、写入、删除操作,精确定位属性被谁写入。 代理整个对象,监控其上所有属性的读写操作,适合不知道属性名时的全量监控。 对整个对象做 Proxy 代理,每次访问都打印 对象名.属性名 的完整路径,适合追踪链式访问。 Hook 内置对象的原型方法,监控 String、Array、
官方文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript
Hook 的核心原理是利用 JavaScript 弱类型语言特性,用自定义函数替换原生函数,在调用时插入 debugger 或日志,从而定位加密逻辑。
被 Hook 的函数必须和
debugger在同一个作用域内,否则 Hook 无效。Hook 代码必须在目标代码执行之前注入,推荐通过 Tampermonkey 的@run-at document-start保证优先级。
两种核心实现方式:
| 方式 | 适用场景 | 备注 |
|---|---|---|
Object.defineProperty |
监控对象属性读写 | 对已有属性可精确控制 getter/setter |
Proxy |
代理整个对象的所有操作 | 更全面,但需要通过代理对象访问才能触发 |
目录
Hook 注入方式
浏览器 DevTools 场景
- 1. 对象监控
- 2. 函数 Hook
- 3. 原型链 Hook
- 4. XMLHttpRequest Hook
- 5. fetch Hook
- 6. Cookie Hook
- 7. localStorage / sessionStorage Hook
- 8. debugger 反调试绕过
- 9. JSON.stringify / JSON.parse Hook
- 10. WebSocket Hook
- 11. eval / Function Hook
- 12. Canvas 指纹 Hook
- 13. 定时器 Hook
- 14. DOM 监控
- 15. console 保护与重写
- 16. 加密函数定位
- 17. 其他实用 Hook
Node.js 补环境场景
参考
注入方式
| 方式 | 说明 |
|---|---|
| 控制台直接粘贴 | 刷新页面前在 Console 面板粘贴执行,适合快速测试 |
| Tampermonkey 脚本 | 写成用户脚本,@run-at document-start,持久化注入 |
| Chrome Local Override | 下载目标 JS 文件,添加 Hook 代码,启用本地覆盖 |
| Fiddler/mitmproxy 注入 | 通过代理在响应体中动态插入 Hook 代码 |
1. 对象监控
1.1 Object.defineProperty 监控属性读写
监控某个对象上指定属性的读取、写入、删除操作,精确定位属性被谁写入。
// 使用场景:追踪 window 上某个变量被谁赋值
(function () {
var _value;
Object.defineProperty(window, 'targetProp', {
configurable: true,
get: function () {
console.log('[GET] targetProp =', _value);
console.log(new Error().stack);
return _value;
},
set: function (val) {
console.log('[SET] targetProp =', val);
console.log(new Error().stack);
_value = val;
}
});
})();
1.2 Proxy 代理整个对象
代理整个对象,监控其上所有属性的读写操作,适合不知道属性名时的全量监控。
// 使用场景:监控 window 对象上所有属性的 get/set,找出未知的加密全局变量
const proxyWindow = new Proxy(window, {
get: function (target, prop, receiver) {
console.log('[GET]', prop, '=', target[prop]);
return Reflect.get(target, prop, receiver);
},
set: function (target, prop, val) {
console.log('[SET]', prop, '=', val);
console.log(new Error().stack);
return Reflect.set(target, prop, val);
},
deleteProperty: function (target, prop) {
console.log('[DELETE]', prop);
return Reflect.deleteProperty(target, prop);
}
});
// 注意:Proxy 是一个壳,必须通过 proxyWindow 访问才会触发拦截
// window = proxyWindow; // 谨慎操作,某些场景会破坏页面
1.3 监控对象上特定属性(通用模板)
// 使用场景:监控任意对象 obj 上名为 propName 的属性
function watchProperty(obj, propName) {
var _val = obj[propName];
Object.defineProperty(obj, propName, {
configurable: true,
enumerable: true,
get: function () {
console.log('[GET]', propName, '=', _val);
return _val;
},
set: function (newVal) {
console.log('[SET]', propName, ':', _val, '->', newVal);
console.log(new Error().stack);
_val = newVal;
}
});
}
// 调用示例
watchProperty(window, 'sign');
watchProperty(window, '_token');
1.4 watch() — 带路径追踪的 Proxy 监控
对整个对象做 Proxy 代理,每次访问都打印 对象名.属性名 的完整路径,适合追踪链式访问。
function watch(obj, name) {
const handler = {
get: function (target, prop) {
console.log('get:', `${name}.${prop}`, '===>', Reflect.get(...arguments));
return Reflect.get(...arguments);
},
set: function (target, prop, value, receiver) {
console.log('set:', `${name}.${prop}`, '===>', value);
return Reflect.set(target, prop, value, receiver);
}
};
return new Proxy(obj, handler);
}
// 用法:把目标对象替换为被监控的代理对象
window.navigator = watch(window.navigator, 'navigator');
// 监控自定义对象
const config = watch({ token: '', uid: 0 }, 'config');
config.token = 'abc'; // 控制台输出: set: config.token ===> abc
2. 函数 Hook
2.1 简单函数替换(记录参数和返回值)
// 使用场景:Hook 任意全局函数,记录每次调用的入参和出参
(function () {
var _original = window.targetFunction;
window.targetFunction = function () {
console.log('[CALL] targetFunction args:', arguments);
console.log(new Error().stack);
var result = _original.apply(this, arguments);
console.log('[RETURN] targetFunction result:', result);
return result;
};
// 伪装成原生函数,防止被检测
window.targetFunction.toString = function () {
return _original.toString();
};
})();
2.2 Hook alert(常用于快速测试)
// 使用场景:阻止页面弹出 alert 干扰调试,同时记录 alert 内容
var _alert = window.alert;
window.alert = function () {
console.log('[ALERT]', arguments[0]);
console.log(new Error().stack);
// _alert.apply(this, arguments); // 注释掉则阻止弹窗
};
window.alert.toString = function () {
return 'function alert() { [native code] }';
};
2.3 通用函数 Hook 模板(带 debugger)
// 使用场景:在加密函数调用时自动断下,配合调用栈分析
(function () {
var _fn = window.encryptFunc; // 替换为目标函数名
window.encryptFunc = function () {
debugger; // 断下后查看 Call Stack
return _fn.apply(this, arguments);
};
window.encryptFunc.toString = _fn.toString.bind(_fn);
})();
3. 原型链 Hook
Hook 内置对象的原型方法,监控 String、Array、Object 等原生方法的调用。
3.1 String.prototype.split Hook
// 使用场景:某些加密函数通过 split 分割字符串,hook 后可找到分割时机
String.prototype._split = String.prototype.split;
String.prototype.split = function (val) {
var str = this.toString();
if (str.length > 10) { // 过滤短字符串,减少噪音
console.log('[split] this =', str, ', separator =', val);
console.log(new Error().stack);
}
return str._split(val);
};
String.prototype.split.toString = function () {
return 'function split() { [native code] }';
};
3.2 RegExp.prototype.test Hook
// 使用场景:绕过正则检测(如反调试检测、UA 检测),或定位正则使用位置
RegExp.prototype._test = RegExp.prototype.test;
RegExp.prototype.test = function (val) {
console.log('[RegExp.test] pattern =', this.toString(), ', input =', val);
return this._test(val);
// 如需强制返回 true:return true;
};
RegExp.prototype.test.toString = function () {
return 'function test() { [native code] }';
};
3.3 Array.prototype.push Hook
// 使用场景:监控数组被 push 元素,定位加密结果被存入数组的位置
Array.prototype._push = Array.prototype.push;
Array.prototype.push = function () {
console.log('[Array.push] item =', arguments[0], ', array length =', this.length);
console.log(new Error().stack);
return this._push.apply(this, arguments);
};
Array.prototype.push.toString = function () {
return 'function push() { [native code] }';
};
3.4 Object.prototype 通用 Hook(慎用)
// 使用场景:监控某个属性名在所有对象上的读写(很重,调试时用,上线禁用)
(function () {
var _defineProperty = Object.defineProperty;
Object.defineProperty = function (obj, prop, descriptor) {
if (prop === 'sign') { // 替换为目标属性名
console.log('[defineProperty] prop =', prop, ', obj =', obj);
console.log(new Error().stack);
}
return _defineProperty.call(this, obj, prop, descriptor);
};
})();
4. XMLHttpRequest Hook
4.1 完整 XHR Hook(记录请求方法、URL、请求头、请求体)
// 使用场景:全量监控 XHR 请求,记录所有请求信息和调用栈
(function () {
var hook_open = XMLHttpRequest.prototype.open;
var hook_setRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
var hook_send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function () {
this._method = arguments[0];
this._url = arguments[1];
return hook_open.call(this, ...arguments);
};
XMLHttpRequest.prototype.setRequestHeader = function () {
console.log(
'[XHR setRequestHeader] url =', this._url,
'\n header:', arguments[0], '=', arguments[1]
);
console.log(new Error().stack);
return hook_setRequestHeader.call(this, ...arguments);
};
XMLHttpRequest.prototype.send = function () {
this._data = arguments[0];
if (this._data != null) {
console.log(
'[XHR send]\n method:', this._method,
'\n url:', this._url,
'\n body:', this._data
);
} else {
console.log('[XHR send]\n method:', this._method, '\n url:', this._url);
}
console.log(new Error().stack);
return hook_send.call(this, ...arguments);
};
})();
4.2 XHR URL 断点 Hook(定位请求发起位置)
// 使用场景:URL 包含目标关键字时断下,定位请求发起的代码位置
(function () {
var _open = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function (method, url, async) {
if (url.indexOf('login') !== -1) { // 替换为目标 URL 关键字
debugger;
}
return _open.apply(this, arguments);
};
})();
4.3 XHR 请求头断点 Hook(定位自定义签名 header)
// 使用场景:定位 X-Sign、Authorization 等自定义请求头的生成位置
(function () {
var _setRequestHeader = window.XMLHttpRequest.prototype.setRequestHeader;
window.XMLHttpRequest.prototype.setRequestHeader = function (key, value) {
if (key === 'X-Sign') { // 替换为目标 header key
debugger;
}
return _setRequestHeader.apply(this, arguments);
};
})();
4.4 XHR 响应拦截 Hook
// 使用场景:监控并可修改 XHR 的响应数据
(function () {
var _open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url) {
this._url = url;
this.addEventListener('readystatechange', function () {
if (this.readyState === 4) {
console.log('[XHR response] url =', this._url);
console.log('[XHR response] status =', this.status);
console.log('[XHR response] body =', this.responseText.substring(0, 200));
}
});
return _open.apply(this, arguments);
};
})();
4.5 Proxy 方式 Hook XHR(拦截构造过程)
// 使用场景:在创建 XHR 实例时即介入,适合需要修改响应的场景
XMLHttpRequest = new Proxy(XMLHttpRequest, {
construct: function (target, args) {
var xhr = new target(...args);
xhr.onreadystatechange = function () {
if (xhr.readyState === 1) {
// 请求发送前,可在此注入 header
xhr.setRequestHeader('X-Custom', 'injected');
}
if (xhr.readyState === 4) {
if (xhr.status === 401) {
console.log('[XHR] 401 Unauthorized');
}
}
};
return xhr;
}
});
5. fetch Hook
5.1 基础 fetch Hook(记录所有参数和调用栈)
// 使用场景:全量监控 fetch 请求,记录 URL、配置项和调用栈
(function () {
var _fetch = window.fetch;
window.fetch = function () {
console.log('[fetch] args:', ...arguments);
console.log(new Error().stack);
return _fetch.apply(this, arguments);
};
})();
5.2 fetch 请求拦截(可修改请求参数)
// 使用场景:拦截特定 URL 的 fetch 请求,可修改请求参数或断下
const { fetch: _fetch } = window;
window.fetch = async function (...args) {
let [resource, config] = args;
// 请求拦截:打印请求信息
console.log('[fetch request] url =', resource);
console.log('[fetch request] config =', config);
// 可在此修改 resource 或 config
// resource = 'https://new-url.example.com/api';
const response = await _fetch(resource, config);
return response;
};
5.3 fetch 响应拦截(可读取/修改响应体)
// 使用场景:读取 fetch 响应内容,定位响应数据解密位置
const { fetch: _fetch } = window;
window.fetch = async function (...args) {
let [resource, config] = args;
let response = await _fetch(resource, config);
// 克隆响应(原始响应只能被消费一次)
const cloned = response.clone();
cloned.text().then(function (body) {
console.log('[fetch response] url =', resource);
console.log('[fetch response] status =', response.status);
console.log('[fetch response] body =', body.substring(0, 300));
});
return response;
};
5.4 Proxy 方式 Hook fetch
// 使用场景:使用 Proxy apply 陷阱拦截 fetch,等价于上面的写法
window.fetch = new Proxy(window.fetch, {
apply: function (target, that, args) {
console.log('[fetch] url =', args[0]);
console.log(new Error().stack);
var result = target.apply(that, args);
result.then(function (res) {
if (res.status === 401) {
console.log('[fetch] 401 Unauthorized');
}
});
return result;
}
});
6. Cookie Hook
6.1 Cookie 写入监控(带条件断点)
// 使用场景:定位特定 Cookie 的生成位置,当 cookie 包含目标 key 时断下
(function () {
var cookieTemp = '';
Object.defineProperty(document, 'cookie', {
set: function (val) {
if (val.indexOf('your_key') !== -1) { // 替换为目标 cookie key
debugger;
}
console.log('[Cookie SET]', val);
console.log(new Error().stack);
cookieTemp = val;
return val;
},
get: function () {
return cookieTemp;
}
});
})();
6.2 Cookie Hook v2(使用原生 getter/setter,更稳定)
// 使用场景:更稳定地 Hook cookie,通过获取 Document.prototype 上的原生访问器实现
(function () {
var cookie_accessor = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');
var get_cookie = cookie_accessor.get;
var set_cookie = cookie_accessor.set;
Object.defineProperty(document, 'cookie', {
get: function () {
return get_cookie.call(document);
},
set: function (cookie) {
console.log('[Cookie SET]', cookie);
console.log(new Error().stack);
set_cookie.call(document, cookie);
}
});
})();
6.3 Cookie 完整管理 Hook(维护本地 cookie 字符串)
// 使用场景:完整接管 cookie 的读写,维护 key-value 映射,记录每次变更
(function () {
'use strict';
document.temp_cookie = document.cookie;
Object.defineProperty(document, 'cookie', {
get: function () {
return document.temp_cookie;
},
set: function (cookie) {
console.log('[Cookie SET]', cookie);
console.log(new Error().stack);
var key_arr = [];
var val_arr = [];
// 处理带过期时间等属性的 cookie(格式:key=val; expires=...)
if (cookie.split('=').length > 2) {
var temp_k = cookie.split('=')[0];
var temp_val = cookie.substring(cookie.indexOf('=') + 1, cookie.indexOf(';'));
key_arr.push(temp_k);
val_arr.push(temp_val);
}
// 合并已有 cookie
if (document.temp_cookie !== '') {
document.temp_cookie.split('; ').forEach(function (item) {
var k = item.split('=')[0];
var v = item.split('=')[1];
if (typeof temp_k !== 'undefined' && temp_k === k) {
// skip, 已处理
} else {
key_arr.push(k);
val_arr.push(v);
}
});
document.temp_cookie = '';
}
// 处理简单 cookie(格式:key=val)
if (!(cookie.split('=').length > 2)) {
var newKey = cookie.split('=')[0];
var newVal = cookie.split('=')[1];
var found = false;
for (var i = 0; i < key_arr.length; i++) {
if (key_arr[i] === newKey) {
val_arr[i] = newVal;
found = true;
break;
}
}
if (!found) {
key_arr.push(newKey);
val_arr.push(newVal);
}
}
// 重新组合 cookie 字符串
document.temp_cookie = key_arr.map(function (k, i) {
return k + '=' + val_arr[i];
}).join('; ');
}
});
})();
7. localStorage / sessionStorage Hook
7.1 localStorage 全方法监控
// 使用场景:监控所有 localStorage 操作(setItem/getItem/removeItem/clear),打印调用栈
(function () {
'use strict';
var _setItem = localStorage.setItem;
localStorage.setItem = function () {
console.log('[localStorage.setItem] key =', arguments[0], ', value =', arguments[1]);
console.log(new Error().stack);
return _setItem.call(this, ...arguments);
};
var _getItem = localStorage.getItem;
localStorage.getItem = function () {
var val = _getItem.call(this, ...arguments);
console.log('[localStorage.getItem] key =', arguments[0], ', value =', val);
console.log(new Error().stack);
return val;
};
var _removeItem = localStorage.removeItem;
localStorage.removeItem = function () {
console.log('[localStorage.removeItem] key =', arguments[0]);
console.log(new Error().stack);
return _removeItem.call(this, ...arguments);
};
var _clear = localStorage.clear;
localStorage.clear = function () {
console.log('[localStorage.clear] all items removed');
console.log(new Error().stack);
return _clear.call(this, ...arguments);
};
})();
7.2 sessionStorage 全方法监控
// 使用场景:监控所有 sessionStorage 操作
(function () {
'use strict';
var _setItem = sessionStorage.setItem;
sessionStorage.setItem = function () {
console.log('[sessionStorage.setItem] key =', arguments[0], ', value =', arguments[1]);
console.log(new Error().stack);
return _setItem.call(this, ...arguments);
};
var _getItem = sessionStorage.getItem;
sessionStorage.getItem = function () {
var val = _getItem.call(this, ...arguments);
console.log('[sessionStorage.getItem] key =', arguments[0], ', value =', val);
console.log(new Error().stack);
return val;
};
var _removeItem = sessionStorage.removeItem;
sessionStorage.removeItem = function () {
console.log('[sessionStorage.removeItem] key =', arguments[0]);
console.log(new Error().stack);
return _removeItem.call(this, ...arguments);
};
var _clear = sessionStorage.clear;
sessionStorage.clear = function () {
console.log('[sessionStorage.clear] all items removed');
console.log(new Error().stack);
return _clear.call(this, ...arguments);
};
})();
7.3 带条件断点的 localStorage Hook
// 使用场景:只在操作特定 key 时断下,过滤无关的读写操作
(function () {
var TARGET_KEY = 'token'; // 替换为目标 key
var _setItem = localStorage.setItem;
localStorage.setItem = function (key, value) {
if (key === TARGET_KEY) {
console.log('[localStorage.setItem TARGET]', key, '=', value);
debugger;
}
return _setItem.call(this, key, value);
};
})();
8. debugger 反调试绕过
8.1 置空定时器(绕过 setInterval 无限 debugger)
// 使用场景:绕过通过 setInterval 循环触发 debugger 的反调试
// 方式一:直接置空 setInterval,会影响页面所有定时器(副作用大)
setInterval = function () {};
// 方式二:Hook setInterval,过滤含 debugger 的函数(推荐)
var _setInterval = setInterval;
setInterval = function (func, timer) {
if (func.toString().indexOf('debugger') !== -1) {
console.log('[反调试] 检测到 setInterval 无限 debugger,已拦截');
return null;
}
return _setInterval(func, timer);
};
setInterval.toString = function () {
return 'function setInterval() { [native code] }';
};
8.2 Hook Function 构造函数(绕过 new Function('debugger'))
// 使用场景:绕过通过 Function('debugger') 实现的无限 debugger(OB 混淆常见)
(function () {
var _constructor = window.Function.prototype.constructor;
window.Function.prototype.constructor = function () {
var fnContent = arguments[arguments.length - 1];
if (typeof fnContent === 'string' && fnContent.indexOf('debugger') !== -1) {
console.log('[反调试] 拦截 Function("debugger")');
return function () {};
}
return _constructor.apply(this, arguments);
};
})();
8.3 绕过 eval('debugger')
// 使用场景:绕过通过 eval('debugger') 实现的反调试
var _eval = window.eval;
window.eval = function (src) {
if (typeof src === 'string' && src.indexOf('debugger') !== -1) {
console.log('[反调试] 拦截 eval("debugger")');
return undefined;
}
return _eval.call(this, src);
};
window.eval.toString = function () {
return 'function eval() { [native code] }';
};
8.4 清除所有已有定时器
// 使用场景:页面已有无限 debugger 定时器在运行,批量清除
for (var i = 1; i < 99999; i++) {
window.clearInterval(i);
window.clearTimeout(i);
}
8.5 组合反调试绕过(推荐一起使用)
// 使用场景:同时覆盖多种无限 debugger 实现方式
(function () {
// 绕过 Function('debugger') 和 constructor('debugger')
var _FunctionConstructor = Function.prototype.constructor;
Function.prototype.constructor = function (arg) {
if (typeof arg === 'string' && arg.indexOf('debugger') !== -1) {
return function () {};
}
return _FunctionConstructor.apply(this, arguments);
};
// 绕过 setInterval(fn, t) 中含 debugger 的函数
var _setInterval = window.setInterval;
window.setInterval = function (func, timer) {
if (typeof func === 'function' && func.toString().indexOf('debugger') !== -1) {
return null;
}
return _setInterval.call(this, func, timer);
};
// 绕过 setTimeout(fn, t) 中含 debugger 的函数
var _setTimeout = window.setTimeout;
window.setTimeout = function (func, timer) {
if (typeof func === 'function' && func.toString().indexOf('debugger') !== -1) {
return null;
}
return _setTimeout.call(this, func, timer);
};
})();
9. JSON.stringify / JSON.parse Hook
9.1 JSON.stringify 和 JSON.parse 完整 Hook
// 使用场景:监控请求体序列化(stringify)和响应体反序列化(parse),定位加密参数拼接位置
(function () {
var _parse = JSON.parse;
JSON.parse = function (str) {
console.log('[JSON.parse] input =', str);
console.log(new Error().stack);
console.log('------------------------------------------------------------');
return _parse(str);
};
var _stringify = JSON.stringify;
JSON.stringify = function (obj) {
console.log('[JSON.stringify] input =', obj);
console.log(new Error().stack);
console.log('------------------------------------------------------------');
return _stringify(obj);
};
})();
9.2 JSON Hook 带条件断点
// 使用场景:只在序列化/反序列化结果包含特定字段时断下
(function () {
var TARGET = 'sign'; // 替换为目标字段名
var _stringify = JSON.stringify;
JSON.stringify = function (obj) {
var result = _stringify(obj);
if (result && result.indexOf(TARGET) !== -1) {
console.log('[JSON.stringify 命中]', result);
debugger;
}
return result;
};
var _parse = JSON.parse;
JSON.parse = function (str) {
if (typeof str === 'string' && str.indexOf(TARGET) !== -1) {
console.log('[JSON.parse 命中]', str);
debugger;
}
return _parse(str);
};
})();
10. WebSocket Hook
10.1 Proxy 方式 Hook WebSocket(推荐)
// 使用场景:监控 WebSocket 连接的所有发送消息、接收消息、连接和关闭事件
(function () {
var WebSocketProxy = new Proxy(window.WebSocket, {
construct: function (target, args) {
console.log('[WebSocket] 连接 URL:', args[0]);
var ws = new target(...args);
// 拦截 send(出站消息)
var _send = ws.send.bind(ws);
ws.send = function (data) {
console.log('[WebSocket SEND]', data);
console.log(new Error().stack);
return _send(data);
};
// 拦截 onmessage(入站消息)
var _onmessage = ws.onmessage;
ws.onmessage = function (event) {
console.log('[WebSocket RECV]', event.data);
if (_onmessage) return _onmessage.call(this, event);
};
// 拦截 close
var _close = ws.close.bind(ws);
ws.close = function () {
console.log('[WebSocket CLOSE]');
return _close();
};
// 拦截 addEventListener(有些页面不用 onmessage,用事件监听)
var _addEventListener = ws.addEventListener.bind(ws);
ws.addEventListener = function (eventName, listener, options) {
if (eventName === 'message') {
var _listener = listener;
listener = function (event) {
console.log('[WebSocket addEventListener message]', event.data);
return _listener.call(this, event);
};
}
return _addEventListener(eventName, listener, options);
};
// 保存到全局方便调试
window._websockets = window._websockets || [];
window._websockets.push(ws);
return ws;
}
});
window.WebSocket = WebSocketProxy;
})();
10.2 wsHook 风格(轻量 Hook,适合快速使用)
// 使用场景:简单场景下拦截 WebSocket 消息,可修改发送内容或阻止接收
var wsHook = {};
wsHook.before = null; // function(data, url, ws) { ... return data; }
wsHook.after = null; // function(event, url, ws) { ... return event; }
(function () {
var _WebSocket = window.WebSocket;
window.WebSocket = function (url, protocols) {
var ws = protocols ? new _WebSocket(url, protocols) : new _WebSocket(url);
var _send = ws.send.bind(ws);
ws.send = function (data) {
if (wsHook.before) {
data = wsHook.before(data, url, ws);
if (data === null) return; // 返回 null 则阻止发送
}
return _send(data);
};
ws.addEventListener('message', function (event) {
if (wsHook.after) {
var result = wsHook.after(event, url, ws);
if (result === null) return; // 返回 null 则阻止触发 onmessage
}
});
return ws;
};
window.WebSocket.prototype = _WebSocket.prototype;
})();
// 使用示例
wsHook.before = function (data, url, ws) {
console.log('[WS SEND]', url, data);
return data;
};
wsHook.after = function (event, url, ws) {
console.log('[WS RECV]', url, event.data);
return event;
};
11. eval / Function Hook
11.1 eval Hook(监控动态代码执行)
// 使用场景:监控通过 eval 执行的动态代码,分析混淆代码中的动态生成逻辑
(function () {
window.__cr_eval = window.eval;
var myeval = function (src) {
console.log('[eval]', src);
console.log('======= eval end =======');
debugger;
return window.__cr_eval(src);
};
var _myeval = myeval.bind(null);
_myeval.toString = window.__cr_eval.toString;
Object.defineProperty(window, 'eval', { value: _myeval });
})();
11.2 eval 替换为 console.log(只打印不执行)
// 使用场景:直接输出 eval 内容而不执行,安全查看动态代码
var _eval_bak = window.eval;
window.eval = console.log;
// 恢复:window.eval = _eval_bak;
11.3 Function 构造函数 Hook(监控 new Function)
// 使用场景:监控通过 new Function(...) 动态创建的函数,常见于 OB 混淆代码
(function () {
window.__cr_fun = window.Function;
var myfun = function () {
var args = Array.prototype.slice.call(arguments, 0, -1).join(',');
var src = arguments[arguments.length - 1];
console.log('[Function] args =', args);
console.log('[Function] body =', src);
console.log('======= Function end =======');
debugger;
return window.__cr_fun.apply(this, arguments);
};
myfun.toString = function () {
return window.__cr_fun + '';
};
Object.defineProperty(window, 'Function', { value: myfun });
})();
12. Canvas 指纹 Hook
12.1 Hook toDataURL(拦截 Canvas 导出图像)
// 使用场景:拦截 canvas.toDataURL 调用,定位指纹采集代码位置,或返回固定值欺骗检测
(function () {
var _toDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function (type) {
console.log('[canvas.toDataURL] type =', type);
console.log('[canvas.toDataURL] canvas size:', this.width, 'x', this.height);
console.log(new Error().stack);
debugger; // 定位调用位置后可去掉
return _toDataURL.apply(this, arguments);
// 如需返回固定值(欺骗指纹):
// return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
};
})();
12.2 Hook getImageData(拦截像素数据读取)
// 使用场景:拦截 canvas 像素数据读取,阻止指纹采集
(function () {
var _getImageData = CanvasRenderingContext2D.prototype.getImageData;
CanvasRenderingContext2D.prototype.getImageData = function (sx, sy, sw, sh) {
console.log('[canvas.getImageData] rect:', sx, sy, sw, sh);
console.log(new Error().stack);
debugger;
return _getImageData.apply(this, arguments);
};
})();
12.3 Canvas 指纹添加噪声(修改像素返回干扰值)
// 使用场景:为每次 canvas 采集添加随机噪声,使指纹不固定
(function () {
var _getImageData = CanvasRenderingContext2D.prototype.getImageData;
CanvasRenderingContext2D.prototype.getImageData = function (sx, sy, sw, sh) {
var imageData = _getImageData.apply(this, arguments);
// 在像素数据中添加微小随机噪声
for (var i = 0; i < imageData.data.length; i += 4) {
imageData.data[i] += Math.floor(Math.random() * 3) - 1; // R
imageData.data[i + 1] += Math.floor(Math.random() * 3) - 1; // G
imageData.data[i + 2] += Math.floor(Math.random() * 3) - 1; // B
// imageData.data[i + 3] 不修改 Alpha 通道
}
return imageData;
};
})();
12.4 Hook toBlob
// 使用场景:拦截通过 toBlob 方式获取 canvas 数据的指纹采集
(function () {
var _toBlob = HTMLCanvasElement.prototype.toBlob;
HTMLCanvasElement.prototype.toBlob = function (callback, type, quality) {
console.log('[canvas.toBlob] type =', type);
console.log(new Error().stack);
return _toBlob.apply(this, arguments);
};
})();
13. 定时器 Hook
13.1 Hook setInterval(监控/过滤定时任务)
// 使用场景:查看页面注册了哪些定时任务,过滤含 debugger 的定时器
(function () {
var _setInterval = window.setInterval;
window.setInterval = function (func, delay) {
var funcStr = typeof func === 'function' ? func.toString() : String(func);
console.log('[setInterval] delay =', delay, '\n func =', funcStr.substring(0, 100));
console.log(new Error().stack);
// 过滤 debugger
if (funcStr.indexOf('debugger') !== -1) {
console.log('[setInterval] 拦截含 debugger 的定时器');
return null;
}
return _setInterval.apply(this, arguments);
};
window.setInterval.toString = function () {
return 'function setInterval() { [native code] }';
};
})();
13.2 Hook setTimeout
// 使用场景:监控延迟执行的任务,追踪延迟触发的加密逻辑
(function () {
var _setTimeout = window.setTimeout;
window.setTimeout = function (func, delay) {
var funcStr = typeof func === 'function' ? func.toString() : String(func);
if (delay > 0) { // 过滤 delay=0 的短任务,减少噪音
console.log('[setTimeout] delay =', delay, '\n func =', funcStr.substring(0, 100));
}
if (funcStr.indexOf('debugger') !== -1) {
console.log('[setTimeout] 拦截含 debugger 的延迟任务');
return null;
}
return _setTimeout.apply(this, arguments);
};
window.setTimeout.toString = function () {
return 'function setTimeout() { [native code] }';
};
})();
13.3 Hook requestAnimationFrame
// 使用场景:部分反调试使用 requestAnimationFrame 循环,hook 可定位或阻断
(function () {
var _raf = window.requestAnimationFrame;
window.requestAnimationFrame = function (callback) {
var _callback = callback;
callback = function (timestamp) {
// console.log('[rAF] callback called');
return _callback(timestamp);
};
return _raf(callback);
};
})();
14. DOM 监控
14.1 MutationObserver 监控 DOM 变化
// 使用场景:监控页面 DOM 的动态变化,检测动态插入的 script 标签或 DOM 注入
(function () {
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
// 监控新增节点
mutation.addedNodes.forEach(function (node) {
if (node.nodeName === 'SCRIPT') {
console.log('[MutationObserver] 新增 script 标签:', node.src || node.textContent.substring(0, 100));
console.log(new Error().stack);
}
if (node.nodeType === 1) { // ELEMENT_NODE
console.log('[MutationObserver] 新增元素:', node.outerHTML ? node.outerHTML.substring(0, 150) : node);
}
});
// 监控属性变化
if (mutation.type === 'attributes') {
console.log('[MutationObserver] 属性变化:', mutation.target.tagName,
mutation.attributeName, '=', mutation.target.getAttribute(mutation.attributeName));
}
});
});
observer.observe(document.documentElement, {
childList: true, // 监控直接子节点增删
subtree: true, // 监控所有后代节点
attributes: true, // 监控属性变化
characterData: true // 监控文本节点变化
});
})();
14.2 Hook innerHTML / outerHTML
// 使用场景:监控动态 HTML 注入(可用于 XSS 分析或追踪动态渲染)
(function () {
var prop = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
var _get = prop.get;
var _set = prop.set;
Object.defineProperty(Element.prototype, 'innerHTML', {
get: function () {
return _get.call(this);
},
set: function (val) {
console.log('[innerHTML SET]', this.tagName, val.substring(0, 150));
console.log(new Error().stack);
return _set.call(this, val);
}
});
})();
14.3 监控特定元素属性变化
// 使用场景:监控某个 input 的 value 属性被动态修改
var targetEl = document.querySelector('#captcha-input'); // 替换为目标选择器
if (targetEl) {
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (m) {
console.log('[DOM 属性变化]', m.attributeName, '=', targetEl.getAttribute(m.attributeName));
});
});
observer.observe(targetEl, { attributes: true });
}
14.4 HTMLScriptElement.src Hook — 监控动态脚本注入
当页面通过 JS 动态创建 <script> 标签加载外部脚本时触发断点。
方式一:defineProperty(推荐,精确监控 src)
// 拦截所有 script 元素的 src 赋值
Object.defineProperty(HTMLScriptElement.prototype, 'src', {
set: function (value) {
console.log('[script.src]', value);
debugger; // 触发断点,查看调用栈定位加载来源
}
});
方式二:Proxy 代理整个原型(监控所有属性)
// 拦截原型上的所有属性赋值,不仅限于 src
HTMLScriptElement.prototype = new Proxy(HTMLScriptElement.prototype, {
set: function (target, prop, value) {
console.log('[HTMLScriptElement]', prop, '=', value);
debugger;
return Reflect.set(target, prop, value);
}
});
两种方式不要同时使用,会产生冲突。推荐用方式一定向监控 src,用方式二时监控所有属性。适用场景:网站动态插入第三方脚本(风控 JS、指纹采集 SDK),定位其加载时机和来源。
15. console 保护与重写
15.1 冻结 console(防止被网站覆盖)
// 使用场景:部分网站会覆盖 console.log 来阻止调试输出,冻结后无法修改
(function () {
Object.freeze(console);
})();
15.2 防止 console.log 被重写(检测并恢复)
// 使用场景:在页面加载完成后检查 console.log 是否被覆盖,如被覆盖则恢复
(function () {
var _log = console.log;
var waiting_time = 3;
var restored = false;
function restoreLog() {
if (console.log.toString() !== 'function log() { [native code] }') {
console.log = _log;
console.warn('[保护] console.log 被重写,已恢复原生实现');
}
restored = true;
}
window.addEventListener('load', restoreLog);
setTimeout(function () {
if (!restored) restoreLog();
}, waiting_time * 1000);
})();
15.3 阻止 console.clear
// 使用场景:防止网站调用 console.clear() 清空调试输出
(function () {
console.clear = function () {
console.log('[console.clear 已被禁用]');
};
})();
15.4 console.log Hook(记录所有输出)
// 使用场景:监控页面内所有 console.log 输出,追踪关键日志
(function () {
var _log = console.log;
console.log = function () {
// 可在此过滤或修改日志
_log.apply(console, arguments);
// 可同时上报到自己的服务器:
// fetch('/log', { method: 'POST', body: JSON.stringify([...arguments]) });
};
})();
16. 加密函数定位
16.1 通过调用栈定位(通用方法)
// 使用场景:在任意位置打印当前调用栈,顺着栈向上找到加密函数入口
console.log(new Error().stack);
// 或者
console.trace('当前调用栈');
16.2 搜索关键词定位加密函数
// 在 Sources 面板 Ctrl+Shift+F 全局搜索以下特征词:
// MD5 特征:1732584193 / 271733879 / md5
// SHA1 特征:1009589776 / sha1
// SHA256 特征:sha256 / 0x6a09e667
// Base64 特征:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmno
// AES 特征:AES / aes / CryptoJS
// RSA 特征:RSAKey / BigInteger / PublicKey
16.3 Hook CryptoJS 加密方法(定位 AES/DES/RC4 等)
// 使用场景:在引入了 CryptoJS 的页面,Hook 所有对称加密方法
(function () {
function hookCryptoMethod(obj, methodName) {
if (!obj || !obj[methodName]) return;
var _original = obj[methodName];
obj[methodName] = function () {
console.log('[CryptoJS.' + methodName + '] args:', ...arguments);
console.log(new Error().stack);
var result = _original.apply(this, arguments);
console.log('[CryptoJS.' + methodName + '] result:', result.toString ? result.toString() : result);
return result;
};
}
// 等待 CryptoJS 加载后执行
var timer = setInterval(function () {
if (typeof CryptoJS !== 'undefined') {
clearInterval(timer);
if (CryptoJS.AES) {
hookCryptoMethod(CryptoJS.AES, 'encrypt');
hookCryptoMethod(CryptoJS.AES, 'decrypt');
}
if (CryptoJS.DES) {
hookCryptoMethod(CryptoJS.DES, 'encrypt');
hookCryptoMethod(CryptoJS.DES, 'decrypt');
}
if (CryptoJS.TripleDES) {
hookCryptoMethod(CryptoJS.TripleDES, 'encrypt');
hookCryptoMethod(CryptoJS.TripleDES, 'decrypt');
}
if (CryptoJS.RC4) {
hookCryptoMethod(CryptoJS.RC4, 'encrypt');
hookCryptoMethod(CryptoJS.RC4, 'decrypt');
}
if (CryptoJS.MD5) hookCryptoMethod(CryptoJS, 'MD5');
if (CryptoJS.HmacSHA256) {
hookCryptoMethod(CryptoJS, 'HmacSHA256');
hookCryptoMethod(CryptoJS, 'HmacSHA1');
}
console.log('[CryptoJS Hook] 已注入所有加密方法');
}
}, 100);
})();
16.4 Hook JSEncrypt RSA 加密
// 使用场景:页面使用 JSEncrypt 进行 RSA 加密时,拦截 encrypt 方法获取明文和密文
(function () {
var timer = setInterval(function () {
if (typeof JSEncrypt !== 'undefined') {
clearInterval(timer);
var _encrypt = JSEncrypt.prototype.encrypt;
JSEncrypt.prototype.encrypt = function (data) {
console.log('[JSEncrypt.encrypt] 明文:', data);
var result = _encrypt.call(this, data);
console.log('[JSEncrypt.encrypt] 密文:', result);
console.log(new Error().stack);
return result;
};
console.log('[JSEncrypt Hook] 已注入 RSA encrypt');
}
}, 100);
})();
16.5 Hook window.btoa / atob(Base64)
// 使用场景:监控 Base64 编解码,定位数据编码位置
(function () {
var _btoa = window.btoa;
window.btoa = function (str) {
console.log('[btoa] input =', str);
console.log(new Error().stack);
return _btoa.call(this, str);
};
var _atob = window.atob;
window.atob = function (str) {
var result = _atob.call(this, str);
console.log('[atob] input =', str, ', output =', result);
console.log(new Error().stack);
return result;
};
})();
16.6 XHR 断点触发后顺着调用栈追溯(流程说明)
// 逆向定位流程:
// 1. 用 XHR URL Hook 或 XHR Header Hook 让请求断下
// 2. 打开 DevTools -> Sources -> Call Stack 面板
// 3. 从栈底往上逐帧查看,跳过 jQuery/axios/webpack 等库的帧
// 4. 找到业务代码帧,双击定位到对应源码行
// 5. 在那一行附近搜索参数名(如 sign、token),找到生成逻辑
// 6. 在生成处打断点,重放请求,单步执行追踪
17. 其他实用 Hook
17.1 阻止 window.close 和 history 跳转
// 使用场景:阻止目标网站动态关闭标签页或强制跳转历史
(function () {
window.close = function () {};
window.history.go = function () {};
window.history.back = function () {};
})();
17.2 固定 window 尺寸(绕过尺寸检测)
// 使用场景:某些网站通过检测窗口尺寸判断是否打开了 DevTools,固定尺寸可绕过
(function () {
var FIXED_HEIGHT = 660;
var FIXED_WIDTH = 1366;
var hProp = Object.getOwnPropertyDescriptor(window, 'innerHeight');
Object.defineProperty(window, 'innerHeight', {
get: function () { return FIXED_HEIGHT; },
set: function () { if (hProp && hProp.set) hProp.set.call(window, FIXED_HEIGHT); }
});
var wProp = Object.getOwnPropertyDescriptor(window, 'innerWidth');
Object.defineProperty(window, 'innerWidth', {
get: function () { return FIXED_WIDTH; },
set: function () { if (wProp && wProp.set) wProp.set.call(window, FIXED_WIDTH); }
});
})();
17.3 Hook Image.src(监控埋点和追踪像素)
// 使用场景:某些埋点通过 new Image().src = url 发请求,hook 可截获
(function () {
var prop = Object.getOwnPropertyDescriptor(Image.prototype, 'src');
var _nativeSet = prop.set;
Object.defineProperty(Image.prototype, 'src', {
get: prop.get,
set: function (url) {
console.log('[Image.src]', url);
console.log(new Error().stack);
return _nativeSet.call(this, url);
}
});
})();
17.4 Hook window.open
// 使用场景:阻止页面自动弹出新窗口,或监控弹窗 URL
(function () {
var _open = window.open;
window.open = function (url) {
console.log('[window.open]', url);
console.log(new Error().stack);
// return _open.call(this, url); // 注释掉则阻止弹窗
};
})();
17.5 格式化检测绕过(防检测 toString)
// 使用场景:部分网站检测函数是否被格式化(代码是否被美化),通过伪装 toString 绕过
// 原理:让被 hook 的函数的 toString 返回原生代码字符串,欺骗检测
function makeNative(fn, nativeFn) {
fn.toString = function () {
return nativeFn.toString();
};
return fn;
}
// 示例:Hook eval 并伪装
var _eval = window.eval;
window.eval = makeNative(function (src) {
console.log('[eval]', src);
return _eval(src);
}, _eval);
18. 环境 Hook 模块(Node.js 补环境)
在 Node.js 本地运行浏览器加密 JS 时使用,补全浏览器环境并对全局对象挂载深度 Proxy。
功能列表:
| 功能 | 说明 |
|---|---|
| 深度 Proxy | 对任意对象/函数做递归代理,追踪 get/set/has/call |
| 浏览器环境模拟 | 补全 window/navigator/document/localStorage |
| 定时器拦截 | 记录 setTimeout/setInterval 调用 |
| 全局对象代理 | 对 window/document/navigator 等批量挂载 Proxy |
| 防重复安装 | 通过 __envHookInstalled 标志防止重复执行 |
18.1 配置项
const CONFIG = {
enableStack: true, // 是否在日志中输出调用栈
deepProxy: true, // 是否对 get 返回值递归代理(深度追踪,会产生大量日志)
logGet: true, // 记录属性读取
logSet: true, // 记录属性写入
logHas: false, // 记录 in 操作符检测(通常不需要)
logCall: true // 记录函数调用
};
18.2 核心代理 createProxy()
对任意对象/函数递归包裹 Proxy,追踪所有操作并记录访问路径。WeakMap 缓存避免对同一对象重复代理。
function createProxy(target, path = "") {
if (!isObject(target) && typeof target !== "function") return target;
if (proxyCache.has(target)) return proxyCache.get(target);
const handler = {
get(target, property, receiver) {
const value = Reflect.get(target, property, receiver);
if (CONFIG.logGet) {
console.log({ type: "get", path, property, valueType: typeof value, stack: safeStack() });
}
// deepProxy: 对返回值也代理,可以追踪链式调用
if (CONFIG.deepProxy) return createProxy(value, `${path}.${String(property)}`);
return value;
},
set(target, property, value, receiver) {
if (CONFIG.logSet) console.log({ type: "set", path, property, value });
return Reflect.set(target, property, value, receiver);
},
has(target, property) {
if (CONFIG.logHas) console.log({ type: "has", path, property });
return Reflect.has(target, property);
},
apply(target, thisArg, args) {
if (CONFIG.logCall) console.log({ type: "call", path, args, stack: safeStack() });
return Reflect.apply(target, thisArg, args);
}
};
const proxy = new Proxy(target, handler);
proxyCache.set(target, proxy);
return proxy;
}
18.3 浏览器环境模拟 patchBrowserEnv()
function patchBrowserEnv() {
if (!global.window) global.window = global;
if (!global.navigator) {
global.navigator = {
userAgent: "Mozilla/5.0",
platform: "Win32",
languages: ["zh-CN", "zh"],
webdriver: false // 关键:不能为 true,否则触发反爬检测
};
}
if (!global.document) {
global.document = {
cookie: "",
createElement: () => ({})
};
}
if (!global.localStorage) {
const store = {};
global.localStorage = {
getItem: k => store[k],
setItem: (k, v) => store[k] = v,
removeItem: k => delete store[k]
};
}
}
18.4 完整代码
将此代码放在目标 JS 文件最顶部,在 require 或 eval 之前执行。
(function () {
if (global.__envHookInstalled) return;
global.__envHookInstalled = true;
const CONFIG = {
enableStack: true,
deepProxy: true,
logGet: true,
logSet: true,
logHas: false,
logCall: true
};
const proxyCache = new WeakMap();
function safeStack() {
if (!CONFIG.enableStack) return null;
return new Error().stack;
}
function isObject(v) {
return typeof v === "object" && v !== null;
}
function createProxy(target, path = "") {
if (!isObject(target) && typeof target !== "function") return target;
if (proxyCache.has(target)) return proxyCache.get(target);
const handler = {
get(target, property, receiver) {
const value = Reflect.get(target, property, receiver);
if (CONFIG.logGet) {
console.log({ type: "get", path, property, valueType: typeof value, stack: safeStack() });
}
if (CONFIG.deepProxy) return createProxy(value, `${path}.${String(property)}`);
return value;
},
set(target, property, value, receiver) {
if (CONFIG.logSet) console.log({ type: "set", path, property, value });
return Reflect.set(target, property, value, receiver);
},
has(target, property) {
if (CONFIG.logHas) console.log({ type: "has", path, property });
return Reflect.has(target, property);
},
apply(target, thisArg, args) {
if (CONFIG.logCall) console.log({ type: "call", path, args, stack: safeStack() });
return Reflect.apply(target, thisArg, args);
}
};
const proxy = new Proxy(target, handler);
proxyCache.set(target, proxy);
return proxy;
}
function patchBrowserEnv() {
if (!global.window) global.window = global;
if (!global.navigator) {
global.navigator = {
userAgent: "Mozilla/5.0", platform: "Win32",
languages: ["zh-CN", "zh"], webdriver: false
};
}
if (!global.document) {
global.document = { cookie: "", createElement: () => ({}) };
}
if (!global.localStorage) {
const store = {};
global.localStorage = {
getItem: k => store[k],
setItem: (k, v) => store[k] = v,
removeItem: k => delete store[k]
};
}
}
function patchTimers() {
const _setTimeout = global.setTimeout;
const _setInterval = global.setInterval;
global.setTimeout = (fn, delay, ...args) => { console.log("setTimeout", delay); return _setTimeout(fn, delay, ...args); };
global.setInterval = (fn, delay, ...args) => { console.log("setInterval", delay); return _setInterval(fn, delay, ...args); };
}
function hookGlobals(list) {
for (const name of list) {
if (!(name in global)) continue;
const original = global[name];
Object.defineProperty(global, name, {
configurable: true, enumerable: true,
get() { return createProxy(original, name); }
});
}
}
patchBrowserEnv();
patchTimers();
hookGlobals(["window", "document", "navigator", "location", "history", "screen", "localStorage", "sessionStorage"]);
console.log("Environment hook installed");
})();
当
deepProxy: true时,链式属性访问会产生大量日志。建议先设置CONFIG.deepProxy = false; CONFIG.logGet = false;只看 set 和 call,找到目标后再开启深度追踪。
场景速查表
| 目标 | 推荐 Hook |
|---|---|
| 定位 Cookie 生成 | Cookie Hook(6.1 / 6.2) |
| 定位 XHR 请求加密参数 | XHR send Hook(4.1) + JSON.stringify Hook(9.1) |
| 定位请求头(X-Sign 等)生成 | XHR Header Hook(4.3) |
| 定位 fetch 请求参数 | fetch 请求拦截(5.2) |
| 定位 localStorage token | localStorage 条件断点 Hook(7.3) |
| 监控 WebSocket 消息 | WebSocket Proxy Hook(10.1) |
| 绕过无限 debugger | 组合反调试绕过(8.5) |
| 分析动态混淆代码 | eval Hook(11.1) + Function Hook(11.3) |
| 阻止 Canvas 指纹采集 | toDataURL Hook(12.1) + getImageData Hook(12.2) |
| 定位 AES/DES 加密调用 | CryptoJS Hook(16.3) |
| 定位 RSA 加密调用 | JSEncrypt Hook(16.4) |
| 追踪 Base64 编码 | btoa/atob Hook(16.5) |
| 监控 DOM 动态注入 | MutationObserver(14.1) |
| 监控动态脚本加载 | HTMLScriptElement.src Hook(14.4) |
| 防止控制台被清空 | console.clear 阻止(15.3) |
| Node.js 本地补环境 | 环境 Hook 模块(18.4) |
| 监控对象全部属性访问路径 | watch()(1.4) |
最佳实践
Hook 必须在目标代码执行前注入:通过 Tampermonkey 设 @run-at document-start,或在 DevTools Sources → Snippets 中在页面加载前手动执行,否则原生函数已被调用,Hook 无法捕获历史调用。
按需选择 Hook 粒度:属性描述符 vs Proxy:对单个属性或已知键名用 Object.defineProperty,对整个对象的所有属性访问用 Proxy。Proxy 开销更高但覆盖面广,不要对高频调用对象无差别使用 Proxy。
// 仅 Hook 一个属性(开销低)
Object.defineProperty(document, 'cookie', {
get() { console.trace('cookie read'); return cookieValue; },
set(v) { console.trace('cookie write', v); cookieValue = v; }
});
// Hook 整个对象所有访问(覆盖广但开销高)
window = new Proxy(window, { get(t, k) { console.log('window.' + k); return t[k]; } });
条件 Hook 减少干扰:只在参数满足特定条件时插入 debugger,避免正常调用淹没真正的加密调用。
const orig = JSON.stringify;
JSON.stringify = function(val) {
if (JSON.stringify(val).includes('sign')) debugger; // 只在有 sign 字段时断点
return orig.apply(this, arguments);
};
恢复原函数要保存引用:Hook 前先把原函数存到变量,调试完后能恢复,避免影响页面正常功能。
const _origFetch = window.fetch;
window.fetch = function(...args) {
console.log('fetch:', args[0]);
return _origFetch.apply(this, args);
};
// 调试完后:window.fetch = _origFetch;
用快速索引表(文末)定位目标 Hook:本文末尾有按场景分类的快速查找表,遇到未知加密时先查表确定 Hook 类型,再找对应章节。
常见陷阱
陷阱:Hook 后目标函数返回 undefined 导致页面报错
现象: 注入 Hook 后,页面报错 Cannot read properties of undefined,原功能失效。
原因: Hook 函数忘记 return orig.apply(this, arguments),导致原函数不被调用,返回值丢失。
解决: 任何替换原函数的 Hook,最后一行必须 return 原函数调用结果。
// 错误:没有 return
window.btoa = function(s) {
console.log('btoa:', s);
orig_btoa(s); // 没有 return,调用方拿到 undefined
};
// 正确:return 原函数结果
window.btoa = function(s) {
console.log('btoa:', s);
return orig_btoa.call(this, s);
};
陷阱:Hook 不生效——目标代码用的是局部变量副本
现象: 已替换 window.btoa,但加密函数调用的 btoa 没有触发 Hook。
原因: 目标代码在模块作用域顶部执行了 const btoa = window.btoa,后续使用局部变量,Hook 替换 window.btoa 对它无效。
解决: 必须在目标代码执行之前注入 Hook;或改用 Object.defineProperty(globalThis, 'btoa', ...) 并设 configurable: false 阻止被覆盖。
陷阱:debugger 在 Hook 里无效
现象: Hook 函数里写了 debugger,但 DevTools 不暂停。
原因: DevTools 设置了"Pause on caught exceptions"且 debugger 被异常捕获;或者 DevTools 未打开,debugger 语句静默跳过;或者该脚本是 Worker 线程,需要在 Workers 面板调试。
解决: 确保 DevTools 已打开再刷新;检查是否在 Worker 上下文(URL 搜索 worker.js);尝试换成 console.trace() 先验证 Hook 是否被触发。