无限debugger的原理与绕过

在上一节的学习过程中,你可能注意到了一个知识点——debugger 关键字的作用。debugger 是 JavaScript 中定义的一个专门用于断点调试的关键字,只要遇到它,JavaScript 的执行便会在此处中断,进人调试模式。

有了 debugger 这个关键字,我们就可以非常方便地对 JavaScript 代码进行调试,比如使用 JavaScriptHook 时,我们可以加入 debugger 关键字,使其在关键的位置停下来,以便查找逆向突破口。

但有时候,debugger 会被网站开发者利用,使其成为阻挠我们正常调试的拦路虎。

本节中,我们介绍一个案例来绕过无限 debugger。

案例介绍

我们先看一个案例,网址是 https://antispider8.scrape.center/ ,打开这个网站,一般操作和之前的网站没有什么不同。但是,一旦我们打开开发者工具,就发现它立即进入了断点模式,如图 11-57 所示。

image 2025 01 26 19 33 12 476
Figure 1. 图 11-57 进入断点模式

我们既没有设置任何断点,也没有执行任何额外的脚本,它就直接进入了断点模式。这时候我们可以点击 Resume script execution(恢复脚本执行)按钮,尝试跳过这个断点继续执行,如图 11-58 所示。

然而不管我们点击多少次按钮,它仍然一次次地进入断点模式,无限循环下去,我们称这样的情况为无限 debugger。

怎么办呢?似乎无法正常添加断点调试了,有什么解决办法吗?

办法当然是有的,本节中我们就来总结一下无限 debugger 的应对方案。

实现原理

我们首先要做的是找到无限 debugger 的源头。在 Sources 面板中可以看到,debugger 关键字出现在一个 JavaScript 文件里,这时点击左下角的格式化按钮,如图 11-59 所示。

图 11-59 点击 Sources 面板中的格式化按钮

格式化后的代码如图 11-60 所示,可以发现这里通过 setInterval 循环,每秒执行 1 次 debugger 语句。

图 11-60 每秒执行 1 次 debugger 语句

当然,还有很多类似的实现,比如无限 for 循环、无限 while 循环、无限递归调用等,它们都可以实现这样的效果,原理大同小异。

了解了原理,下面我们就对症下药吧!

禁用断点

因为 debugger 其实就是对应的一个断点,它相当于用代码显式地声明了一个断点,要解除它,我们只需要禁用这个断点就好了。

首先,我们可以禁用所有断点。全局禁用开关位于 Sources 面板的右上角,叫作 Deactivate breakpoints,如图 11-61 所示。

image 2025 01 26 19 39 10 551
Figure 2. 图 11-61 全局禁用开关

点击它,会发现所有的断点变成了灰色,如图 11-62 所示。

这时候我们再重新点击一下 Resume script execution 按钮,跳过当前断点,页面就不会再进入到无限 debugger 的状态了。

但是这种全局禁用其实并不是一个好的方案,因为禁用之后我们也无法在其他位置增加断点进行调试了,所有的断点都失效了。

这时候我们可以选择禁用局部断点。取消刚才的 Deactivate breakpoints 模式,页面会重新进入无限 debugger 模式,我们尝试使用另一种方法来跳过这个无限 debugger。

我们可能会想着去掉 Breakpoints 里勾选的断点,心想这样不就禁用了吗? 大家尝试取消勾选,如图 11-63 所示。

然而,取消之后再继续点击 Resume script execution 按钮,它依然不断地停在有 debugger 关键字的地方,并没有什么效果。

其实,Breakpoints 只代表了我们手动添加的断点。对于 debugger 关键字声明的断点,这里直接取消是没有用的。

这种情况下还有什么办法吗?

有的。我们可以先将当前 Breakpoints 里面的断点删除,然后在 debugger 语句所在的行的行号上单击鼠标右键,此时会出现一个快捷菜单,如图 11-64 所示。

这里会有一个 Never pause here 选项,意思是从不在此处暂停。选择这个选项,于是页面变成如图 11-65 所示的样子。

图 11-65 选择 Never pause here 选项后的页面

当前断点显示为橙色,并且断点前面多了一个 ? 符号,同时 Breakpoints 也出现了刚才添加的断点位置。这时再次点击 Resume script execution 按钮,就可以发现我们不会再进入无限 debugger 模式了。

当然,我们也可以选择另外一个选项 Add conditional breakpoin,如图 11-66 所示。

图 11-66 Add conditional breakpoint 选项

这个模式更加高级,我们可以设置进入断点的条件,比如在调试过程中,期望某个变量的值大于某个具体值的时候才停下来。但在本案例中,由于这里是无限循环,我们没有什么具体的变量可以作为判定依据,因此可以直接写一个简单的表达式来控制。

选择 Add conditional breakpoint 选项,直接填入 false 即可,如图 11-67 所示。

图 11-67 设置 Conditional breakpoint 为 false

此时其效果就和选择 Never pause here 选项一样,重新点击 Resume script execution 按钮,也不会进入无限 Debbugger 循环了。

替换文件

前文我们介绍过 Overrides 面板的用法,利用它我们可以将远程的 JavaScript 文件替换成本地的 JavaScript 文件,这里我们依然可以使用这个方法来对文件进行替换,替换成什么呢?

很简单,我们只需要在新的文件里面把 debugger 这个关键字删除。

我们将当前的 JavaScript 文件复制到文本编辑器中,删除或者直接注释掉 debugger 这个关键字,修改如下:

setInterval((function () {
    // debugger; // 可以直接删除此行或者注释此行
    console.log("debugger")
}))

打开 Sources 面板下的 Overrides 面板,将修改后的完整 JavaScript 文件复制进去,修改的内容如图 11-68 所示。

图 11-68 改后的 JavaScript 文件

替换完成之后,重新刷新网页,这时候发现不会进入无限 debugger 模式了。

另外,我们不仅可以使用 Charles、Fiddler 等抓包工具进行替换,也可以使用测览器插件 ReRes 等进行替换,还可以通过 Playwright 等工具使用 Request Interception 进行替换。这三种方式达成的效果是一致的,原理都是将在线加载的 JavaScript 文件进行替换,最终消除无限 debugger。

总结

本节讲解了无限 debugger 的绕过方案,包括禁用全局断点、条件断点、替换原始文件等,从这些操作中我们也可以学习到一些 JavaScript 逆向的基本思路,建议好好掌握本节内容。