网页反调试debugger绕过方案:条件断点替换(最快)、油猴脚本Hook(一劳永逸)

网页反调试debugger绕过方案:条件断点替换(最快)、油猴脚本Hook(一劳永逸)

今天在网页抓包的时候碰到了反调试debugger代码,打开浏览器的开发者工具就会直接触发debugger反调试断点,网页就会卡在这里,无法请求接口数据,页面也无法正常加载。

针对这个问题,我们来详细说一下如何进行绕过处理,方便以后你们遇到这种问题时知道怎么解决。

本文目录

  1. 问题现象:打开F12就卡死
  2. 定位问题:查看调用堆栈
  3. 分析混淆代码:找到debugger入口
  4. 解决方案一:条件断点替换(推荐)
  5. 解决方案二:油猴脚本Hook(一劳永逸)
  6. 解决方案三:抓包工具(最暴力)
  7. 方案对比与总结

1. 问题现象:打开F12就卡死

今天在网页抓包的时候碰到了反调试debugger代码,打开浏览器的开发者工具就会直接触发debugger反调试断点,网页就会卡在这里,无法请求接口数据,页面也无法正常加载。

图1:打开开发者工具后触发的debugger断点

2. 定位问题:查看调用堆栈

我在网页上打开了开发者工具,可以看到已经触发了 debugger 反调试断点。同时这个断点停在一个 VM114792 的脚本中,这代表这段代码是动态生成并执行的,并非来自原始 JS 文件。

我们需要查看调用堆栈,看是在哪里执行的。可以看到 Call Stack 面板中的调用顺序是:

(匿名)→(匿名)→ _0x429e6f → _XPMRQ

其中 _XPMRQ 是最外层的调用函数。

图2:调用堆栈面板,红框标出调用顺序

3. 分析混淆代码:找到debugger入口

通过堆栈调用函数的名称,明显可以看出来这些代码是经过混淆的。这样的混淆代码可读性很差,需要一个一个去控制台还原才能知道大概它在做什么。

前面讲过的《小红薯X-S签名逆向解析(上篇):突破自定义Base64与算法轮廓》就是采用的这种混淆方式。

_XPMRQ 函数代码如下:

'ZVEcM': function(_0x42853b, _0x275a55) { return _0x42853b(_0x275a55); },
图3:_XPMRQ函数中的ZVEcM方法

这个函数的作用很简单:接收一个函数 _0x42853b 和一个参数 _0x275a55,然后执行 _0x42853b(_0x275a55)。而 _0x42853b 很可能就是 Function('debugger') 或类似的 debugger 生成函数。

4. 解决方案一:条件断点替换(推荐)

如果你动手能力强且有耐心,就直接向上跟栈,直到把 debugger 的调用入口函数跟出来,下一个条件断点,把这个函数重新赋值,返回一个空函数,就能把 debugger 反调试过掉。

例如我这个网站是在 _XPMRQ 里面调用的,我在 return _0x42853b(_0x275a55) 这一行插入如下条件断点

(_0x42853b = function(){ return function(){} }, false)

操作步骤:

  • 找到 return _0x42853b(_0x275a55) 这一行
  • 右键 → 添加条件断点(Add conditional breakpoint)
  • 粘贴 (_0x42853b = function(){ return function(){} }, false)
  • 回车,然后继续运行(F8)
图4:设置条件断点,输入框中显示代码

效果:_0x42853b 替换成返回空函数的函数,原来的 debugger 不会执行,网页可以正常抓包。

注意事项:时间戳导致断点失效

这种方法的有个前提:代码所在的文件必须是静态 JS 文件,而不是每次请求都动态生成的新文件。

如果你的条件断点下在了一个带时间戳参数的 JS 文件里(例如 xxx.js?_t=123456789),那么刷新页面后,浏览器会重新请求这个文件,新的请求会生成一个新的时间戳,相当于加载了一个全新的文件。你在旧文件上下过的断点,自然就管不到新文件了。

另外,如果反调试代码是在 VM 虚拟机中动态生成的,刷新后 VM 脚本也会重新生成,之前下的断点同样会失效。

解决办法: 遇到这种情况,建议改用油猴脚本方案(见第五节),或者直接用抓包工具绕过(见第六节)。

5. 解决方案二:油猴脚本Hook(一劳永逸)

如果你不会跟栈,或者觉得上面那种方法麻烦,我们还可以使用浏览器的油猴插件,写一段 Hook 代码直接把 debugger 给 Hook 掉。

Hook 掉以后,代码就会进入我们指定的逻辑执行,直接返回空函数,debugger 就不会触发了。

油猴脚本完整代码

