// ==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 // @downloadURL https://update.greasyfork.icu/scripts/550234/Anti-Disable-Devtool.user.js // @updateURL https://update.greasyfork.icu/scripts/550234/Anti-Disable-Devtool.meta.js // ==/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' && /