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但绝对可靠的方式是使用虚拟机,不过虚拟机启动后比较耗系统资源,有些情况下我们也不方便在考试电脑上安装虚拟机软件和里面的操作系统。
实际上,在浏览器中破解切屏检测也十分容易,我们将blur
、mouseleave
等事件的回调函数覆盖掉即可,此外为了将脚本注入到页面,我们可以使用油猴插件来实现。
具体例子代码如下。
// ==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
,我们还使用了定时器来循环执行这段逻辑。
反复制的实现可能覆盖了很多种能够触发复制的组合键或操作,此外还可能覆盖了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
组合键的代码逻辑,它向系统剪贴板写入当前拖动选取的内容。