旅行网站控制台检测

js 复制代码
(function() {
    'use strict';

    console.log('[Anti-Debug Bypass] 初始化反调试绕过...');

    // ==================== 方法1: 拦截 Function 构造函数 ====================
    const originalFunction = window.Function;
    const FunctionConstructorBackup = originalFunction.bind(null);

    window.Function = new Proxy(originalFunction, {
        apply(target, thisArg, args) {
            const body = args[args.length - 1];

            // 检测是否包含 debugger
            if (typeof body === 'string') {
                // 移除 debugger 语句
                if (body.includes('debugger')) {
                    console.log('[Anti-Debug Bypass] 阻止了 Function 构造函数中的 debugger');

                    // 替换为空语句或无害代码
                    const cleanedBody = body
                        .replace(/debugger/g, '//debugger removed')
                        .replace(/;\s*;/g, ';');

                    // 构建新的参数
                    const newArgs = [...args];
                    newArgs[newArgs.length - 1] = cleanedBody;
                    return target.apply(thisArg, newArgs);
                }
            }

            return target.apply(thisArg, args);
        },
        construct(target, args, newTarget) {
            const body = args[args.length - 1];

            if (typeof body === 'string' && body.includes('debugger')) {
                console.log('[Anti-Debug Bypass] 阻止了 Function 构造中的 debugger');

                const cleanedBody = body.replace(/debugger/g, '//debugger removed');
                const newArgs = [...args];
                newArgs[newArgs.length - 1] = cleanedBody;
                return target.construct(args, newTarget);
            }

            return target.construct(args, newTarget);
        }
    });

    // ==================== 方法2: 拦截 setInterval ====================
    const originalSetInterval = window.setInterval;
    window.setInterval = new Proxy(originalSetInterval, {
        apply(target, thisArg, args) {
            const fn = args[0];

            try {
                const fnStr = typeof fn === 'string' ? fn : (fn ? fn.toString() : '');

                if (fnStr.includes('debugger')) {
                    console.log('[Anti-Debug Bypass] 阻止了 setInterval 中的 debugger');
                    return NaN; // 返回无效的 timer ID
                }
            } catch (e) {}

            return target.apply(thisArg, args);
        }
    });

    // ==================== 方法3: 拦截 setTimeout ====================
    const originalSetTimeout = window.setTimeout;
    window.setTimeout = new Proxy(originalSetTimeout, {
        apply(target, thisArg, args) {
            const fn = args[0];

            try {
                const fnStr = typeof fn === 'string' ? fn : (fn ? fn.toString() : '');

                if (fnStr.includes('debugger')) {
                    console.log('[Anti-Debug Bypass] 阻止了 setTimeout 中的 debugger');
                    return NaN;
                }
            } catch (e) {}

            return target.apply(thisArg, args);
        }
    });

    // ==================== 方法4: 拦截 requestAnimationFrame ====================
    const originalRAF = window.requestAnimationFrame;
    window.requestAnimationFrame = new Proxy(originalRAF, {
        apply(target, thisArg, args) {
            try {
                if (args[0] && args[0].toString && args[0].toString().includes('debugger')) {
                    console.log('[Anti-Debug Bypass] 阻止了 rAF 中的 debugger');
                    return NaN;
                }
            } catch (e) {}

            return target.apply(thisArg, args);
        }
    });

    // ==================== 方法5: 欺骗 DevTools 检测 ====================
    // 伪造窗口尺寸,使 outerWidth === innerWidth
    Object.defineProperty(window, 'outerWidth', {
        get: function() {
            return window.innerWidth;
        },
        configurable: true
    });

    Object.defineProperty(window, 'outerHeight', {
        get: function() {
            return window.innerHeight;
        },
        configurable: true
    });

    // ==================== 方法6: 禁用 eval 中的 debugger ====================
    const originalEval = window.eval;
    window.eval = new Proxy(originalEval, {
        apply(target, thisArg, args) {
            const code = args[0];

            if (typeof code === 'string' && code.includes('debugger')) {
                console.log('[Anti-Debug Bypass] 阻止了 eval 中的 debugger');

                // 移除 debugger 后执行
                const cleanedCode = code.replace(/debugger/g, '');
                if (cleanedCode.trim()) {
                    return target.apply(thisArg, [cleanedCode]);
                }
                return undefined;
            }

            return target.apply(thisArg, args);
        }
    });

    // ==================== 方法7: 禁止脚本清空我们的保护 ====================
    // 防止页面脚本删除我们设置的 hooks
    const originalDeleteProperty = Object.prototype.__lookupSetter__ ? Object.prototype.__lookupSetter__('delete') : null;

    // 保护关键的 hook 函数
    Object.defineProperty(window, 'Function', { configurable: false, writable: false });
    Object.defineProperty(window, 'setInterval', { configurable: false, writable: false });
    Object.defineProperty(window, 'setTimeout', { configurable: false, writable: false });
    Object.defineProperty(window, 'eval', { configurable: false, writable: false });

    // ==================== 方法8: 禁用 console 访问检测 ====================
    let consoleAccessCount = 0;
    const originalConsole = Object.assign({}, console);

    // 创建假的 getter 来检测 console 访问
    Object.defineProperty(window, 'console', {
        get: function() {
            consoleAccessCount++;

            // 如果被频繁访问,可能是反调试检测
            if (consoleAccessCount > 100) {
                // 返回一个空的 console 对象
                return {
                    log: function() {},
                    warn: function() {},
                    error: function() {},
                    info: function() {},
                    table: function() {},
                    trace: function() {},
                    debug: function() {}
                };
            }

            return console;
        },
        set: function(val) {},
        configurable: false
    });

    console.log('[Anti-Debug Bypass] 反调试绕过已启用!');
    console.log('[Anti-Debug Bypass] 保护的方法: Function, setInterval, setTimeout, eval, requestAnimationFrame');

})();

