发布于

防切屏检测器:对抗在线考试和监控系统的技术实现

作者

防切屏检测器:对抗在线考试和监控系统的技术实现

在线考试、远程工作监控等场景中,切屏检测器被广泛应用于监控用户是否离开当前页面。本文将分析切屏检测的技术原理,并实现一个有效的防切屏检测器。

切屏检测原理分析

常见检测机制

// 切屏检测的核心机制分析
class ScreenSwitchDetectionAnalysis {
    constructor() {
        this.detectionMethods = [
            {
                name: "Page Visibility API",
                description: "通过document.visibilityState和visibilitychange事件检测页面是否可见",
                code: `
                document.addEventListener('visibilitychange', () => {
                    if (document.visibilityState === 'hidden') {
                        console.log('用户离开页面');
                    } else {
                        console.log('用户返回页面');
                    }
                });
                `
            },
            {
                name: "Window Focus/Blur Events",
                description: "通过window的focus和blur事件检测窗口是否获得或失去焦点",
                code: `
                window.addEventListener('blur', () => {
                    console.log('窗口失去焦点');
                });
                window.addEventListener('focus', () => {
                    console.log('窗口获得焦点');
                });
                `
            },
            {
                name: "Mouse Movement Tracking",
                description: "监控鼠标移动,判断用户是否活跃",
                code: `
                let lastActivity = Date.now();
                document.addEventListener('mousemove', () => {
                    lastActivity = Date.now();
                });
                setInterval(() => {
                    if (Date.now() - lastActivity > 5000) {
                        console.log('用户可能不活跃');
                    }
                }, 1000);
                `
            },
            {
                name: "Fullscreen Change Detection",
                description: "检测全屏状态变化",
                code: `
                document.addEventListener('fullscreenchange', () => {
                    if (document.fullscreenElement) {
                        console.log('进入全屏模式');
                    } else {
                        console.log('退出全屏模式');
                    }
                });
                `
            }
        ];
    }
    
    analyzeDetector(detectorCode) {
        const results = {
            usesVisibilityAPI: detectorCode.includes('visibilitychange') || 
                              detectorCode.includes('visibilityState'),
            usesWindowEvents: detectorCode.includes('window.addEventListener(\'blur\'') || 
                             detectorCode.includes('window.addEventListener(\'focus\''),
            usesMouseTracking: detectorCode.includes('mousemove'),
            usesFullscreenAPI: detectorCode.includes('fullscreenchange'),
            overallComplexity: 'Low'
        };
        
        // 评估检测器复杂度
        let complexityScore = 0;
        if (results.usesVisibilityAPI) complexityScore += 1;
        if (results.usesWindowEvents) complexityScore += 1;
        if (results.usesMouseTracking) complexityScore += 1;
        if (results.usesFullscreenAPI) complexityScore += 1;
        
        if (complexityScore >= 3) {
            results.overallComplexity = 'High';
        } else if (complexityScore >= 2) {
            results.overallComplexity = 'Medium';
        }
        
        return results;
    }
}

示例检测器分析

分析上面提供的切屏检测器代码,我们可以看到它主要使用了以下技术:

  1. Page Visibility API:通过 document.visibilitychange 事件检测页面是否可见
  2. Window Focus/Blur:通过 window.focuswindow.blur 事件检测窗口焦点变化
  3. 计时和记录:记录切屏次数、离开时间和总离开时间
  4. 警报机制:视觉和声音提醒用户已切屏
  5. 历史记录:记录用户切屏的时间和持续时间

防切屏检测器实现

核心防护策略

// anti-screen-switch-detector.js - 防切屏检测器核心实现
class AntiScreenSwitchDetector {
    constructor(options = {}) {
        this.options = {
            enableVisibilityProtection: true,
            enableFocusProtection: true,
            enableMouseSimulation: true,
            enableConsoleProtection: true,
            enableStorageProtection: true,
            ...options
        };
        
        this.isActive = false;
        this.originalMethods = {};
        this.intervalIds = [];
        
        this.initializeUI();
    }
    
