只是用于大家讨论怎么检测devtools
开启devtools的方法如下
- 按键 F12 CTRL+SHIFT+I
- 网页右键 触发右键菜单, 点击'检查'
- 浏览器空白页面提前打开devtools然后进行url跳转
代码如下,单个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>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
.container {
text-align: center;
padding: 40px 20px;
}
.warning {
color: #dc3545;
font-weight: bold;
margin: 20px 0;
}
.instructions {
text-align: left;
background-color: #f8f9fa;
padding: 20px;
border-radius: 5px;
margin-top: 30px;
}
.btn {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
margin-top: 20px;
}
.btn:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="container">
<h1>反调试功能测试页面</h1>
<p class="warning">此页面包含反调试保护功能,尝试打开开发者工具将触发保护机制</p>
<p>请尝试以下操作测试反调试效果:</p>
<ul class="instructions">
<li>按F12键尝试打开开发者工具</li>
<li>使用快捷键Ctrl+Shift+I (Windows) 或 Cmd+Opt+I (Mac)</li>
<li>右键点击页面尝试打开上下文菜单</li>
<li>尝试选中并复制页面文本</li>
</ul>
<button class="btn" onclick="showMessage()">测试按钮</button>
<p id="message"></p>
</div>
<script>
/**
* 反调试核心配置参数
* @typedef {Object} AntiDebugConfig
* @property {number} interval - 检测间隔时间(毫秒),默认500ms
* @property {boolean} disableMenu - 是否禁用右键菜单(防止通过右键打开开发者工具)
* @property {boolean} clearIntervalWhenDevOpenTrigger - 检测到调试行为后是否停止检测
* @property {number[]|"all"} detectors - 启用的探测器类型数组,"all"表示启用所有探测器
* @property {boolean} clearLog - 是否定期清除控制台日志(防止通过日志分析)
* @property {boolean} disableSelect - 是否禁用文本选择功能
* @property {boolean} disableCopy - 是否禁用复制功能
* @property {boolean} disableCut - 是否禁用剪切功能
* @property {boolean} disablePaste - 是否禁用粘贴功能
* @property {string} rewriteHTML - 检测到调试行为时重写页面的HTML内容
*/
/**
* 反调试核心配置(仅保留与DevTools监测相关参数)
* @type {AntiDebugConfig}
*/
const config = {
interval: 500,
disableMenu: true,
clearIntervalWhenDevOpenTrigger: false,
detectors: [1, 3, 4, 5, 6, 7],
clearLog: true,
disableSelect: false,
disableCopy: false,
disableCut: false,
disablePaste: false,
rewriteHTML: "<h1>检测到调试行为!</h1><p>已阻止开发者工具使用。</p>"
};
/**
* 浏览器环境信息
* @typedef {Object} BrowserInfo
* @property {boolean} pc - 是否为桌面端
* @property {boolean} firefox - 是否为Firefox浏览器
* @property {boolean} macos - 是否运行在macOS系统
* @property {boolean} iosChrome - 是否为iOS版Chrome浏览器
* @property {boolean} iosEdge - 是否为iOS版Edge浏览器
* @property {boolean} chrome - 是否为Chrome或兼容内核浏览器
* @property {boolean} mobile - 是否为移动端设备
*/
/**
* 检测浏览器环境信息(仅保留与调试监测相关的判断)
* @returns {BrowserInfo} 浏览器环境信息对象
*/
const browser = (() => {
const ua = navigator.userAgent.toLowerCase();
const isMobile = /(iphone|ipad|ipod|ios|android)/i.test(ua);
return {
pc: !isMobile,
firefox: ua.includes("firefox"),
macos: ua.includes("macintosh"),
iosChrome: ua.includes("crios"),
iosEdge: ua.includes("edgios"),
chrome: ua.includes("chrome") || ua.includes("crios"),
mobile: isMobile
};
})();
/**
* 探测器类型枚举
* @enum {number}
*/
const DetectorType = {
Unknown: -1,
RegToString: 0,
DefineId: 1,
Size: 2,
DateToString: 3,
FuncToString: 4,
Debugger: 5,
Performance: 6,
DebugLib: 7
};
/**
* 基础探测器类,所有具体探测器的父类
*/
class Detector {
/**
* 创建探测器实例
* @param {Object} options - 探测器配置
* @param {DetectorType} options.type - 探测器类型
* @param {boolean} [options.enabled=true] - 是否启用该探测器
*/
constructor({ type, enabled = true }) {
/** @type {DetectorType} 探测器类型 */
this.type = type;
/** @type {boolean} 是否启用探测器 */
this.enabled = enabled;
if (this.enabled) {
this.init();
}
}
/**
* 检测到开发者工具打开时触发的回调函数
*/
onDevToolOpen() {
console.warn(`检测到DevTools打开 [类型: ${this.type}]`);
handleDevToolOpen(this.type);
}
/**
* 初始化探测器(子类可重写此方法)
*/
init() {}
/**
* 执行检测逻辑(子类需重写此方法)
*/
detect() {}
}
/**
* DOM属性劫持探测器
* 通过劫持DOM元素的属性访问器,当开发者工具检查元素时触发检测
* @extends Detector
*/
class DefineIdDetector extends Detector {
/**
* 创建DOM属性劫持探测器实例
*/
constructor() {
super({ type: DetectorType.DefineId });
/** @type {HTMLDivElement|null} 用于检测的DOM元素 */
this.div = null;
}
/**
* 初始化探测器,创建隐藏的div并劫持其id属性
*/
init() {
this.div = document.createElement("div");
const self = this;
// 劫持div的id属性getter,当开发者工具访问该属性时触发检测
Object.defineProperty(this.div, "id", {
get: function() {
self.onDevToolOpen();
return "";
}
});
}
/**
* 执行检测,通过console.log触发属性访问
*/
detect() {
console.log(this.div);
}
}
/**
* 窗口尺寸差探测器
* 通过检测窗口内外尺寸差异判断开发者工具是否打开
* @extends Detector
*/
class SizeDetector extends Detector {
/**
* 创建窗口尺寸差探测器实例
*/
constructor() {
super({
type: DetectorType.Size,
enabled: !browser.edge // Edge浏览器中尺寸检测兼容性较差
});
}
/**
* 初始化探测器,绑定窗口大小变化事件
*/
init() {
this.checkWindowSizeUneven();
window.addEventListener("resize", () => {
setTimeout(() => this.checkWindowSizeUneven(), 100);
});
}
/**
* 检查窗口内外尺寸差异是否超过阈值
* 开发者工具打开时通常会导致明显的尺寸差异
*/
checkWindowSizeUneven() {
const ratio = window.devicePixelRatio || 1;
const widthDiff = window.outerWidth - window.innerWidth * ratio > 200;
const heightDiff = window.outerHeight - window.innerHeight * ratio > 300;
if (widthDiff || heightDiff) {
this.onDevToolOpen();
}
}
}
/**
* Date对象toString探测器
* 通过监测Date对象toString方法的调用次数判断开发者工具是否打开
* @extends Detector
*/
class DateToStringDetector extends Detector {
/**
* 创建Date对象toString探测器实例
*/
constructor() {
super({
type: DetectorType.DateToString,
enabled: !browser.iosChrome && !browser.iosEdge
});
/** @type {number} 方法调用计数器 */
this.count = 0;
/** @type {Date|null} 用于检测的Date对象 */
this.date = null;
}
/**
* 初始化探测器,重写Date对象的toString方法
*/
init() {
this.date = new Date();
const self = this;
this.date.toString = function() {
self.count++;
return "";
};
}
/**
* 执行检测,通过console.log触发toString调用
* 开发者工具打开时会导致多次调用
*/
detect() {
this.count = 0;
console.log(this.date);
if (this.count >= 2) {
this.onDevToolOpen();
}
}
}
/**
* 函数toString探测器
* 通过监测函数toString方法的调用次数判断开发者工具是否打开
* @extends Detector
*/
class FuncToStringDetector extends Detector {
/**
* 创建函数toString探测器实例
*/
constructor() {
super({
type: DetectorType.FuncToString,
enabled: !browser.iosChrome && !browser.iosEdge
});
/** @type {number} 方法调用计数器 */
this.count = 0;
/** @type {Function|null} 用于检测的函数 */
this.func = null;
}
/**
* 初始化探测器,重写函数的toString方法
*/
init() {
this.func = function() {};
const self = this;
this.func.toString = function() {
self.count++;
return "";
};
}
/**
* 执行检测,通过console.log触发toString调用
* 开发者工具打开时会导致多次调用
*/
detect() {
this.count = 0;
console.log(this.func);
if (this.count >= 2) {
this.onDevToolOpen();
}
}
}
/**
* Debugger语句探测器
* 通过debugger语句的执行耗时判断开发者工具是否打开
* @extends Detector
*/
class DebuggerDetector extends Detector {
/**
* 创建Debugger语句探测器实例
*/
constructor() {
super({
type: DetectorType.Debugger,
enabled: browser.iosChrome || browser.iosEdge
});
}
/**
* 执行检测,通过debugger语句的耗时判断是否有调试行为
* 开发者工具打开时会暂停执行,导致耗时增加
*/
detect() {
const start = performance.now();
debugger;
const end = performance.now();
if (end - start > 100) {
this.onDevToolOpen();
}
}
}
/**
* 性能耗时探测器
* 通过打印大型对象的性能差异判断开发者工具是否打开
* @extends Detector
*/
class PerformanceDetector extends Detector {
/**
* 创建性能耗时探测器实例
*/
constructor() {
super({
type: DetectorType.Performance,
enabled: browser.chrome || !browser.mobile
});
/** @type {number} 连续触发计数器 */
this.count = 0;
/** @type {Object[]|null} 用于性能测试的大型对象数组 */
this.largeObjectArray = null;
}
/**
* 初始化探测器,创建大型对象数组
*/
init() {
this.largeObjectArray = Array(50).fill().map(() => {
const obj = {};
for (let i = 0; i < 500; i++) {
obj[i] = i.toString();
}
return obj;
});
}
/**
* 执行检测,比较不同打印方式的性能差异
* 开发者工具打开时解析大型对象会导致显著的性能差异
*/
detect() {
/**
* 测量函数执行时间
* @param {Function} fn - 要执行的函数
* @returns {number} 执行时间(毫秒)
*/
const measureTime = (fn) => {
const start = performance.now();
fn();
return performance.now() - start;
};
const normalTime = measureTime(() => console.log(this.largeObjectArray));
const devTime = measureTime(() => console.table(this.largeObjectArray));
if (devTime > 10 * normalTime) {
this.count++;
if (this.count >= 2) {
this.onDevToolOpen();
}
}
}
}
/**
* 探测器类型与类的映射关系
* @type {Object.<DetectorType, typeof Detector>}
*/
const detectorMap = {
[DetectorType.DefineId]: DefineIdDetector,
[DetectorType.Size]: SizeDetector,
[DetectorType.DateToString]: DateToStringDetector,
[DetectorType.FuncToString]: FuncToStringDetector,
[DetectorType.Debugger]: DebuggerDetector,
[DetectorType.Performance]: PerformanceDetector
};
/**
* 处理检测到开发者工具打开的情况
* @param {DetectorType} type - 触发的探测器类型
*/
function handleDevToolOpen(type) {
if (config.clearIntervalWhenDevOpenTrigger) {
clearInterval(intervalId);
}
if (config.rewriteHTML) {
try {
document.documentElement.innerHTML = config.rewriteHTML;
} catch (e) {
document.documentElement.innerText = config.rewriteHTML;
}
}
}
/**
* 禁用可能打开开发者工具的入口点
* 包括右键菜单、快捷键等
*/
function disableDebugEntryPoints() {
// 禁用右键菜单
if (config.disableMenu) {
document.addEventListener("contextmenu", (e) => {
e.preventDefault();
return false;
});
}
// 禁用常用调试快捷键
document.addEventListener("keydown", (e) => {
const key = e.keyCode || e.which;
if (key === 123 || // F12
(e.ctrlKey && e.shiftKey && (key === 73 || key === 74)) || // Ctrl+Shift+I/J
(browser.macos && e.metaKey && e.altKey && (key === 73 || key === 74))) { // Mac Cmd+Opt+I/J
e.preventDefault();
return false;
}
});
// 禁用复制/剪切/粘贴
const events = [];
if (config.disableCopy) events.push("copy");
if (config.disableCut) events.push("cut");
if (config.disablePaste) events.push("paste");
events.forEach(event => {
document.addEventListener(event, (e) => {
e.preventDefault();
return false;
});
});
// 禁用文本选择
if (config.disableSelect) {
document.addEventListener("selectstart", (e) => {
e.preventDefault();
return false;
});
}
}
/** @type {number|null} 检测循环的定时器ID */
let intervalId = null;
/**
* 初始化反调试功能
* @param {Partial<AntiDebugConfig>} [options={}] - 自定义配置选项
*/
function initAntiDebug(options = {}) {
// 合并配置
Object.assign(config, options);
// 初始化探测器
const detectors = [];
const detectorTypes = config.detectors === "all"
? Object.values(DetectorType).filter(v => typeof v === "number" && v >= 0)
: config.detectors;
detectorTypes.forEach(type => {
const DetectorClass = detectorMap[type];
if (DetectorClass) {
detectors.push(new DetectorClass());
}
});
// 禁用调试入口
disableDebugEntryPoints();
// 启动检测循环
intervalId = setInterval(() => {
detectors.forEach(detector => {
if (detector.enabled) {
try {
detector.detect();
} catch (e) {}
}
});
if (config.clearLog) {
console.clear();
}
}, config.interval);
}
/**
* 页面测试按钮点击事件处理函数
*/
function showMessage() {
document.getElementById("message").textContent = "按钮点击成功!当前时间: " + new Date().toLocaleTimeString();
}
// 初始化反调试保护
initAntiDebug();
</script>
</body>
</html>