// ==================== Playwright/Puppeteer 使用示例 ====================
/*
// Playwright:
await page.addInitScript(() => {
    // 将上面的绕过脚本放在这里
});

// Puppeteer:
await page.evaluateOnNewDocument(() => {
    // 将上面的绕过脚本放在这里
});
*/

// ==================== Selenium 使用示例 ====================
/*
// 使用 Chrome DevTools Protocol
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
    'source': '''
        // 将上面的绕过脚本放在这里
    '''
});
*/

这段代码是一个用于绕过网站反调试机制的JavaScript脚本。它通过拦截和修改关键的JavaScript内置函数和属性,阻止网站利用 debugger 语句进行无限断点调试,同时试图欺骗开发者工具的检测。下面我会详细解析代码的各个部分及其工作原理。


1. 反调试机制概述

根据注释,网站的反调试机制主要有三个特点:

  • 使用 Function('debugger')() 动态生成函数并执行,造成无限 debugger 中断。
  • 检测开发者工具是否打开(例如通过窗口尺寸差异、console 访问频率等)。
  • 使用递归函数持续检测,防止被简单绕过。

2. 绕过脚本的总体设计

脚本采用立即执行函数,在页面加载时注入,对多个可能导致 debugger 的全局函数进行代理(Proxy)拦截,并修改一些属性来欺骗检测。同时,它还试图保护自己的修改不被后续脚本覆盖。


3. 各绕过方法的详细解析

方法1:拦截 Function 构造函数

javascript 复制代码
const originalFunction = window.Function;
window.Function = new Proxy(originalFunction, { apply, construct });

原理Function 构造函数通常用于动态创建函数,例如 new Function('debugger') 会创建一个包含 debugger 语句的函数。脚本通过 Proxy 拦截 Function 的调用(apply)和构造(construct),检查最后一个参数(函数体)是否包含字符串 'debugger'。如果包含,则将其替换为 '//debugger removed' 或空语句,从而移除 debugger

优点 :直接消除了最经典的 debugger 注入方式。
局限 :如果网站使用 Function.prototype.constructor 而非直接调用 Function,或者函数体不直接包含 'debugger' 字符串(例如通过变量拼接),则可能无法拦截。


方法2~4:拦截 setIntervalsetTimeoutrequestAnimationFrame

javascript 复制代码
window.setInterval = new Proxy(originalSetInterval, { apply });
// 类似处理 setTimeout 和 requestAnimationFrame

原理 :这些定时器函数常被用来反复执行 debugger 函数。脚本通过检查传入的第一个参数(函数或字符串)的字符串表示是否包含 'debugger',如果包含则返回 NaN(无效的定时器ID),从而阻止 debugger 的执行。

优点 :可以阻止通过定时器持续触发的 debugger
局限 :如果传入的函数不是字符串,而是通过其他方式间接执行 debugger(例如调用另一个函数),则无法通过简单的字符串包含检测识别。


方法5:欺骗开发者工具窗口尺寸检测

javascript 复制代码
Object.defineProperty(window, 'outerWidth', {
    get: function() { return window.innerWidth; }
});

原理 :许多网站通过比较 window.outerWidthwindow.innerWidth 的差值来判断开发者工具是否打开(因为开发者工具会占用窗口部分区域)。脚本将 outerWidthouterHeightgetter 改为返回 innerWidth/Height,使得两者始终相等,从而欺骗这种检测。