    initializeUI() {
        // 创建控制面板
        const controlPanel = document.createElement('div');
        controlPanel.id = 'anti-detector-panel';
        controlPanel.style.cssText = `
            position: fixed;
            bottom: 10px;
            right: 10px;
            background: rgba(0, 0, 0, 0.7);
            color: #fff;
            padding: 10px;
            border-radius: 5px;
            font-family: Arial, sans-serif;
            font-size: 12px;
            z-index: 9999;
            transition: opacity 0.3s;
            opacity: 0.3;
        `;
        controlPanel.innerHTML = `
            <div style="margin-bottom: 5px; font-weight: bold;">防切屏检测器</div>
            <div id="anti-detector-status">状态: 未激活</div>
            <button id="anti-detector-toggle" style="margin-top: 5px; padding: 3px 8px;">启动保护</button>
        `;
        
        // 鼠标悬停时显示完整面板
        controlPanel.addEventListener('mouseenter', () => {
            controlPanel.style.opacity = '1';
        });
        
        controlPanel.addEventListener('mouseleave', () => {
            controlPanel.style.opacity = '0.3';
        });
        
        // 添加到页面
        document.body.appendChild(controlPanel);
        
        // 绑定事件
        const toggleButton = document.getElementById('anti-detector-toggle');
        toggleButton.addEventListener('click', () => {
            if (this.isActive) {
                this.deactivate();
                toggleButton.textContent = '启动保护';
            } else {
                this.activate();
                toggleButton.textContent = '停止保护';
            }
        });
        
        // 添加快捷键支持
        document.addEventListener('keydown', (e) => {
            // Ctrl+Shift+A 切换保护状态
            if (e.ctrlKey && e.shiftKey && e.key === 'A') {
                toggleButton.click();
            }
        });
    }
    
    activate() {
        if (this.isActive) return;
        
        this.isActive = true;
        document.getElementById('anti-detector-status').textContent = '状态: 已激活';
        
        // 应用所有保护措施
        if (this.options.enableVisibilityProtection) this.protectVisibilityAPI();
        if (this.options.enableFocusProtection) this.protectWindowFocus();
        if (this.options.enableMouseSimulation) this.simulateUserActivity();
        if (this.options.enableConsoleProtection) this.protectConsole();
        if (this.options.enableStorageProtection) this.protectStorage();
        
        console.log('%c[防切屏] 保护已激活', 'color: green; font-weight: bold;');
    }
    
    deactivate() {
        if (!this.isActive) return;
        
        this.isActive = false;
        document.getElementById('anti-detector-status').textContent = '状态: 未激活';
        
        // 恢复所有原始方法和事件
        this.restoreOriginalMethods();
        
        // 清除所有定时器
        this.intervalIds.forEach(id => clearInterval(id));
        this.intervalIds = [];
        
        console.log('%c[防切屏] 保护已停止', 'color: orange; font-weight: bold;');
    }
    
    protectVisibilityAPI() {
        // 保存原始属性描述符
        this.saveOriginalProperty(document, 'visibilityState');
        this.saveOriginalProperty(document, 'hidden');
        
        // 重写 visibilityState 和 hidden 属性
        Object.defineProperty(document, 'visibilityState', {
            get: () => 'visible'
        });
        
        Object.defineProperty(document, 'hidden', {
            get: () => false
        });
        
        // 阻止 visibilitychange 事件
        this.preventEvent(document, 'visibilitychange');
        
        console.log('[防切屏] Visibility API 保护已启用');
    }
    
    protectWindowFocus() {
        // 阻止 blur 和 focus 事件
        this.preventEvent(window, 'blur');
        this.preventEvent(window, 'focus');
        
        // 重写 document.hasFocus 方法
        this.saveOriginalMethod(document, 'hasFocus');
        document.hasFocus = () => true;
        
        console.log('[防切屏] Window Focus 保护已启用');
    }
    
    simulateUserActivity() {
        // 创建一个模拟鼠标移动的函数
        const simulateMouseMovement = () => {
            const event = new MouseEvent('mousemove', {
                view: window,
                bubbles: true,
                cancelable: true,
                clientX: Math.floor(Math.random() * window.innerWidth),
                clientY: Math.floor(Math.random() * window.innerHeight)
            });
            
            document.dispatchEvent(event);
        };
        
        // 定期模拟鼠标移动
        const intervalId = setInterval(simulateMouseMovement, 30000 + Math.random() * 10000);
        this.intervalIds.push(intervalId);
        
        console.log('[防切屏] 用户活动模拟已启用');
    }
    
    protectConsole() {
        // 保存原始控制台方法
        this.saveOriginalMethod(console, 'log');
        this.saveOriginalMethod(console, 'warn');
        this.saveOriginalMethod(console, 'error');
        
        // 过滤控制台输出,阻止检测相关日志
        const filterLog = (originalFn) => {
            return function(...args) {
                const message = args.join(' ');
                if (message.includes('切屏') || 
                    message.includes('离开') || 
                    message.includes('focus') || 
                    message.includes('blur') ||
                    message.includes('visibility')) {
                    return; // 阻止检测相关日志
                }
                return originalFn.apply(this, args);
            };
        };
        
        console.log = filterLog(this.originalMethods['console.log']);
        console.warn = filterLog(this.originalMethods['console.warn']);
        console.error = filterLog(this.originalMethods['console.error']);
        
        console.log('[防切屏] 控制台保护已启用');
    }
    
