JavaScript逆向之反制无限debugger陷阱

JavaScript 中的 debugger 是什么

JavaScript 中的 debugger 关键字是一个编程式的断点 。 它的作用是在代码中设置一个"暂停点"。当浏览器的开发者工具(或其他调试环境)处于打开状态时,代码执行到 debugger; 这一行就会自动暂停,就像您手动在代码行号旁边打了一个断点一样。

工作原理

  1. 触发条件: 当 JavaScript 引擎执行到 debugger; 语句时,它会检查当前是否有调试器(debugger)附加到了这个运行环境。
  2. 如果调试器已附加 :
    • 脚本的执行会立即暂停在 debugger; 这一行。
    • 如果是浏览器环境,通常会自动切换到开发者工具的 "源代码" (Sources) 面板。
    • 此时,您可以:
      • 检查变量 (Inspect Variables): 查看当前作用域(Scope)中所有变量的值。
      • 查看调用栈 (Call Stack): 了解函数是如何一层层调用到这里的。
      • 单步调试: 使用"单步跳过"(Step Over)、"单步进入"(Step In)、"单步跳出"(Step Out) 和 "恢复执行"(Resume) 等工具来逐行控制代码的运行。
  3. 如果调试器未附加 :
    • 如果用户没有打开开发者工具,那么 debugger; 这条语句不会产生任何效果,代码会像没看到它一样继续向下执行。

debugger 如何被用来对抗逆向分析

debugger 关键字之所以能极大地增加 JavaScript 逆向工程的困难,是因为它可以从一个开发辅助工具 转变为一个主动攻击和干扰逆向分析的"武器" 。逆向工程师的核心工具就是浏览器的开发者工具(DevTools),特别是其中的调试器。而反逆向技术的核心思想就是:让调试器变得不可用、极难用,或者欺骗调试器。debugger 关键字正是实现这一目的的绝佳手段。

高频定时器 + debugger 循环(最经典、最烦人)

这是最基础也是最直接的干扰方法。网站开发者会在代码中植入一个高频触发的定时器,这个定时器会不断地执行含有 debugger 的代码。

javascript 复制代码
setInterval(function() {
  debugger;
}, 100); // 每 100 毫秒执行一次

如这个网站:shanzhi.spbeen.com/,打开这个网站,一般操作和之前的网站没有什么不同。但是,一旦我们打开开发者工具,就发现它立即进入了断点模式,如下图所示: 此时我们可以点击 Resume script execution(恢复脚本执行)按钮,尝试跳过这个断点继续执行,然而不管我们点击多少次按钮,它仍然一次次地进入断点模式,无限循环下去,如下图所示:

函数劫持与代码混淆

这种方法更为隐蔽。它通过重写或修改一些常用函数(甚至是调试器自身会调用的函数)来植入 debugger。如逆向工程师最常用的手段就是通过 console.log 打印变量来分析逻辑。网站可以重写这个函数:

javascript 复制代码
const originalLog = console.log;
console.log = function(value) {
  debugger; // 只要你调用 console.log,就先暂停
  originalLog.call(console, value);
};

这样一来,逆向工程师的分析工具反而变成了触发陷阱的扳机。

利用 debugger 检测调试器

这种方法更进一步,它利用 debugger 语句来判断开发者工具是否被打开,并据此执行反制措施。

javascript 复制代码
function detectDebugger() {
  const startTime = new Date().getTime();
  
  debugger; // 关键点:如果 DevTools 打开,代码会在这里暂停
  
  const endTime = new Date().getTime();
  
  // 如果暂停时间超过了一个阈值(比如 500ms),
  // 就说明用户打开了调试器并且停留在了断点处
  if (endTime - startTime > 500) {
    console.log("检测到调试器!正在执行反制措施...");
    // 反制措施可以是:
    // 1. 无限刷新页面
    // window.location.reload();
    // 2. 污染关键变量,让程序逻辑出错
    // critical_variable = null;
    // 3. 向服务器发送警报
    // sendAlertToServer();
  }
}

如何反制这些技术?

当然,有攻就有防。逆向工程师也有对应的方法来绕过这些陷阱。

停用所有断点 (Deactivate Breakpoints)

禁用所有断点开关位于 Sources 面板的右上角,叫做 Deactivate breakpoints,如下图所示: 点击它,该按钮会被激活变成蓝色,可以暂时禁用所有断点,包括 debugger 语句。然而禁用之后我们也无法在其他位置增加断点进行调试了,所有的断点都失效了!

条件断点 / 从不在此处暂停

可以在 debugger; 所在行的行号上右键,选择"一律不在此处暂停"(Never pause here),浏览器就会忽略这一行的 debugger。

或者使用条件断点,设置一个永远为false的条件,来跳过这一行的debugger。

本地覆盖 (Local Overrides)

这是最强大的反制手段。在 Chrome DevTools 的"源代码"面板 > "替换"(Overrides) 标签页,你可以将网站的 JS 文件保存到本地。在源代码页面,右键文件标签,选择替换内容。然后,你可以直接在本地修改这个 JS 文件(例如,删除或注释掉所有 debugger 相关的代码),并让浏览器加载你修改后的版本,而不是服务器上的原始版本。注意修改后保存并刷新网页。

脚本注入

脚本注入就是我们常说的Hook,使用油猴 (Tampermonkey) 等浏览器扩展,在网站的 JS 执行之前注入你自己的 JS 代码,提前重写或"解毒"掉那些被污染的函数。如果debugger是由定时器产生的如setInterval,在执行之前可以在控制台置空这个函数。

javascript 复制代码
// 这里是业务代码和setInterval无关,所以直接置空即可
setInterval = function(){} 
相关推荐
小高0072 小时前
🔍说说对React的理解?有哪些特性?
前端·javascript·react.js
烛阴2 小时前
【TS 设计模式完全指南】懒加载、缓存与权限控制:代理模式在 TypeScript 中的三大妙用
javascript·设计模式·typescript
skykun2 小时前
今天你学会JS的类型转换了吗?
javascript
Lotzinfly2 小时前
8 个经过实战检验的 Promise 奇淫技巧你需要掌握😏😏😏
前端·javascript·面试
RoyLin2 小时前
TypeScript设计模式:单例模式
前端·后端·node.js
小公主2 小时前
我的第一个 React Flow 小实验
前端
RoyLin2 小时前
TypeScript设计模式:工厂方法模式
前端·后端·node.js
掘金酱2 小时前
🎉 2025年8月金石计划开奖公示
前端·人工智能·后端