优点 :简单有效,能绕过基于窗口尺寸的检测。
局限 :如果网站采用更复杂的检测(如检查 outerWidth - innerWidth 的精确值、监听 resize 事件或使用 devtools-detect 库的其他特征),则可能仍会被识别。


方法6:拦截 eval

javascript 复制代码
window.eval = new Proxy(originalEval, { apply });

原理 :类似于 Function 拦截,检查传入的代码字符串是否包含 'debugger',若有则移除后执行,若移除后为空则返回 undefined

优点 :防止通过 eval('debugger') 执行。
局限 :同样只适用于字符串直接包含 'debugger' 的情况。


方法7:保护修改后的函数不被重新定义或删除

javascript 复制代码
Object.defineProperty(window, 'Function', { configurable: false, writable: false });
// 类似处理 setInterval, setTimeout, eval

原理 :将修改后的全局函数设置为不可配置(configurable: false)和不可写(writable: false),防止后续脚本通过重新赋值或 delete 操作恢复原函数。

优点 :增强了绕过的稳定性。
局限:如果脚本在页面加载的极早期执行,后续代码可能会尝试重新定义这些属性,但由于设置为不可写,会静默失败(非严格模式下)或抛出错误(严格模式下)。这可能导致某些依赖这些函数的正常功能失效,但通常影响不大。


方法8:禁用 console 访问检测

javascript 复制代码
Object.defineProperty(window, 'console', {
    get: function() {
        consoleAccessCount++;
        if (consoleAccessCount > 100) {
            return { log: function() {}, ... }; // 返回空console
        }
        return console;
    }
});

原理 :一些网站会通过频繁访问 console 对象来检测开发者工具是否打开(例如在控制台输出内容)。脚本监控 console 的访问次数,一旦超过阈值(100次),就返回一个空的 console 对象,使得后续的 console 调用失效,从而隐藏调试痕迹。

优点 :能抑制部分基于 console 的检测。
局限 :阈值是硬编码的,可能不准确;且返回的空 console 会导致正常的调试输出丢失;另外,如果检测逻辑不是简单计数,而是每次调用都检查(例如 console.log 被重写),则此方法可能无效。


4. 整体绕过策略评价

  • 综合性 :脚本覆盖了多种常见的 debugger 注入方式(Function、定时器、eval)以及典型的开发者工具检测手段(窗口尺寸、console 访问),形成了一个相对全面的绕过方案。
  • 适用场景:特别适合在自动化工具(如 Playwright、Puppeteer、Selenium)中作为初始化脚本注入,使爬虫或自动化脚本能够顺利运行而不被反调试机制干扰。
  • 局限性
    • 字符串检测过于简单:如果反调试代码将 debugger 语句拆分或动态生成(例如 '\x64\x65\x62\x75\x67\x67\x65\x72'),则无法被识别。
    • 未处理 Web Workersiframe 中的反调试。
    • 部分方法可能影响正常功能(例如 console 劫持可能导致页面本身的调试信息丢失)。
    • 如果网站采用更先进的检测(如检测堆栈深度、执行时间差、DOM 元素变化等),该脚本可能无法绕过。

5. 使用示例

脚本末尾提供了在 Playwright、Puppeteer 和 Selenium 中注入的示例代码,方便开发者集成到自动化工具中。


总结

这段代码是一个针对同程旅行网站反调试机制的绕过脚本,通过拦截和修改关键 API、欺骗属性检测来阻止 debugger 执行并隐藏开发者工具的存在。它的设计思路清晰,覆盖了常见绕过点,但受限于简单的字符串匹配,仍可能被更隐蔽的反调试技术突破。在实际使用中,建议结合其他手段(如禁用 debugger 的浏览器启动参数、使用 undetected-chromedriver 等)以提高成功率。

相关推荐
小付同学呀1 小时前
C语言学习(四)——C语言变量、常量
c语言·开发语言
梦游钓鱼1 小时前
C++指针深度解析:核心概念与工业级实践
开发语言·c++
哆啦A梦15881 小时前
Vue3魔法手册 作者 张天禹 016_vue3中一些特定用法介绍
前端·vue.js·typescript
游乐码1 小时前
c#索引器
开发语言·c#
henry1010102 小时前
DeepSeek生成的网页小游戏 - 迷你高尔夫
前端·javascript·游戏·html
jaysee-sjc2 小时前
十三、Java入门进阶:异常、泛型、集合与 Stream 流
java·开发语言·算法
Maggie_ssss_supp2 小时前
Linux-python
开发语言·python
薛一半2 小时前
React的组件
前端·javascript·react.js