智能拦截 disable-devtool 反调试脚本,强制恢复 F12 和右键菜单
// ==UserScript==
// @name Anti-Disable-Devtool
// @namespace https://github.com/MissChina/anti-disable-devtool
// @version 12.0.0
// @description 智能拦截 disable-devtool 反调试脚本,强制恢复 F12 和右键菜单
// @author MissChina
// @license Personal Non-Commercial License
// @match *://*.hhkan0.com/*
// @match *://*.hhkan1.com/*
// @match *://*.hhkan2.com/*
// @match *://*.hhkan3.com/*
// @match *://*.hhkan4.com/*
// @match *://hhkan0.com/*
// @match *://hhkan1.com/*
// @match *://hhkan2.com/*
// @match *://hhkan3.com/*
// @match *://hhkan4.com/*
// @run-at document-start
// @grant none
// @icon https://github.com/MissChina/anti-disable-devtool/raw/main/icon.svg
// @homepageURL https://github.com/MissChina/anti-disable-devtool
// @supportURL https://github.com/MissChina/anti-disable-devtool/issues
// ==/UserScript==
// ================================================================
// 【Phase 0】直接在 Tampermonkey 上下文中执行(最快,不经过 DOM 注入)
// @grant none 时共享页面 JS 上下文,可以直接修改原型链
// 这段代码在 document-start 时刻立即执行,先于任何页面脚本
// ================================================================
(function() {
'use strict';
const BAD_DOMAINS = ['baidu.com', 'google.com', 'bing.com', 'theajack.github.io', '404.html', 'about:blank'];
const isBadUrl = (url) => {
if (!url) return false;
const s = String(url).toLowerCase();
return BAD_DOMAINS.some(d => s.includes(d));
};
// --- 1) 立即 hook Location.prototype(最关键) ---
try {
const L = Location.prototype;
const _assign = L.assign;
const _replace = L.replace;
L.assign = function(url) {
if (isBadUrl(url)) { console.log('[Anti-DD/P0] 阻止 assign:', url); return; }
return _assign.call(this, url);
};
L.replace = function(url) {
if (isBadUrl(url)) { console.log('[Anti-DD/P0] 阻止 replace:', url); return; }
return _replace.call(this, url);
};
const hrefDesc = Object.getOwnPropertyDescriptor(L, 'href');
if (hrefDesc && hrefDesc.set) {
const _hrefSet = hrefDesc.set;
Object.defineProperty(L, 'href', {
get: hrefDesc.get,
set: function(url) {
if (isBadUrl(url)) { console.log('[Anti-DD/P0] 阻止 href:', url); return; }
return _hrefSet.call(this, url);
},
configurable: false,
enumerable: true
});
}
} catch(e) { console.warn('[Anti-DD/P0] Location hook 失败:', e); }
// --- 2) 立即伪造 outerWidth / outerHeight(防止尺寸差检测触发跳转) ---
try {
Object.defineProperty(window, 'outerWidth', {
get: function() { return window.innerWidth; },
configurable: false
});
Object.defineProperty(window, 'outerHeight', {
get: function() { return window.innerHeight; },
configurable: false
});
} catch(e) {}
// 同时保护 screen 相关属性(某些检测用 screen.availWidth vs outerWidth)
try {
const origAvailWidth = screen.availWidth;
const origAvailHeight = screen.availHeight;
Object.defineProperty(screen, 'availWidth', {
get: function() { return window.innerWidth; },
configurable: false
});
Object.defineProperty(screen, 'availHeight', {
get: function() { return window.innerHeight; },
configurable: false
});
} catch(e) {}
// --- 3) Navigation API 终极拦截(Chrome 102+,能捕获所有导航方式) ---
try {
if (window.navigation) {
window.navigation.addEventListener('navigate', function(e) {
if (e.destination && isBadUrl(e.destination.url)) {
console.log('[Anti-DD/P0] Navigation API 拦截:', e.destination.url);
e.preventDefault();
}
});
}
} catch(e) {}
// --- 4) 锁定 window.location 赋值 ---
// 注意:现代浏览器中 window.location 是 unforgeable 属性,defineProperty 可能失败
// 但 Location.prototype.href 的 hook 已经能捕获 window.location.href = url
// 这里做额外尝试,失败也没关系
['window', 'self', 'top', 'parent'].forEach(function(name) {
try {
var obj = window[name === 'window' ? name : name];
if (name === 'top') obj = window.top;
if (name === 'self') obj = window.self;
if (name === 'parent') obj = window.parent;
if (!obj) return;
var loc = obj.location;
Object.defineProperty(obj, 'location', {
get: function() { return loc; },
set: function(url) {
if (isBadUrl(url)) {
console.log('[Anti-DD/P0] 阻止 ' + name + '.location =', url);
return;
}
loc.href = url;
},
configurable: false
});
} catch(e) {}
});
// --- 5) 拦截 window.open ---
var _open = window.open;
window.open = function(url) {
if (isBadUrl(url)) {
console.log('[Anti-DD/P0] 阻止 window.open:', url);
return null;
}
return _open.apply(this, arguments);
};
console.log('[Anti-DD/P0] Phase 0 反跳转保护已激活');
})();
// ================================================================
// 【Phase 1】页面注入方式 —— 完整的反调试 + 事件恢复
// ================================================================
const injectedCode = `(function() {
'use strict';
// ============================================================
// 保存原生方法引用
// ============================================================
const _addEventListener = EventTarget.prototype.addEventListener;
const _stopImmediatePropagation = Event.prototype.stopImmediatePropagation;
const _setTimeout = window.setTimeout;
const _setInterval = window.setInterval;
// ============================================================
// 恶意 URL 判定(与 Phase 0 一致)
// ============================================================
const BAD_DOMAINS = ['baidu.com', 'google.com', 'bing.com', 'theajack.github.io', '404.html', 'about:blank'];
const isBadUrl = (url) => {
if (!url) return false;
const s = String(url).toLowerCase();
return BAD_DOMAINS.some(d => s.includes(d));
};
// ============================================================
// 【事件层】强制恢复 F12 和右键菜单
// ============================================================
const isDevToolsKey = (e) => {
if (!e) return false;
if (e.keyCode === 123 || e.key === 'F12') return true;
if (e.ctrlKey && e.shiftKey && [73, 74, 67].includes(e.keyCode)) return true;
if (e.ctrlKey && !e.shiftKey && !e.altKey && e.keyCode === 85) return true;
if (e.keyCode === 116) return true;
return false;
};
// 捕获阶段注册(最高优先级)
_addEventListener.call(window, 'contextmenu', function(e) {
_stopImmediatePropagation.call(e);
}, true);
['keydown', 'keyup', 'keypress'].forEach(function(evtType) {
_addEventListener.call(window, evtType, function(e) {
if (isDevToolsKey(e)) {
_stopImmediatePropagation.call(e);
}
}, true);
});
_addEventListener.call(window, 'selectstart', function(e) {
_stopImmediatePropagation.call(e);
}, true);
_addEventListener.call(window, 'copy', function(e) {
_stopImmediatePropagation.call(e);
}, true);
// ============================================================
// 锁定 on* 属性
// ============================================================
const lockEventProps = (obj, name) => {
['oncontextmenu', 'onkeydown', 'onkeyup', 'onkeypress', 'onselectstart', 'oncopy', 'ondragstart'].forEach(prop => {
try {
Object.defineProperty(obj, prop, {
get: () => null,
set: () => true,
configurable: false
});
} catch(e) {}
});
};
lockEventProps(document, 'document');
lockEventProps(window, 'window');
const lockBody = () => {
if (document.body) lockEventProps(document.body, 'body');
};
// ============================================================
// 拦截 addEventListener 注册
// ============================================================
const BLOCKED_EVENTS = new Set(['contextmenu', 'selectstart', 'copy', 'dragstart']);
const KEYBOARD_EVENTS = new Set(['keydown', 'keyup', 'keypress']);
EventTarget.prototype.addEventListener = function(type, listener, options) {
if (BLOCKED_EVENTS.has(type)) {
return;
}
if (KEYBOARD_EVENTS.has(type)) {
const origListener = listener;
const wrappedListener = function(e) {
if (isDevToolsKey(e)) {
const origPD = e.preventDefault;
e.preventDefault = function() {};
e.returnValue = true;
try {
if (typeof origListener === 'function') origListener.call(this, e);
else if (origListener && typeof origListener.handleEvent === 'function') origListener.handleEvent(e);
} catch(ex) {}
e.preventDefault = origPD;
return;
}
if (typeof origListener === 'function') return origListener.call(this, e);
else if (origListener && typeof origListener.handleEvent === 'function') return origListener.handleEvent(e);
};
return _addEventListener.call(this, type, wrappedListener, options);
}
return _addEventListener.call(this, type, listener, options);
};
// ============================================================
// CSS 覆盖
// ============================================================
const injectCSS = () => {
const style = document.createElement('style');
style.textContent = \`
html, body, * {
-webkit-user-select: text !important;
-moz-user-select: text !important;
user-select: text !important;
}
\`;
(document.head || document.documentElement).appendChild(style);
};
// ============================================================
// MutationObserver 清理内联事件属性
// ============================================================
const INLINE_ATTRS = ['oncontextmenu', 'onselectstart', 'ondragstart', 'oncopy', 'onkeydown', 'onkeyup', 'onkeypress'];
const cleanElement = (el) => {
if (!el || !el.removeAttribute) return;
INLINE_ATTRS.forEach(attr => {
if (el.hasAttribute && el.hasAttribute(attr)) {
el.removeAttribute(attr);
}
});
};
const setupCleanupObserver = () => {
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
for (const node of m.addedNodes) {
if (node.nodeType === 1) {
cleanElement(node);
if (node.querySelectorAll) {
node.querySelectorAll('[oncontextmenu],[onselectstart],[ondragstart],[oncopy],[onkeydown]').forEach(cleanElement);
}
}
}
if (m.type === 'attributes' && INLINE_ATTRS.includes(m.attributeName)) {
m.target.removeAttribute(m.attributeName);
}
}
});
const target = document.documentElement || document;
observer.observe(target, { childList: true, subtree: true, attributes: true, attributeFilter: INLINE_ATTRS });
};
setupCleanupObserver();
// ============================================================
// 拦截 meta refresh
// ============================================================
const blockMetaRefresh = () => {
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
for (const node of m.addedNodes) {
if (node.tagName === 'META') {
const equiv = node.getAttribute('http-equiv');
const content = node.getAttribute('content') || '';
if (equiv && equiv.toLowerCase() === 'refresh' && isBadUrl(content)) {
node.remove();
}
}
}
}
});
if (document.documentElement) {
observer.observe(document.documentElement, { childList: true, subtree: true });
}
};
blockMetaRefresh();
// ============================================================
// 拦截 alert / confirm
// ============================================================
const _alert = window.alert;
const _confirm = window.confirm;
window.alert = function(msg) {
const s = String(msg || '').toLowerCase();
if (s.includes('devtool') || s.includes('调试') || s.includes('控制台') || s.includes('检测') || s.includes('debug')) {
return;
}
return _alert.call(this, msg);
};
window.confirm = function(msg) {
const s = String(msg || '').toLowerCase();
if (s.includes('devtool') || s.includes('调试') || s.includes('控制台')) {
return false;
}
return _confirm.call(this, msg);
};
// ============================================================
// 破坏检测机制
// ============================================================
// 伪造 Firebug
try {
Object.defineProperty(window, 'Firebug', { get: () => undefined, set: () => true, configurable: false });
} catch(e) {}
// 锁定 DisableDevtool 全局变量
const fakeDD = function() { return { success: false }; };
fakeDD.md5 = () => '';
fakeDD.version = '0.0.0';
fakeDD.isRunning = false;
fakeDD.isSuspend = true;
fakeDD.config = () => fakeDD;
fakeDD.close = () => {};
fakeDD.ondevtoolopen = null;
fakeDD.ondevtoolclose = null;
['DisableDevtool', 'disableDevtool', 'DISABLE_DEVTOOL', 'dd', 'devtoolsDetector', '__DISABLE_DEVTOOL__'].forEach(name => {
try {
Object.defineProperty(window, name, { get: () => fakeDD, set: () => true, configurable: false });
} catch(e) {}
});
// 拦截 Function / eval 中的 debugger
const _Function = window.Function;
window.Function = function(...args) {
const code = args[args.length - 1];
if (typeof code === 'string' && /debugger/.test(code)) {
args[args.length - 1] = code.replace(/\\bdebugger\\b/g, '');
}
return _Function.apply(this, args);
};
window.Function.prototype = _Function.prototype;
try { window.Function.prototype.constructor = window.Function; } catch(e) {}
const _eval = window.eval;
window.eval = function(code) {
if (typeof code === 'string' && /debugger/.test(code)) {
code = code.replace(/\\bdebugger\\b/g, '');
}
return _eval.call(this, code);
};
// 拦截 setInterval / setTimeout 字符串 debugger
window.setInterval = function(fn, delay) {
if (typeof fn === 'string' && fn.includes('debugger')) return 0;
return _setInterval.apply(this, arguments);
};
window.setTimeout = function(fn, delay) {
if (typeof fn === 'string' && fn.includes('debugger')) return 0;
return _setTimeout.apply(this, arguments);
};
// 阻止 console.clear
console.clear = function() {};
// 保护 toString 方法
try {
Object.defineProperty(RegExp.prototype, 'toString', { value: RegExp.prototype.toString, writable: false, configurable: false });
Object.defineProperty(Date.prototype, 'toString', { value: Date.prototype.toString, writable: false, configurable: false });
} catch(e) {}
// ============================================================
// 特征库 & 脚本拦截
// ============================================================
const CONFIG = { enableBlock: true, threshold: 4 };
const FEATURES = {
urls: [
'disable-devtool', 'disable_devtool', 'disabledevtool',
'anti-debug', 'anti_debug', 'devtools-detect',
'devtools-detector', 'console-ban'
],
codes: [
[/DisableDevtool/i, 3, 'DisableDevtool'],
[/theajack\\.github\\.io/i, 5, '官方地址'],
[/ondevtoolopen/i, 3, 'ondevtoolopen'],
[/ondevtoolclose/i, 2, 'ondevtoolclose'],
[/isDevToolOpened/i, 2, 'isDevToolOpened'],
[/clearIntervalWhenDevOpenTrigger/i, 5, '特有函数'],
[/outerWidth\\s*-\\s*innerWidth/i, 2, '尺寸检测'],
[/outerHeight\\s*-\\s*innerHeight/i, 2, '高度检测'],
[/RegToString|FuncToString|DateToString/i, 3, 'ToString检测'],
[/DefineId|DebugLib/i, 2, 'DefineId'],
[/Function\\s*\\(\\s*["']debugger["']\\s*\\)/, 3, 'Function debugger'],
[/setInterval[\\s\\S]{0,100}debugger/, 2, 'setInterval debugger'],
[/eruda|vconsole/i, 1, '调试工具检测'],
[/location\\s*[.=][\\s\\S]{0,30}(baidu|google|bing)\\.com/i, 3, '跳转检测'],
[/oncontextmenu\\s*=\\s*(null|false)/i, 1, '右键禁用'],
[/keyCode\\s*={2,3}\\s*123/i, 2, 'F12检测'],
[/devtools/i, 1, 'devtools关键词'],
[/console\\.clear/i, 1, 'console.clear'],
[/\\b__DEVTOOLS/i, 2, 'DEVTOOLS变量']
]
};
const DATA = { scripts: [], blocked: [], cache: new Map(), count: 0 };
const getName = (url) => {
if (!url) return '(inline)';
try { return new URL(url).pathname.split('/').pop() || url; }
catch { return url.split('/').pop() || url; }
};
const getStack = () => {
try { throw new Error(); }
catch (e) { return (e.stack || '').split('\\n').slice(3, 7).join('\\n'); }
};
const analyze = (code, url) => {
const key = url || (code ? code.slice(0, 100) : '');
if (DATA.cache.has(key)) return DATA.cache.get(key);
const result = { dangerous: false, score: 0, matches: [] };
if (url) {
const lower = url.toLowerCase();
for (const kw of FEATURES.urls) {
if (lower.includes(kw)) { result.score += 5; result.matches.push({ name: 'URL:' + kw, weight: 5 }); break; }
}
}
if (code && typeof code === 'string') {
for (const [regex, weight, name] of FEATURES.codes) {
if (regex.test(code)) { result.score += weight; result.matches.push({ name, weight }); }
}
}
result.dangerous = result.score >= CONFIG.threshold;
DATA.cache.set(key, result);
return result;
};
const record = (url, code, method, stack) => {
const analysis = analyze(code || '', url || '');
const entry = { id: ++DATA.count, url: url || '', name: getName(url), method, analysis, blocked: false, time: Date.now() };
DATA.scripts.push(entry);
return entry;
};
// ============================================================
// 拦截脚本加载
// ============================================================
const O = {
createElement: Document.prototype.createElement,
appendChild: Element.prototype.appendChild,
insertBefore: Element.prototype.insertBefore,
append: Element.prototype.append,
prepend: Element.prototype.prepend,
setAttribute: Element.prototype.setAttribute,
innerHTML: Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML'),
write: Document.prototype.write,
writeln: Document.prototype.writeln
};
Document.prototype.createElement = function(tag, opts) {
const el = O.createElement.call(this, tag, opts);
if (tag && tag.toLowerCase() === 'script') {
const stack = getStack();
let _src = '';
Object.defineProperty(el, 'src', {
get: () => _src,
set: (url) => {
const a = analyze('', url);
const r = record(url, '', 'src', stack);
if (CONFIG.enableBlock && a.dangerous) {
r.blocked = true; DATA.blocked.push(r);
console.log('[Anti-DD] 拦截脚本:', getName(url));
return;
}
_src = url;
O.setAttribute.call(el, 'src', url);
},
configurable: true
});
el._stack = stack;
}
return el;
};
const interceptInsert = (orig, name) => function(...args) {
for (const node of args) {
if (node && node.tagName === 'SCRIPT') {
const url = node.src || (node.getAttribute && node.getAttribute('src')) || '';
const code = node.textContent || node.innerHTML || '';
const a = analyze(code, url);
const r = record(url, code, name, node._stack || getStack());
if (CONFIG.enableBlock && a.dangerous) {
r.blocked = true; DATA.blocked.push(r);
console.log('[Anti-DD] 拦截[' + name + ']:', r.name);
return node;
}
}
}
return orig.apply(this, args);
};
Element.prototype.appendChild = interceptInsert(O.appendChild, 'appendChild');
Element.prototype.insertBefore = interceptInsert(O.insertBefore, 'insertBefore');
if (O.append) Element.prototype.append = interceptInsert(O.append, 'append');
if (O.prepend) Element.prototype.prepend = interceptInsert(O.prepend, 'prepend');
if (O.innerHTML && O.innerHTML.set) {
Object.defineProperty(Element.prototype, 'innerHTML', {
get: O.innerHTML.get,
set: function(html) {
if (typeof html === 'string' && /<script/i.test(html)) {
const matches = html.match(/<script[^>]*>[\\s\\S]*?<\\/script>/gi) || [];
for (const m of matches) {
const srcMatch = m.match(/src=["']([^"']+)["']/i);
const url = srcMatch ? srcMatch[1] : '';
const codeMatch = m.match(/<script[^>]*>([\\s\\S]*?)<\\/script>/i);
const code = codeMatch ? codeMatch[1] : '';
const a = analyze(code, url);
const r = record(url, code, 'innerHTML', getStack());
if (CONFIG.enableBlock && a.dangerous) {
r.blocked = true; DATA.blocked.push(r);
html = html.replace(m, '<!-- blocked -->');
}
}
}
return O.innerHTML.set.call(this, html);
},
configurable: true, enumerable: true
});
}
const interceptWrite = (orig, name) => function(html) {
if (typeof html === 'string' && /<script/i.test(html)) {
const matches = html.match(/<script[^>]*>[\\s\\S]*?<\\/script>/gi) || [];
for (const m of matches) {
const srcMatch = m.match(/src=["']([^"']+)["']/i);
const url = srcMatch ? srcMatch[1] : '';
const codeMatch = m.match(/<script[^>]*>([\\s\\S]*?)<\\/script>/i);
const code = codeMatch ? codeMatch[1] : '';
const a = analyze(code, url);
const r = record(url, code, name, getStack());
if (CONFIG.enableBlock && a.dangerous) {
r.blocked = true; DATA.blocked.push(r);
html = html.replace(m, '');
}
}
}
return orig.call(this, html);
};
Document.prototype.write = interceptWrite(O.write, 'write');
Document.prototype.writeln = interceptWrite(O.writeln, 'writeln');
// MutationObserver 拦截脚本
const setupScriptObserver = () => {
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
for (const node of m.addedNodes) {
if (node.tagName === 'SCRIPT' && !node._tracked) {
node._tracked = true;
const url = node.src || '';
const code = node.textContent || '';
const a = analyze(code, url);
const r = record(url, code, 'Observer', '');
if (CONFIG.enableBlock && a.dangerous) {
r.blocked = true; DATA.blocked.push(r);
node.remove();
}
}
}
}
});
if (document.documentElement) {
observer.observe(document.documentElement, { childList: true, subtree: true });
} else {
document.addEventListener('DOMContentLoaded', () => {
observer.observe(document.documentElement, { childList: true, subtree: true });
});
}
};
setupScriptObserver();
// ============================================================
// DOMContentLoaded 清理
// ============================================================
const cleanup = () => {
lockBody();
injectCSS();
document.querySelectorAll('[oncontextmenu],[onselectstart],[ondragstart],[oncopy],[onkeydown],[onkeyup],[onkeypress]').forEach(cleanElement);
cleanElement(document.body);
cleanElement(document.documentElement);
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', cleanup);
} else {
cleanup();
}
_setTimeout.call(window, cleanup, 100);
_setTimeout.call(window, cleanup, 500);
_setTimeout.call(window, cleanup, 2000);
_setTimeout.call(window, cleanup, 5000);
console.log('[Anti-DD] v12.0.0 Phase 1 已启动');
window._AntiDD = { version: '12.0.0', config: CONFIG, data: DATA, analyze };
})();`;
// 注入到页面
const script = document.createElement('script');
script.textContent = injectedCode;
if (document.documentElement) {
document.documentElement.insertBefore(script, document.documentElement.firstChild);
} else {
const observer = new MutationObserver(() => {
if (document.documentElement) {
document.documentElement.insertBefore(script, document.documentElement.firstChild);
observer.disconnect();
}
});
observer.observe(document, { childList: true });
}