    protectStorage() {
        // 保存原始存储方法
        this.saveOriginalMethod(localStorage, 'setItem');
        this.saveOriginalMethod(localStorage, 'getItem');
        
        // 拦截存储操作,防止记录切屏数据
        localStorage.setItem = (key, value) => {
            if (key.includes('screen') || 
                key.includes('detector') || 
                key.includes('switch') ||
                key.includes('monitor')) {
                console.log(`[防切屏] 已阻止存储操作: ${key}`);
                return;
            }
            return this.originalMethods['localStorage.setItem'].call(localStorage, key, value);
        };
        
        console.log('[防切屏] 存储保护已启用');
    }
    
    // 辅助方法
    saveOriginalProperty(obj, prop) {
        const descriptor = Object.getOwnPropertyDescriptor(obj, prop);
        if (descriptor) {
            this.originalMethods[`${obj}.${prop}`] = descriptor;
        }
    }
    
    saveOriginalMethod(obj, method) {
        if (obj && obj[method]) {
            this.originalMethods[`${obj.constructor.name}.${method}`] = obj[method];
        }
    }
    
    restoreOriginalMethods() {
        // 恢复所有原始方法和属性
        for (const key in this.originalMethods) {
            const [objName, methodName] = key.split('.');
            
            if (objName === 'document' && (methodName === 'visibilityState' || methodName === 'hidden')) {
                // 恢复属性描述符
                Object.defineProperty(document, methodName, this.originalMethods[key]);
            } else {
                // 恢复方法
                const obj = this.getObjectByName(objName);
                if (obj && this.originalMethods[key]) {
                    obj[methodName] = this.originalMethods[key];
                }
            }
        }
        
        // 清空保存的方法
        this.originalMethods = {};
    }
    
    getObjectByName(name) {
        switch (name) {
            case 'document': return document;
            case 'window': return window;
            case 'console': return console;
            case 'localStorage': return localStorage;
            case 'Object': return Object;
            default: return window[name.toLowerCase()];
        }
    }
    
    preventEvent(target, eventName) {
        // 创建一个空的事件处理器来阻止事件传播
        const emptyHandler = (e) => {
            e.stopImmediatePropagation();
            e.preventDefault();
        };
        
        // 使用捕获阶段拦截事件
        target.addEventListener(eventName, emptyHandler, true);
        
        // 保存移除事件的函数
        const removeKey = `remove_${target}_${eventName}`;
        this.originalMethods[removeKey] = () => {
            target.removeEventListener(eventName, emptyHandler, true);
        };
    }
}

// 高级防护策略
class AdvancedAntiDetection extends AntiScreenSwitchDetector {
    constructor(options) {
        super(options);
        this.advancedOptions = {
            enableTabCloning: true,
            enablePeriodicRefocus: true,
            enableTimerProtection: true,
            ...options
        };
    }
    
    activate() {
        super.activate();
        
        // 应用高级保护措施
        if (this.advancedOptions.enableTabCloning) this.setupTabCloning();
        if (this.advancedOptions.enablePeriodicRefocus) this.setupPeriodicRefocus();
        if (this.advancedOptions.enableTimerProtection) this.protectTimers();
        
        console.log('[防切屏] 高级保护已激活');
    }
    
    setupTabCloning() {
        // 创建一个隐藏的iframe来保持页面活跃状态
        const iframe = document.createElement('iframe');
        iframe.style.cssText = 'position: absolute; width: 0; height: 0; border: 0; visibility: hidden;';
        iframe.src = location.href;
        
        document.body.appendChild(iframe);
        this.cloneIframe = iframe;
        
        console.log('[防切屏] 标签页克隆已启用');
    }
    
    setupPeriodicRefocus() {
        // 定期重新获取焦点
        const refocusInterval = setInterval(() => {
            window.focus();
        }, 10000);
        
        this.intervalIds.push(refocusInterval);
        console.log('[防切屏] 定期重新获取焦点已启用');
    }
    
