1. 为什么需要Hook Function构造器
当你打开Chrome开发者工具调试某些网站时,经常会遇到无限debugger暂停的问题,导致无法正常调试代码。这是因为目标网站的前端代码中植入了大量反调试代码,最常见的就是使用debugger语句。
传统的解决方案是直接禁用所有断点,但这会导致我们失去正常的调试能力。更优雅的方式是通过Hook Function.prototype.constructor来精准拦截并净化包含debugger的恶意代码,就像外科手术一样精确。
我在实际爬虫项目中遇到过这样一个案例:某电商网站使用了动态生成debugger语句的混淆代码,每隔几秒就会触发一次debugger。通过分析发现,他们是通过Function构造器动态生成包含debugger的代码。这时候就需要用到我们今天要介绍的Hook技术。
2. debugger反调试原理剖析
2.1 常见的debugger实现方式
网站开发者通常会用以下几种方式实现反调试:
- 直接插入debugger语句:
setInterval(function(){ debugger; }, 1000);- 通过Function构造器动态生成:
new Function("debugger")();- 使用eval执行包含debugger的字符串:
eval("deb" + "ugger");- 更隐蔽的拼接方式:
var a = "deb"; var b = "ugger"; setInterval(function(){ eval(a + b); }, 100);2.2 为什么Hook Function构造器有效
JavaScript中所有函数都是Function构造器的实例,包括通过function关键字定义的函数。当我们Hook Function.prototype.constructor时,实际上拦截了所有函数的创建过程。这让我们有机会在函数被创建时检查其内容,如果发现包含debugger就进行净化处理。
这种方法的优势在于:
- 精准拦截,不影响其他正常代码
- 无需禁用所有断点,保留调试能力
- 可以处理动态生成的debugger代码
- 兼容大多数现代浏览器
3. 油猴脚本实现详解
3.1 完整脚本代码
下面是一个完整的油猴脚本实现,可以直接安装使用:
// ==UserScript== // @name Anti Anti-debugger // @namespace http://tampermonkey.net/ // @version 1.0 // @description 通过Hook Function构造器绕过前端反调试 // @author YourName // @match *://*/* // @grant unsafeWindow // @run-at document-start // ==/UserScript== (function() { 'use strict'; // 保存原始Function构造器 var originalConstructor = unsafeWindow.Function.prototype.constructor; // Hook Function构造器 unsafeWindow.Function.prototype.constructor = function() { // 获取函数体内容 var fnBody = arguments[0]; if (fnBody && typeof fnBody === 'string') { // 检查是否包含debugger if (fnBody.includes('debugger')) { // 获取调用者信息(非标准实现,部分浏览器支持) var caller = Function.prototype.constructor.caller; if (caller) { var callerStr = caller.toString(); if (callerStr.includes('debugger')) { // 移除调用者中的所有debugger语句 callerStr = callerStr.replace(/\bdebugger\b/gi, ''); try { // 重新定义调用者函数 eval('caller = ' + callerStr); } catch(e) { console.warn('Failed to redefine caller:', e); } } } // 返回一个空函数替代原函数 return function(){}; } } // 正常情况使用原始构造器 return originalConstructor.apply(this, arguments); }; })();3.2 关键代码解析
@run-at document-start:确保脚本在页面加载前执行,尽早HookunsafeWindow:访问页面的原始window对象保存原始构造器:先备份原始Function.prototype.constructor,以便后续正常调用
检查函数体:分析传入的代码字符串是否包含debugger关键字
处理调用链:尝试找到并净化调用链上的debugger语句
返回空函数:对于包含debugger的函数,返回一个无害的空函数
4. 高级应用与优化
4.1 处理混淆代码
对于经过混淆的代码,简单的字符串匹配可能不够。我们可以增强检测逻辑:
// 增强版的debugger检测 function containsDebugger(code) { // 处理简单debugger if (code.includes('debugger')) return true; // 处理拆分的debugger var debuggerParts = ['de', 'bug', 'ger']; var combined = debuggerParts.join(''); if (code.includes(combined)) return true; // 处理通过字符编码表示的debugger var charCodes = [100, 101, 98, 117, 103, 103, 101, 114]; var fromCharCode = String.fromCharCode(...charCodes); if (code.includes(fromCharCode)) return true; return false; }4.2 性能优化
Hook Function构造器可能影响页面性能,特别是对于大量使用动态函数的网站。我们可以添加白名单机制:
// 域名白名单 var whitelist = [ 'trusted-site.com', 'cdn.google.com' ]; // 检查当前域名是否在白名单 function isWhitelisted() { return whitelist.some(function(domain) { return window.location.hostname.endsWith(domain); }); } // 修改Hook逻辑 if (!isWhitelisted()) { unsafeWindow.Function.prototype.constructor = function() { // Hook逻辑... }; }4.3 错误处理增强
添加更健壮的错误处理,避免脚本自身导致页面崩溃:
try { var originalConstructor = unsafeWindow.Function.prototype.constructor; unsafeWindow.Function.prototype.constructor = function() { try { // Hook逻辑... } catch (e) { console.error('Hook error:', e); return originalConstructor.apply(this, arguments); } }; } catch (e) { console.error('Initialization failed:', e); }5. 实际案例分析
5.1 电商网站反调试破解
某电商网站使用了如下反调试代码:
function createDebugger() { return new Function( 'var a="deb";var b="ugger";' + 'if(window.console){' + ' eval(a+b);' + '}' ); } setInterval(createDebugger(), 3000);我们的Hook脚本成功拦截了这种动态生成的debugger,因为:
- 检测到new Function调用
- 分析出拼接的debugger字符串
- 返回了空函数替代原函数
5.2 金融网站保护机制绕过
某金融网站使用了更复杂的保护:
var _0xad3b = ["\x64\x65\x62\x75\x67\x67\x65\x72"]; (function(_0x12cf8a, _0xad3b82) { var _0x5e4f4a = function(_0x1a8f00) { while (--_0x1a8f00) { _0x12cf8a["push"](_0x12cf8a["shift"]()); } }; _0x5e4f4a(++_0xad3b82); })(_0xad3b, 0x1a3); var _0x5e4f = function(_0x12cf8a, _0xad3b82) { _0x12cf8a = _0x12cf8a - 0x0; var _0x5e4f4a = _0xad3b[_0x12cf8a]; return _0x5e4f4a; }; setInterval(function() { var _0x1a8f00 = _0x5e4f("0x0"); new Function(_0x1a8f00)(); }, 0x7d0);即使在这种复杂混淆下,我们的Hook仍然有效,因为它最终还是要通过Function构造器执行。
6. 常见问题与解决方案
6.1 Hook后功能异常
有时Hook可能导致网站功能异常,解决方案:
- 检查是否是白名单网站
- 添加更精确的匹配条件
- 在特定条件下恢复原始构造器
var isDebugging = false; unsafeWindow.Function.prototype.constructor = function() { if (!isDebugging) { return originalConstructor.apply(this, arguments); } // 正常的Hook逻辑... };6.2 无法拦截eval执行的debugger
对于直接使用eval的情况,可以额外Hook eval:
var originalEval = unsafeWindow.eval; unsafeWindow.eval = function(code) { if (containsDebugger(code)) { console.log('Blocked debugger in eval'); return; } return originalEval.apply(this, arguments); };6.3 性能开销问题
对于性能敏感的场景:
- 限制Hook的作用范围
- 使用更高效的正则匹配
- 避免不必要的调用栈分析
// 简化的性能优化版 unsafeWindow.Function.prototype.constructor = function() { var fnBody = arguments[0]; if (typeof fnBody === 'string' && /d\s*e\s*b\s*u\s*g\s*g\s*e\s*r/i.test(fnBody)) { return function(){}; } return originalConstructor.apply(this, arguments); };7. 安全与伦理考量
在使用这类技术时,需要注意:
- 仅用于合法授权的测试
- 尊重网站的robots.txt协议
- 不要绕过合理的访问限制
- 遵守相关法律法规
技术本身是中立的,关键在于使用者的意图。作为开发者,我们应当把这类技术用于提升Web安全性,而不是滥用它。