屏蔽 DevTools 检测

拦截 devtools-detector.js 脚本,禁用页面检测 DevTools 的功能。

目前为 2025-03-27 提交的版本。查看 最新版本

// ==UserScript==
// @name         屏蔽 DevTools 检测
// @namespace    https://github.com/LFWQSP2641/
// @version      1.1
// @description  拦截 devtools-detector.js 脚本,禁用页面检测 DevTools 的功能。
// @author       LFWQSP2641
// @match        *://*.chaoxing.com/*
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    // 1. 拦截 devtools-detector.js
    if (true) {
        const observer = new MutationObserver(() => {
            document.querySelectorAll('script[src*="devtools-detector.js"]').forEach(el => {
                console.log("Tampermonkey: 拦截 devtools-detector.js");
                el.remove();
            });
        });
        observer.observe(document.documentElement, { childList: true, subtree: true });
    }

    // 2. 覆盖 devtoolsDetector 对象
    if (false) {
        const script = document.createElement('script');
        script.textContent = `
            // 在全局作用域定义一个假的 devtoolsDetector 对象
            window.devtoolsDetector = {
                // 主要方法的模拟实现
                isLaunch: () => false,
                launch: () => {},
                stop: () => {},
                isOpen: false,
                addListener: () => {},
                removeListener: () => {},
                setDetectDelay: () => {},
                
                // 模拟检测器返回假结果
                _broadcast: () => {},
                _detectLoop: () => {},
                _isOpen: false,
                _detectLoopStopped: true
            };
            
            // 防止原始对象被重新定义
            Object.defineProperty(window, 'devtoolsDetector', {
                writable: false,
                configurable: false
            });
        `;

        // 将脚本注入页面
        document.documentElement.appendChild(script);
        document.documentElement.removeChild(script);
    }

    // 4. 多层防御,拦截关键的对象和方法。
    if (false) {
        // 在页面加载最开始就注入我们的代码
        const script = document.createElement('script');
        script.textContent = `
            // 1. 首先,保存原始的属性描述符方法,防止它们被篡改
            const originalDefineProperty = Object.defineProperty;
            const originalGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
            
            // 2. 创建一个假的 devtoolsDetector 对象
            const fakeDetector = {
                isLaunch: () => false,
                launch: () => {},
                stop: () => {},
                addListener: () => {},
                removeListener: () => {},
                setDetectDelay: () => {},
                _detectLoop: () => {},
                _broadcast: () => {},
                _detectLoopStopped: true,
                isOpen: false,
                _isOpen: false
            };
            
            // 3. 拦截全局 window 的 devtoolsDetector 属性设置
            originalDefineProperty(window, 'devtoolsDetector', {
                configurable: true,
                get: function() {
                    return fakeDetector;
                },
                set: function() {
                    // 忽略所有设置尝试
                    console.log("拦截到 devtoolsDetector 设置尝试");
                    return fakeDetector;
                }
            });
            
            // 4. 劫持函数构造器,防止检测脚本通过 Function 创建检测函数
            const originalFunction = Function;
            window.Function = function() {
                const fnBody = arguments[arguments.length - 1];
                if (typeof fnBody === 'string') {
                    // 检查函数体中是否包含特定关键词
                    if (fnBody.includes('debugger') || 
                        fnBody.includes('toString') && (fnBody.includes('isOpen') || fnBody.includes('devtools'))) {
                        // 返回一个无害的函数
                        return function() { return false; };
                    }
                }
                // 否则使用原始构造函数
                return originalFunction.apply(this, arguments);
            };
            
            // 5. 阻止定时器检测
            const originalSetTimeout = window.setTimeout;
            window.setTimeout = function(fn, delay) {
                if (typeof fn === 'function' && delay >= 100 && delay <= 1000) {
                    const fnStr = fn.toString();
                    if (fnStr.includes('devtools') || fnStr.includes('debugger') || fnStr.includes('_detectLoop')) {
                        // 替换为无操作的函数
                        fn = function() {};
                    }
                }
                return originalSetTimeout.apply(this, arguments);
            };
            
            // 6. 劫持 console.* 方法,因为一些检测器会利用这些方法
            if (window.console) {
                const originalLog = console.log;
                const originalTable = console.table;
                const originalClear = console.clear;
                
                console.log = function() {
                    // 过滤掉与检测相关的调用
                    const stackTrace = new Error().stack || '';
                    if (stackTrace.includes('devtools') || stackTrace.includes('detector')) {
                        return;
                    }
                    return originalLog.apply(this, arguments);
                };
                
                console.table = function() {
                    const stackTrace = new Error().stack || '';
                    if (stackTrace.includes('devtools') || stackTrace.includes('detector')) {
                        return;
                    }
                    return originalTable.apply(this, arguments);
                };
                
                console.clear = function() {
                    const stackTrace = new Error().stack || '';
                    if (stackTrace.includes('devtools') || stackTrace.includes('detector')) {
                        return;
                    }
                    return originalClear.apply(this, arguments);
                };
            }
            
            console.log('🛡️ DevTools 保护已激活');
        `;

        // 尽早添加到页面
        document.documentElement.appendChild(script);
        document.documentElement.removeChild(script);

        // 添加DOM过滤器,移除加载的检测脚本
        const observer = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (node.tagName === 'SCRIPT' &&
                        (node.src && (node.src.includes('devtools-detector') ||
                            node.src.includes('devtoolsDetector')))) {
                        console.log("已拦截检测脚本的加载");
                        node.remove();
                    }
                }
            }
        });

        observer.observe(document, { childList: true, subtree: true });
    }

    // 4.5. 4 的 ai 优化版本
    if (true) {
        // 在页面加载最开始就注入我们的代码
        const script = document.createElement('script');
        script.textContent = `
            // 保存原始方法,避免它们被篡改
            const originalDefineProperty = Object.defineProperty;
            const originalGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
            
            // 创建不可变的假 devtoolsDetector 对象
            const fakeDetector = Object.freeze({
                isLaunch: () => false,
                launch: () => {},
                stop: () => {},
                addListener: () => {},
                removeListener: () => {},
                setDetectDelay: () => {},
                _detectLoop: () => {},
                _broadcast: () => {},
                _detectLoopStopped: true,
                isOpen: false,
                _isOpen: false
            });
            
            // 拦截全局 window 的 devtoolsDetector 属性设置
            originalDefineProperty(window, 'devtoolsDetector', {
                configurable: false, // 防止属性被重新配置
                enumerable: true,
                get: function() {
                    return fakeDetector;
                },
                set: function() {
                    // 忽略所有设置尝试,静默失败
                    return fakeDetector;
                }
            });
            
            // 劫持函数构造器,防止通过 Function 创建检测函数
            const originalFunction = Function;
            window.Function = function() {
                const fnBody = arguments[arguments.length - 1];
                if (typeof fnBody === 'string') {
                    // 检查函数体中是否包含特定关键词
                    if (fnBody.includes('debugger') || 
                        (fnBody.includes('toString') && (fnBody.includes('isOpen') || fnBody.includes('devtools')))) {
                        return function() { return false; };
                    }
                }
                return originalFunction.apply(this, arguments);
            };
            // 保持原始函数的原型链
            window.Function.prototype = originalFunction.prototype;
            
            // 阻止定时器检测
            const originalSetTimeout = window.setTimeout;
            window.setTimeout = function(fn, delay) {
                // 只拦截可能的检测函数
                if (typeof fn === 'function' && delay >= 100 && delay <= 1000) {
                    try {
                        const fnStr = fn.toString();
                        if (fnStr.includes('devtools') || fnStr.includes('debugger') || fnStr.includes('_detectLoop')) {
                            fn = function() {};
                        }
                    } catch (e) {
                        // 防止异常中断脚本执行
                    }
                }
                return originalSetTimeout.apply(this, arguments);
            };
            
            // 劫持 console 方法,减少检测可能性
            if (window.console) {
                const methods = ['log', 'table', 'clear', 'trace', 'debug'];
                methods.forEach(method => {
                    if (typeof console[method] === 'function') {
                        const original = console[method];
                        console[method] = function() {
                            // 检测调用栈是否与检测相关
                            try {
                                const stackTrace = new Error().stack || '';
                                if (stackTrace.includes('devtools') || stackTrace.includes('detector')) {
                                    return;
                                }
                            } catch (e) {
                                // 防止异常破坏页面功能
                            }
                            return original.apply(this, arguments);
                        };
                    }
                });
            }
            
            // 记录激活状态,但避免明显特征
            const ts = Date.now();
            sessionStorage.setItem('_sys' + ts, '1');
        `;

        // 尽早添加到页面并立即移除
        (document.head || document.documentElement).appendChild(script);
        script.remove();

        // 添加DOM过滤器,移除加载的检测脚本
        const scriptFilter = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                if (mutation.type !== 'childList') continue;

                for (const node of mutation.addedNodes) {
                    if (node.nodeName === 'SCRIPT' &&
                        node.src &&
                        /devtools[-_]detector|devtoolsDetector/i.test(node.src)) {
                        console.log("已拦截检测脚本的加载");
                        node.remove();
                    }
                }
            }
        });

        // 使用更高效的选择器
        scriptFilter.observe(document, {
            childList: true,
            subtree: true
        });

        // 定时检查观察器是否正常运行
        const checkInterval = setInterval(() => {
            if (!scriptFilter || !document.body) return;

            if (!scriptFilter.takeRecords || scriptFilter.takeRecords().length === 0) {
                // 重新初始化观察器如果它停止工作
                scriptFilter.disconnect();
                scriptFilter.observe(document, { childList: true, subtree: true });
            }
        }, 30000);

        // 页面卸载时清理资源
        window.addEventListener('unload', () => {
            scriptFilter.disconnect();
            clearInterval(checkInterval);
        }, { once: true });
    }
    
    // 105. 拦截 XMLHttpRequest
    // (由于网页通过 <script> 标签直接加载 devtoolsDetector ,无法拦截)
    if (false) {
        // 重写 XMLHttpRequest
        const originalXHR = unsafeWindow.XMLHttpRequest;
        unsafeWindow.XMLHttpRequest = function () {
            const xhr = new originalXHR();
            const originalOpen = xhr.open;

            xhr.open = function () {
                const url = arguments[1];
                if (url && (
                    url.includes('devtools-detector') ||
                    url.includes('devtoolsDetector')
                )) {
                    // 使用空实现替换
                    xhr.addEventListener('readystatechange', function () {
                        if (xhr.readyState === 4) {
                            Object.defineProperty(xhr, 'responseText', {
                                value: 'window.devtoolsDetector = { isOpen: false, launch: function(){}, stop: function(){}, isLaunch: function(){ return false; }, addListener: function(){} };'
                            });
                        }
                    });
                }

                return originalOpen.apply(this, arguments);
            };

            return xhr;
        };
    }
})();