    protectTimers() {
        // 保存原始定时器方法
        this.saveOriginalMethod(window, 'setInterval');
        this.saveOriginalMethod(window, 'setTimeout');
        
        // 拦截定时器创建,防止检测器的定时检查
        window.setInterval = (fn, delay, ...args) => {
            const fnStr = fn.toString().toLowerCase();
            
            // 检查是否为检测相关的定时器
            if (fnStr.includes('visibility') || 
                fnStr.includes('focus') || 
                fnStr.includes('blur') || 
                fnStr.includes('switch') ||
                fnStr.includes('detect')) {
                console.log('[防切屏] 已阻止可疑定时器');
                return -1; // 返回无效的定时器ID
            }
            
            return this.originalMethods['window.setInterval'].call(window, fn, delay, ...args);
        };
        
        // 类似地处理 setTimeout
        window.setTimeout = (fn, delay, ...args) => {
            const fnStr = typeof fn === 'function' ? fn.toString().toLowerCase() : '';
            
            if (fnStr.includes('visibility') || 
                fnStr.includes('focus') || 
                fnStr.includes('blur') || 
                fnStr.includes('switch') ||
                fnStr.includes('detect')) {
                console.log('[防切屏] 已阻止可疑延时任务');
                return -1;
            }
            
            return this.originalMethods['window.setTimeout'].call(window, fn, delay, ...args);
        };
        
        console.log('[防切屏] 定时器保护已启用');
    }
    
    deactivate() {
        super.deactivate();
        
        // 清理高级保护措施
        if (this.cloneIframe && this.cloneIframe.parentNode) {
            this.cloneIframe.parentNode.removeChild(this.cloneIframe);
            this.cloneIframe = null;
        }
        
        console.log('[防切屏] 高级保护已停止');
    }
}

使用方法

将以下代码添加到您的网页中,或通过浏览器控制台执行:

// 使用方法
(function() {
    // 创建并加载防切屏检测器脚本
    function loadAntiDetector() {
        // 检查是否已加载
        if (window.antiScreenSwitchDetector) {
            console.log('防切屏检测器已加载');
            return;
        }
        
        // 创建防切屏检测器实例
        window.antiScreenSwitchDetector = new AdvancedAntiDetection({
            enableVisibilityProtection: true,
            enableFocusProtection: true,
            enableMouseSimulation: true,
            enableConsoleProtection: true,
            enableStorageProtection: true,
            enableTabCloning: true,
            enablePeriodicRefocus: true,
            enableTimerProtection: true
        });
        
        // 自动激活
        window.antiScreenSwitchDetector.activate();
        
        console.log('防切屏检测器已加载并激活');
    }
    
    // 检查页面是否已完全加载
    if (document.readyState === 'complete') {
        loadAntiDetector();
    } else {
        window.addEventListener('load', loadAntiDetector);
    }
})();

防护效果分析

对抗常见检测机制

  1. Page Visibility API

    • 通过重写 document.visibilityStatedocument.hidden 属性,使页面始终显示为可见状态
    • 阻止 visibilitychange 事件的传播,防止检测器接收到页面可见性变化的通知
  2. Window Focus/Blur 事件

    • 阻止 window.blurwindow.focus 事件的传播
    • 重写 document.hasFocus() 方法,使其始终返回 true
    • 定期调用 window.focus() 确保窗口保持焦点状态
  3. 用户活动模拟

    • 定期触发鼠标移动事件,模拟用户活动
    • 随机化模拟事件的时间间隔和位置,增加真实性
  4. 存储和日志保护

    • 拦截 localStorage 操作,防止检测器记录切屏数据
    • 过滤控制台输出,阻止检测相关日志
  5. 定时器保护

    • 拦截 setIntervalsetTimeout,阻止可疑的检测定时器
    • 分析定时器回调函数,识别并阻止检测相关的定时任务

局限性

尽管我们的防切屏检测器能够对抗大多数常见的检测机制,但仍存在一些局限性:

  1. 无法对抗服务器端检测:如果网站使用服务器端技术检测用户活动,如定期发送心跳包或记录API调用频率,本工具无法有效对抗

  2. 无法对抗摄像头监控:一些在线考试系统使用摄像头监控用户,这超出了纯JavaScript防护的范围

  3. 可能被反反检测机制发现:高级检测系统可能会检测防护工具本身的存在

  4. 浏览器更新可能影响功能:浏览器安全策略更新可能会限制某些API的重写能力

结论

本文介绍的防切屏检测器提供了一种技术手段来保护用户隐私和自由。在使用此类工具时,请遵守相关法律法规和道德准则。

防切屏检测器的实现展示了Web API的灵活性和JavaScript运行时的可塑性,同时也反映了Web安全和隐私保护的复杂性。通过深入理解检测机制,我们能够开发出更有效的防护策略。


注意:本文仅供技术研究和学习目的,请在合法合规的前提下使用相关技术。