// ==UserScript==
// @name         彻底禁用 debugger
// @namespace    http://tampermonkey.net/
// @version      1.0
// @match        *://*/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    console.log('%c[油猴] 反调试脚本已加载', 'color: #00ff00; font-size: 14px; font-weight: bold');

    // 1. 劫持 Function 构造器
    const OriginalFunction = Function;
    window.Function = function() {
        const args = [...arguments];
        const body = args[args.length - 1] || '';
        if (body === 'de' || body.includes('debugger')) {
            return function() {};
        }
        return new OriginalFunction(...args);
    };
    window.Function.prototype = OriginalFunction.prototype;

    // 2. 劫持 eval
    const OriginalEval = eval;
    window.eval = function(code) {
        if (typeof code === 'string' && (code === 'de' || code.includes('debugger'))) {
            return undefined;
        }
        return OriginalEval.call(this, code);
    };

    // 3. 覆盖全局 debugger(静默,无输出)
    window.debugger = function() {};

    // 4. 劫持构造函数
    const OriginalConstructor = Function.prototype.constructor;
    Function.prototype.constructor = function() {
        const args = [...arguments];
        const body = args[args.length - 1] || '';
        if (body.includes('debugger')) {
            return function() {};
        }
        return new OriginalConstructor(...args);
    };

    // 5. 劫持 setTimeout
    const OriginalSetTimeout = window.setTimeout;
    window.setTimeout = function(fn, delay, ...args) {
        if (typeof fn === 'string' && fn.includes('debugger')) {
            return 0;
        }
        return OriginalSetTimeout.call(this, fn, delay, ...args);
    };

    // 6. 劫持 setInterval
    const OriginalSetInterval = window.setInterval;
    window.setInterval = function(fn, delay, ...args) {
        if (typeof fn === 'string' && fn.includes('debugger')) {
            return 0;
        }
        return OriginalSetInterval.call(this, fn, delay, ...args);
    };

    // 7. 劫持 addEventListener
    const OriginalAddEventListener = EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener = function(type, listener, options) {
        if (typeof listener === 'function' && listener.toString().includes('debugger')) {
            return;
        }
        return OriginalAddEventListener.call(this, type, listener, options);
    };

    console.log('%c╔════════════════════════════════════════╗', 'color: #00ff00');
    console.log('%c║      ✅ 反调试脚本已完全生效 ✅       ║', 'color: #00ff00');
    console.log('%c╠════════════════════════════════════════╣', 'color: #00ff00');
    console.log('%c║  ✔ Function 构造器已劫持              ║', 'color: #00ccff');
    console.log('%c║  ✔ eval 已劫持                        ║', 'color: #00ccff');
    console.log('%c║  ✔ debugger 变量已覆盖                ║', 'color: #00ccff');
    console.log('%c║  ✔ setTimeout/setInterval 已劫持      ║', 'color: #00ccff');
    console.log('%c║  ✔ addEventListener 已劫持            ║', 'color: #00ccff');
    console.log('%c╚════════════════════════════════════════╝', 'color: #00ff00');
    console.log('%c[油猴] 🚀 现在可以放心打开开发者工具了', 'color: #00ff00; font-size: 14px; font-weight: bold');
})();

使用步骤

  • 安装油猴插件(Tampermonkey)
  • 点击插件图标 → 添加新脚本
  • 复制上面的代码,粘贴进去
  • Ctrl+S 保存
  • 刷新目标页面
图6:油猴脚本添加界面

注意事项

  • 部分网站会检测油猴脚本的注入行为,如果发现被拦截,可能会采取其他反制措施。遇到这种情况,可以尝试用 Chrome 的 Overrides 功能本地替换 JS 文件。
  • 油猴脚本的 @match 规则建议写成具体的目标网站域名,不要用 *://*/*,避免影响其他网站。

6. 解决方案三:抓包工具(最暴力)

还有一种方法,如果你只是为了单纯的抓包,不需要网页调试,我们可以使用第三方抓包工具,像 FiddlerCharles 这些。

你反调试代码能管得了浏览器,难道还能管得了我这个中间人?

Fiddler 方案

  • 打开 Fiddler,勾选 Decrypt HTTPS 解密 HTTPS 流量
  • 设置 Filters,只抓目标域名的包
  • 刷新页面,直接查看请求和响应
  • 完全不会被 debugger 卡住
图7:Fiddler抓包界面

Charles 方案

  • 打开 Charles,安装 SSL 证书
  • 设置 SSL Proxying,添加目标域名
  • 刷新页面,直接抓包

7. 方案对比与总结

三种方案的优缺点对比如下:

方案优点缺点适用场景
条件断点快速,不需要装插件文件带时间戳会失效临时调试
油猴脚本一劳永逸,刷新自动生效需要装油猴插件,可能被检测长期调试
抓包工具完全不受影响,最稳定只能抓包,不能调试JS纯抓包需求

遇到网页反调试 debugger,不用慌,按以下顺序尝试:

  1. 先用条件断点:在 return _0x42853b(_0x275a55) 处下条件断点 (_0x42853b = function(){ return function(){} }, false)
  2. 如果文件带时间戳或 VM 动态生成:用油猴脚本 Hook Function 构造器
  3. 如果只想抓包,不需要调试:直接上 Fiddler 或 Charles

三种方法总有一种能解决你的问题。

温馨提示:本文技术仅用于安全研究与技术学习,请严格遵守相关法律法规,不得用于任何非法用途。

下篇预告: 下一篇《JS逆向实战:某点评平台_signature参数逆向与请求模拟》中,我们将分析大众点评的请求加密体系,探讨Web端反爬策略的演变趋势,并提供完整的逆向方案。

Comments

No comments yet. Why don’t you start the discussion?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注