Hook 代码段
Hook 的核心原理是利用 JavaScript 弱类型语言特性,用自定义函数替换原生函数,在调用时插入 debugger 或日志,从而定位加密逻辑。 两种核心实现方式: 监控某个对象上指定属性的读取、写入、删除操作,精确定位属性被谁写入。 代理整个对象,监控其上所有属性的读写操作,适合不知道属性名时的全量监控。 Hook 内置对象的原型方法,监控 String、Array、Object 等原生方法的调用。 优先用 XHR/fetch 断点定位,再向上溯源:不要一开始就 Hook 加密函数,先在 DevTools Network 面板开启"XHR/fet
官方文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
Hook 的核心原理是利用 JavaScript 弱类型语言特性,用自定义函数替换原生函数,在调用时插入 debugger 或日志,从而定位加密逻辑。
被 Hook 的函数必须和
debugger在同一个作用域内,否则 Hook 无效。Hook 代码必须在目标代码执行之前注入,推荐通过 Tampermonkey 的@run-at document-start保证优先级。
两种核心实现方式:
| 方式 | 适用场景 | 备注 |
|---|---|---|
Object.defineProperty |
监控对象属性读写 | 对已有属性可精确控制 getter/setter |
Proxy |
代理整个对象的所有操作 | 更全面,但需要通过代理对象访问才能触发 |
注入方式
| 方式 | 说明 |
|---|---|
| 控制台直接粘贴 | 刷新页面前在 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');
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;
var _onreadystatechange = this.onreadystatechange;
this.addEventListener('readystatechange', function () {
if (this.readyState === 4) {
console.log('[XHR response] url =', this._url);
console.log('[XHR response] status =', this.status);
// this.responseText 即为响应体文本
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);
};
// 拦截 onopen
ws.onopen = function (event) {
console.log('[WebSocket OPEN]');
if (_onmessage && ws._onopen) return ws._onopen.call(this, event);
};
// 保存到全局方便调试
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 });
}
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);
// Hook 各加密算法的 encrypt/decrypt
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);
场景速查表
| 目标 | 推荐 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) |
| 防止控制台被清空 | console.clear 阻止(15.3) |
最佳实践
优先用 XHR/fetch 断点定位,再向上溯源:不要一开始就 Hook 加密函数,先在 DevTools Network 面板开启"XHR/fetch breakpoints"匹配目标 URL,断到请求发出时,再通过调用栈往上找参数生成的位置,能快速缩小范围。
Hook 里用条件 debugger 而非无条件 debugger:加密函数往往被高频调用,无条件 debugger 会每次都暂停。先 console.log 观察参数规律,再加条件限制只在目标参数时暂停。
const orig = CryptoJS.AES.encrypt;
CryptoJS.AES.encrypt = function(data, key) {
// 只在数据包含 'token' 字段时断点
if (JSON.stringify(data).includes('token')) debugger;
return orig.apply(this, arguments);
};
Proxy Hook 优于 Object.defineProperty Hook:Object.defineProperty 只能拦截已知属性,Proxy 可以拦截动态属性和方法调用。对不确定属性名的对象用 Proxy,对已知属性用 defineProperty(开销更低)。
Cookie Hook 优先用 Object.defineProperty(document, 'cookie', ...):这是最可靠的 Cookie 读写监控方式,比 Hook XMLHttpRequest 更早拦截,且能同时捕获读和写。
保存原函数引用确保可恢复:始终用 const orig = target.method 保存原函数,调试完后 target.method = orig 恢复,避免影响后续正常功能的测试。
常见陷阱
陷阱:Hook fetch 后响应体只能读一次
现象: Hook 里 response.json() 打印了数据,但调用方再次调用 .json() 报错 body stream already read。
原因: Response 的 body 是流,只能消费一次。Hook 里读取后,原调用方就读不到了。
解决: 用 response.clone() 克隆响应,Hook 读克隆体,把原始响应传给调用方。
const origFetch = window.fetch;
window.fetch = async function(...args) {
const res = await origFetch.apply(this, args);
const clone = res.clone(); // 克隆,Hook 读克隆体
clone.json().then(d => console.log('fetch response:', d)).catch(() => {});
return res; // 返回原始响应给调用方
};
陷阱:Hook XMLHttpRequest.prototype.send 拿不到请求头
现象: Hook send 能打印 body,但加密参数在请求头里,看不到。
原因: 请求头通过 setRequestHeader 方法添加,send 触发时已经设置完成,但 Hook 的 send 拿不到已设置的头部集合(XHR 内部维护)。
解决: 同时 Hook XMLHttpRequest.prototype.setRequestHeader,在里面记录所有头部。
const headers = {};
const origSetHeader = XMLHttpRequest.prototype.setRequestHeader;
XMLHttpRequest.prototype.setRequestHeader = function(name, value) {
headers[name] = value;
if (name.toLowerCase().includes('sign')) debugger;
return origSetHeader.apply(this, arguments);
};
陷阱:无限 debugger 绕过后 Hook 失效
现象: 成功绕过无限 debugger,但同时注入的 Hook 也不触发了。
原因: 无限 debugger 往往通过 eval 或 Function 构造动态执行,绕过方式通常是 Hook 掉 eval/Function,导致所有动态代码都不执行,目标加密函数也在其中。
解决: 区分无限 debugger 代码和加密代码:只在 eval 内容包含 debugger 关键字时拦截,其他正常执行。
const origEval = eval;
window.eval = function(code) {
if (code.includes('debugger')) return; // 只拦截含 debugger 的动态代码
return origEval.call(this, code);
};