Web端切屏检测和反复制的实现和反制手段

目录:前端开发  |  标签:JavaScript  |  发表于:2024-06-20 21:10:31

Web端的考试系统中,为了防止考生用电脑切到其他页面或软件查资料,一般都会在考试时进入全屏并设置切屏检测功能,同时配合反复制等手段阻碍考生将题目复制到剪贴板。这篇文章介绍网页端实现切屏检测和反复制的原理,以及如何破解切屏检测和反复制机制。

实现切屏检测

实现切屏检测其实很简单,我们切屏时都会造成窗口失去焦点,浏览器JavaScript中也有对应的API来判断当前页面是否失去焦点。

下面例子中我们编写了一个简单的页面,点击「开始考试」按钮后,系统会进入全屏状态且监听window对象的blur事件,页面失去焦点时对应的事件函数会被回调。

index.html

<!DOCTYPE html>
<html lang="zh-cn">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>考试系统</title>
    <script src="jquery.min.js"></script>
    <script src="index.js"></script>
  </head>
  <body>
    <button id="begin-exam">开始考试</button>
    <div id="msg-box"></div>
  </body>
</html>

index.js

$(function () {
  function launchFullscreen(element) {
    if (element.requestFullscreen) {
      element.requestFullscreen();
    } else if (element.mozRequestFullScreen) {
      element.mozRequestFullScreen();
    } else if (element.webkitRequestFullscreen) {
      element.webkitRequestFullscreen();
    } else if (element.msRequestFullscreen) {
      element.msRequestFullscreen();
    }
  }

  $("#begin-exam").click(function () {
    launchFullscreen(document.documentElement);
    window.addEventListener("blur", function () {
      $("#msg-box").append(
        "<div>[" +
          new Date().toLocaleTimeString() +
          "]你切屏了,考试中禁止切屏!</div>"
      );
    });
  });
});

当我们切屏时系统就会给出警告。

实际上,除了window对象的blur,我们还有一些其它可以监听的位置来判断是否发生了切屏,例如document对象的mouseleave等。

破解切屏检测

破解切屏检测比较Low但绝对可靠的方式是使用虚拟机,不过虚拟机启动后比较耗系统资源,有些情况下我们也不方便在考试电脑上安装虚拟机软件和里面的操作系统。

实际上,在浏览器中破解切屏检测也十分容易,我们将blurmouseleave等事件的回调函数覆盖掉即可,此外为了将脚本注入到页面,我们可以使用油猴插件来实现。

具体例子代码如下。

// ==UserScript==
// @name         Fuck Examination
// @namespace    http://tampermonkey.net/
// @version      2024-06-20
// @description  try to take over the world!
// @author       You
// @match        *://*/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// ==/UserScript==

(function () {
  "use strict";
  Object.defineProperty(window, "onblur", {
    value: null,
    writable: false,
  });
  Object.defineProperty(document, "onblur", {
    value: null,
    writable: false,
  });
  Object.defineProperty(window, "onmouseleave", {
    value: null,
    writable: false,
  });
  Object.defineProperty(document, "onmouseleave", {
    value: null,
    writable: false,
  });
  Object.defineProperty(window, "onvisibilitychange", {
    value: null,
    writable: false,
  });
  Object.defineProperty(document, "onvisibilitychange", {
    value: null,
    writable: false,
  });

  const originalEventTargetAddEventListener =
    EventTarget.prototype.addEventListener;
  const originalHTMLElementAddEventListener =
    HTMLElement.prototype.addEventListener;
  const originalWindowAddEventListener = Window.prototype.addEventListener;
  const originalDocumentAddEventListener = Document.prototype.addEventListener;

  function allowCall(eventName) {
    return (
      eventName !== "visibilitychange" &&
      eventName !== "focus" &&
      eventName !== "focusin" &&
      eventName !== "focusout" &&
      eventName !== "blur" &&
      eventName !== "mouseleave"
    );
  }

  EventTarget.prototype.addEventListener = function (eventName, eventHandler) {
    if (allowCall(eventName)) {
      originalEventTargetAddEventListener.call(this, eventName, eventHandler);
    }
  };

  HTMLElement.prototype.addEventListener = function (eventName, eventHandler) {
    if (allowCall(eventName)) {
      originalHTMLElementAddEventListener.call(this, eventName, eventHandler);
    }
  };

  Window.prototype.addEventListener = function (eventName, eventHandler) {
    if (allowCall(eventName)) {
      originalWindowAddEventListener.call(this, eventName, eventHandler);
    }
  };

  Document.prototype.addEventListener = function (eventName, eventHandler) {
    if (allowCall(eventName)) {
      originalDocumentAddEventListener.call(this, eventName, eventHandler);
    }
  };
})();

启用插件脚本后,前面编写的切屏检测代码就不再起作用了。

破解反复制

很多考试系统还实现了反复制来阻碍考生复制试题去搜索引擎中查询。反复制通常由禁用鼠标拖动选取,禁用CTRL+C、右键菜单,再加上覆盖copy事件实现,反复制实现起来要考虑的更多,但破解反复制却更加简单,我们直接使用CSS取消禁用拖动选取,然后重新实现”按下CTRL+C,将选中文本写入剪贴板“的逻辑即可。

反制禁用鼠标拖动选取

CSS中,将user-select属性设置为none可以实现对该标签和子标签禁用鼠标拖动选取,我们将其还原为默认值auto即可反制这种效果。

setInterval(() => {
  // 将被设置了user-select:none的标签还原为默认值
  var inWrapEle = document.getElementById("app");
  if (inWrapEle) {
    inWrapEle.style.userSelect = "auto";
  }
}, 300);

代码中,假设我们要操作的标签ID为app,我们将其内联user-select样式设置为auto即可破解禁用拖动选取了。为了防止异步加载的试题在页面加载完成后再被设置为user-select: none,我们还使用了定时器来循环执行这段逻辑。

重新实现CTRL+C复制文本

反复制的实现可能覆盖了很多种能够触发复制的组合键或操作,此外还可能覆盖了JavaScript的copy事件,我们没必要针对这些手段逐一规避和反制,我们直接重新实现”按下CTRL+C,将选中文本写入剪贴板“的逻辑即可,下面是例子代码。

document.addEventListener("keydown", function (event) {
  if (event.ctrlKey && event.key === "c") {
    var selectedText = window.getSelection().toString();
    window.navigator.clipboard.writeText(selectedText);
    event.preventDefault();
  }
});

代码中,我们重新实现了CTRL+C组合键的代码逻辑,它向系统剪贴板写入当前拖动选取的内容。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap