ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

无限 debugger 如何破解?

无限 debugger 如何破解? 做前端逆向、爬虫开发或者线上问题排查时很多人都遇到过这种场景刚按下 F12 打开开发者工具页面就立刻卡在 debugger 断点上点一下继续执行马上又断住循环往复根本没法正常调试代码。这就是前端最常见的反调试手段 ——无限 debugger。它的原理并不复杂本质是利用浏览器开发者工具开启时会执行debugger语句并中断执行的特性通过循环、递归等方式持续触发断点从而抬高代码分析的门槛。本文从原理、常见实现到不同层级的破解方案完整拆解无限 debugger 的应对思路。一、无限 debugger 的核心逻辑与常见实现所有无限 debugger 的底层逻辑都一致在检测到开发者工具打开的状态下以极低的间隔持续触发 debugger 语句。根据实现方式的隐蔽程度常见的有 5 种类型基础定时器型最原始也最常见的实现通过setInterval定时执行 debugger代码通常长这样setInterval(function () { debugger; }, 100);特点是逻辑简单、容易定位但也最容易被清除。动态执行型为了避免静态扫描识别出 debugger 关键字会用Function构造器、eval动态生成并执行代码setInterval(function() { Function(debugger)(); }, 100);代码里不会直接出现 debugger 字面量普通的字符串过滤会失效。递归调用型通过自执行函数递归调用自身触发断点不依赖定时器清除定时器的方法对它无效(function debugLoop() { debugger; requestAnimationFrame(debugLoop); })();条件触发型不会一直执行 debugger而是先通过各种手段检测开发者工具是否打开只有检测到开启时才启动无限断点平时完全隐藏隐蔽性更强。混淆加密型把反调试逻辑整体做代码混淆、字符串加密配合大量无效代码干扰debugger 点位分散在多个动态生成的 VM 脚本中很难定位源头。二、入门级破解浏览器原生功能快速绕过面对普通的无限 debugger用浏览器自带的功能就能快速解决适合临时快速调试的场景。1. 一键禁用所有断点这是最简单粗暴的方法打开开发者工具的「Sources」面板点击顶部的「停用断点」图标或直接按快捷键Ctrl F8/Cmd F8。 开启后所有断点包括 debugger 都会失效页面可以正常运行。优点操作零成本一秒生效缺点自己也无法设置断点调试适合只看页面运行逻辑、不需要断点分析的场景2. 单点位永久跳过Never pause here当 debugger 第一次断住时在调用栈中找到触发 debugger 的那一行代码右键点击左侧行号选择「Never pause here」永不中断。 浏览器会自动给这一行添加一个条件为false的条件断点之后执行到这里永远不会断住。优点不影响自己设置其他断点针对性强缺点只对固定行号的代码有效如果是动态生成的 VM 脚本、多位置触发的 debugger需要逐个添加三、进阶级破解Hook 原生 API 从源头拦截面对动态生成、多点位触发的无限 debugger更高效的方式是在代码执行前劫持浏览器原生 API从源头拦截 debugger 的生成。1. 暴力清除所有定时器绝大多数无限 debugger 都依赖定时器驱动批量清除所有定时器 ID 就能直接终止循环// 暴力清除 1~9999 范围内的所有定时器 for (let i 1; i 9999; i) { clearInterval(i); clearTimeout(i); }注意这种方法会把页面正常业务逻辑的定时器也一起清除可能导致页面功能异常仅适合纯调试逻辑、不关心页面正常运行的场景。2. 劫持定时器过滤反调试回调更优雅的方式是重写setInterval/setTimeout在定时器注册时就过滤掉包含 debugger 的回调不影响正常业务定时器// 保存原生方法 const originalSetInterval window.setInterval; window.setInterval function (callback, delay) { // 字符串类型回调 if (typeof callback string callback.includes(debugger)) { return -1; // 返回无效ID不注册 } // 函数类型回调转字符串检测 if (typeof callback function) { if (callback.toString().includes(debugger)) { return -1; } } return originalSetInterval.apply(this, arguments); };3. 拦截动态执行Hook Function 与 eval针对用Function(debugger)()、eval动态执行的反调试代码可以重写构造函数和 eval 方法// Hook Function 构造器 const originalFunction Function; Function function (...args) { const code args.join( ); if (code.includes(debugger)) { // 遇到包含 debugger 的代码返回空函数 return function () {}; } return originalFunction.apply(this, args); }; // Hook eval const originalEval window.eval; window.eval function (code) { if (code.includes(debugger)) return; return originalEval.call(this, code); };这种方法能拦截绝大多数动态生成的 debugger 代码对 VM 匿名脚本也有效。四、高阶破解应对混淆加密的强反调试当网站使用代码混淆、动态加密、多文件分散反调试逻辑时上面的 Hook 方法可能被绕过这时需要更底层的解决方案。1. 本地文件替换Local Overrides这是通用性最强、一劳永逸的方案利用浏览器的「本地覆盖」功能用修改后的本地文件替换线上的 JS 文件。 操作步骤在 Network 面板找到包含反调试逻辑的 JS 文件右键选择「Save as」保存到本地文件夹打开本地文件删除或注释掉所有 debugger 相关的反调试代码打开开发者工具 Sources 面板切换到「Overrides」标签点击「Select folder for overrides」选择刚才的本地文件夹允许权限刷新页面浏览器就会用你修改后的本地文件替换线上文件所有反调试逻辑直接失效这种方法不受代码混淆、加密的影响只要能定位到源文件就能破解适合需要长期调试的站点。2. 油猴脚本前置注入如果反调试代码执行时机很早手动 Hook 来不及可以用油猴Tampermonkey脚本在页面加载前就注入 Hook 代码。 脚本设置run-at document-start确保在页面任何 JS 执行之前就完成原生 API 的劫持从最开始就拦截所有 debugger 逻辑。3. 条件断点过滤调用栈对于分散在多处、找不到源头的 debugger可以在 debugger 断住时添加条件断点通过调用栈特征跳过所有反调试断点// 条件断点内容调用栈中不包含反调试函数特征时才中断 !new Error().stack.includes(反调试函数名特征)适合已经定位到反调试函数名但不方便修改源码的场景。五、总结无限 debugger 是前端反调试体系里最基础的手段所有破解思路本质上都归为三类跳过执行、拦截生成、替换源码。临时快速调试用禁用断点、Never pause here 即可中等强度反调试用 Hook 原生 API 从源头拦截高强度混淆站点优先使用本地文件替换方案反调试和调试永远是成本对抗没有绝对无法破解的防护也没有万能的破解方法根据站点的防护强度选择对应的方案才是最高效的做法。
返回列表