// ==UserScript== // @name Anti Anti-Debugger Enhanced v1.3 // @namespace https://example.com/ // @version 1.3.0 // @description 阻止网页通过 Function/eval/debugger 等常见方式来无限断点反调试 // @author IsKongKongYa // @match *://*/* // @run-at document-start // @inject-into page // @grant unsafeWindow // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/571593/Anti%20Anti-Debugger%20Enhanced%20v13.user.js // @updateURL https://update.greasyfork.icu/scripts/571593/Anti%20Anti-Debugger%20Enhanced%20v13.meta.js // ==/UserScript== (function () { 'use strict'; // ================================================================== // 用户可配置项(按需开关) // ================================================================== const CONFIG = { DEBUG_LOG: false, // 控制台调试日志 AGGRESSIVE_IMPORT_SCRIPTS: true, // Worker 内 importScripts 使用 XHR+eval 激进拦截 BLOCK_CONSOLE_CLEAR: true, // 阻止 console.clear PATCH_INNER_HTML: true, // 拦截 innerHTML 中的反调试代码 PATCH_REQUEST_ANIMATION_FRAME: true, // 拦截 requestAnimationFrame 中的可疑回调 EXPOSE_DEBUG_API: true, // 暴露 __ANTI_DEBUGGER__ 调试 API }; const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; const doc = win.document; function log(...args) { if (CONFIG.DEBUG_LOG) console.log('[Anti-Debugger v3.1]', ...args); } const NativeStore = { Function: win.Function, eval: win.eval, Blob: win.Blob, Worker: win.Worker, URL_createObjectURL: win.URL && win.URL.createObjectURL, URL_revokeObjectURL: win.URL && win.URL.revokeObjectURL, FnToString: Function.prototype.toString, setTimeout: win.setTimeout, setInterval: win.setInterval, appendChild: Element.prototype.appendChild, insertBefore: Element.prototype.insertBefore, replaceChild: Element.prototype.replaceChild, createElement: Document.prototype.createElement, write: Document.prototype.write, writeln: Document.prototype.writeln, }; // ================================================================== // 可序列化纯函数(通过 fn.toString() 注入 Worker,禁止引用闭包变量) // ================================================================== // [FIX #1] 循环折叠 + 同类引号 + 混合引号("a"+'b')支持 function tryFoldSimpleConcat(code) { if (typeof code !== 'string') return code; var dqRe = /"((?:[^"\\]|\\.)*)"\s*\+\s*"((?:[^"\\]|\\.)*)"/g; var sqRe = /'((?:[^'\\]|\\.)*)'\s*\+\s*'((?:[^'\\]|\\.)*)'/g; var btRe = /`((?:[^`\\]|\\.)*)`\s*\+\s*`((?:[^`\\]|\\.)*)`/g; var mxRe = /(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)')\s*\+\s*(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)')/g; var out = code; var prev; var rounds = 0; do { prev = out; // 同类引号折叠(保留原始引号风格) dqRe.lastIndex = 0; out = out.replace(dqRe, function (_, a, b) { return '"' + a + b + '"'; }); sqRe.lastIndex = 0; out = out.replace(sqRe, function (_, a, b) { return "'" + a + b + "'"; }); btRe.lastIndex = 0; out = out.replace(btRe, function (_, a, b) { return '`' + a + b + '`'; }); // 混合引号折叠(输出双引号,跳过含双引号内容防语法错误) mxRe.lastIndex = 0; out = out.replace(mxRe, function (m, dq1, sq1, dq2, sq2) { // 两侧引号类型相同 → 跳过(已被同类正则处理) var leftIsDouble = dq1 != null; var rightIsDouble = dq2 != null; if (leftIsDouble === rightIsDouble) return m; var a = leftIsDouble ? dq1 : (sq1 || ''); var b = rightIsDouble ? dq2 : (sq2 || ''); if (a.indexOf('"') >= 0 || b.indexOf('"') >= 0) return m; return '"' + a + b + '"'; }); } while (out !== prev && ++rounds < 5); return out; } // [FIX #2 prep] 改用 atob(非 win.atob)以便序列化到 Worker function tryDecodeBase64Literal(code) { if (typeof code !== 'string') return code; return code.replace( /\batob\s*\(\s*(['"`])([A-Za-z0-9+/=]{8,})\1\s*\)/g, function (m, q, b64) { try { return JSON.stringify(atob(b64)); } catch (e) { return m; } } ); } // [FIX #2] 只解码可见 ASCII 范围(0x20-0x7E),避免展开控制字符破坏代码 function tryDecodeEscapes(code) { if (typeof code !== 'string') return code; return code .replace(/\\x([2-7][0-9a-fA-F])/g, function (m, h) { var c = parseInt(h, 16); return (c >= 0x20 && c <= 0x7e) ? String.fromCharCode(c) : m; }) .replace(/\\u00([2-7][0-9a-fA-F])/g, function (m, h) { var c = parseInt(h, 16); return (c >= 0x20 && c <= 0x7e) ? String.fromCharCode(c) : m; }); } function normalizeCode(code) { if (typeof code !== 'string') return code; var out = code; out = tryFoldSimpleConcat(out); out = tryDecodeBase64Literal(out); out = tryDecodeEscapes(out); return out; } function stripDebugger(code) { if (typeof code !== 'string') return code; var out = normalizeCode(code); out = out.replace(/\bdebugger\b\s*;?/g, ''); out = out.replace(/;\s*;/g, ';'); out = out.replace(/Function\s*\(\s*(['"`])\s*debugger\s*;?\s*\1\s*\)/g, 'Function("")'); out = out.replace(/new\s+Function\s*\(\s*(['"`])\s*debugger\s*;?\s*\1\s*\)/g, 'new Function("")'); out = out.replace(/eval\s*\(\s*(['"`])\s*debugger\s*;?\s*\1\s*\)/g, 'eval("")'); return out; } // ================================================================== // [FIX #4] looksSuspicious 评分制,降低误报率 // ================================================================== function looksSuspicious(code) { if (typeof code !== 'string') return false; var normalized = normalizeCode(code); // 直接命中 if (/\bdebugger\b/.test(normalized)) return true; // 高可疑:Function/eval 参数直接包含 debugger if (/Function\s*\(\s*['"`][^)]{0,500}debugger[^)]{0,500}['"`]\s*\)/.test(normalized)) return true; if (/eval\s*\(\s*['"`][^)]{0,500}debugger[^)]{0,500}['"`]\s*\)/.test(normalized)) return true; // 评分制:至少命中 2 项才标记 var score = 0; if (/new\s+Function\s*\(/.test(normalized)) score++; if (/\.constructor\s*\(\s*['"`]/.test(normalized)) score++; if (/\beval\s*\(\s*atob\s*\(/.test(normalized)) score++; if (/\batob\s*\([\s\S]*(?:debugger|Function|eval)/.test(code)) score++; if (/setInterval\s*\(\s*['"`]/.test(normalized)) score++; if (/setTimeout\s*\(\s*['"`]/.test(normalized)) score++; return score >= 2; } // ================================================================== // 工具函数 // ================================================================== function safeToString(name) { return function () { return 'function ' + name + '() { [native code] }'; }; } function patchFunctionLike(targetObj, key, wrapperFactory) { var original = targetObj[key]; if (typeof original !== 'function') return; var wrapped = wrapperFactory(original); try { Object.defineProperty(wrapped, 'toString', { configurable: true, value: safeToString(key) }); } catch (e) {} try { Object.defineProperty(targetObj, key, { configurable: true, writable: true, value: wrapped }); log('patched', key); } catch (e) { log('patch failed:', key, e); } } function patchCodeStringArg(args, indexList) { var newArgs = Array.from(args); for (var i = 0; i < indexList.length; i++) { if (typeof newArgs[indexList[i]] === 'string') { newArgs[indexList[i]] = stripDebugger(newArgs[indexList[i]]); } } return newArgs; } // ================================================================== // 主线程基础补丁 // ================================================================== patchFunctionLike(win, 'eval', function (original) { return new Proxy(original, { apply: function (target, thisArg, args) { return Reflect.apply(target, thisArg, patchCodeStringArg(args, [0])); } }); }); (function patchFunctionConstructor() { var RawFunction = NativeStore.Function; function SafeFunction() { var patchedArgs = Array.from(arguments).map(function (arg) { return typeof arg === 'string' ? stripDebugger(arg) : arg; }); return RawFunction.apply(this, patchedArgs); } SafeFunction.prototype = RawFunction.prototype; var FunctionProxy = new Proxy(SafeFunction, { apply: function (target, thisArg, args) { var patchedArgs = args.map(function (arg) { return typeof arg === 'string' ? stripDebugger(arg) : arg; }); return Reflect.apply(RawFunction, thisArg, patchedArgs); }, construct: function (target, args, newTarget) { var patchedArgs = args.map(function (arg) { return typeof arg === 'string' ? stripDebugger(arg) : arg; }); return Reflect.construct(RawFunction, patchedArgs, newTarget || RawFunction); } }); try { Object.defineProperty(FunctionProxy, 'toString', { configurable: true, value: safeToString('Function') }); } catch (e) {} try { Object.defineProperty(win, 'Function', { configurable: true, writable: true, value: FunctionProxy }); } catch (e) {} try { Object.defineProperty(RawFunction.prototype, 'constructor', { configurable: true, writable: true, value: FunctionProxy }); } catch (e) {} })(); ['setTimeout', 'setInterval'].forEach(function (name) { patchFunctionLike(win, name, function (original) { return new Proxy(original, { apply: function (target, thisArg, args) { return Reflect.apply(target, thisArg, patchCodeStringArg(args, [0])); } }); }); }); if (CONFIG.PATCH_REQUEST_ANIMATION_FRAME) { patchFunctionLike(win, 'requestAnimationFrame', function (original) { return new Proxy(original, { apply: function (target, thisArg, args) { var cb = args[0]; if (typeof cb === 'function') { try { var src = NativeStore.FnToString.call(cb); if (looksSuspicious(src)) { args[0] = function () {}; } } catch (e) {} } return Reflect.apply(target, thisArg, args); } }); }); } // 注意:MutationObserver 触发时脚本可能已开始执行,修改 textContent 无法阻止已启动的执行。 // 但对通过 innerHTML 批量插入的后续 script 节点仍有拦截价值。 function patchScriptNode(node) { if (!node || node.nodeType !== 1 || node.tagName !== 'SCRIPT') return node; try { if (typeof node.textContent === 'string' && node.textContent) { node.textContent = stripDebugger(node.textContent); } } catch (e) {} try { if (node.src && typeof node.src === 'string' && node.src.startsWith('javascript:')) { node.src = stripDebugger(node.src); } } catch (e) {} return node; } ['appendChild', 'insertBefore', 'replaceChild'].forEach(function (name) { patchFunctionLike(Element.prototype, name, function (original) { return new Proxy(original, { apply: function (target, thisArg, args) { if (args[0]) patchScriptNode(args[0]); return Reflect.apply(target, thisArg, args); } }); }); }); patchFunctionLike(Document.prototype, 'createElement', function (original) { return new Proxy(original, { apply: function (target, thisArg, args) { var node = Reflect.apply(target, thisArg, args); try { if (String(args[0]).toLowerCase() === 'script') { // 注意:此处用实例属性劫持 text/textContent,会脱离 DOM 原型链行为。 // 可能与 React/Vue 等框架操作 script 节点时产生细微不一致。 var _text = ''; var _textContent = ''; Object.defineProperty(node, 'text', { configurable: true, get: function () { return _text; }, set: function (v) { _text = typeof v === 'string' ? stripDebugger(v) : v; } }); Object.defineProperty(node, 'textContent', { configurable: true, get: function () { return _textContent; }, set: function (v) { _textContent = typeof v === 'string' ? stripDebugger(v) : v; } }); } } catch (e) {} return node; } }); }); ['write', 'writeln'].forEach(function (name) { patchFunctionLike(Document.prototype, name, function (original) { return new Proxy(original, { apply: function (target, thisArg, args) { var newArgs = args.map(function (arg) { return typeof arg === 'string' ? stripDebugger(arg) : arg; }); return Reflect.apply(target, thisArg, newArgs); } }); }); }); if (CONFIG.PATCH_INNER_HTML) { try { var rawInnerHTMLDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML'); if (rawInnerHTMLDescriptor && rawInnerHTMLDescriptor.set) { var rawSetter = rawInnerHTMLDescriptor.set; var rawGetter = rawInnerHTMLDescriptor.get; Object.defineProperty(Element.prototype, 'innerHTML', { configurable: true, get: function () { return rawGetter.call(this); }, set: function (v) { if (typeof v === 'string' && looksSuspicious(v)) { v = v.replace( /(]*>)([\s\S]*?)(<\/script>)/gi, function (m, open, body, close) { return open + stripDebugger(body) + close; } ); } return rawSetter.call(this, v); } }); } } catch (e) {} } // ================================================================== // Blob / URL / Worker 主链增强 // ================================================================== var blobToMeta = new WeakMap(); var urlToMeta = new Map(); var wrappedWorkerUrlSet = new Set(); function detectJsMime(type) { return /javascript|ecmascript|json|text\/plain|application\/octet-stream/i.test(type || ''); } // [FIX #6] Worker bootstrap 使用 fn.toString() 序列化,消除代码重复 // workerPatchBody 是一个永远不在主线程调用的函数,仅取 toString() 注入到 Worker // 它假定 stripDebugger 和 __config 变量已在 Worker 作用域中定义 function workerPatchBody() { /* eslint-disable no-undef */ var __strip = stripDebugger; function __patchCodeArgs(args, idxs) { var out = Array.prototype.slice.call(args); for (var i = 0; i < idxs.length; i++) { if (typeof out[idxs[i]] === 'string') out[idxs[i]] = __strip(out[idxs[i]]); } return out; } try { var __rawEval = self.eval; self.eval = new Proxy(__rawEval, { apply: function (target, thisArg, args) { return Reflect.apply(target, thisArg, __patchCodeArgs(args, [0])); } }); } catch (e) {} try { var __RawFunction = self.Function; function __SafeFunction() { var a = Array.prototype.slice.call(arguments).map(function (x) { return typeof x === 'string' ? __strip(x) : x; }); return __RawFunction.apply(this, a); } __SafeFunction.prototype = __RawFunction.prototype; var __FP = new Proxy(__SafeFunction, { apply: function (target, thisArg, args) { args = args.map(function (x) { return typeof x === 'string' ? __strip(x) : x; }); return Reflect.apply(__RawFunction, thisArg, args); }, construct: function (target, args, newTarget) { args = args.map(function (x) { return typeof x === 'string' ? __strip(x) : x; }); return Reflect.construct(__RawFunction, args, newTarget || __RawFunction); } }); self.Function = __FP; __RawFunction.prototype.constructor = __FP; } catch (e) {} // [FIX #3] importScripts 激进拦截:XHR 同步读取 + __strip + eval // Worker 中同步 XHR 不受主线程限制,比主线程可靠得多 try { var __rawImportScripts = self.importScripts; if (__config.AGGRESSIVE_IMPORT_SCRIPTS) { self.importScripts = function () { var urls = Array.prototype.slice.call(arguments); for (var i = 0; i < urls.length; i++) { try { var xhr = new XMLHttpRequest(); xhr.open('GET', urls[i], false); xhr.send(); if (xhr.status >= 200 && xhr.status < 300) { var code = __strip(xhr.responseText || ''); (0, eval)(code); continue; } } catch (e) {} // XHR 失败(跨域等)回退原生 importScripts __rawImportScripts.call(self, urls[i]); } }; } // 非激进模式不做任何处理,保持原生 importScripts } catch (e) {} try { var __rawSetTimeout = self.setTimeout; self.setTimeout = new Proxy(__rawSetTimeout, { apply: function (target, thisArg, args) { return Reflect.apply(target, thisArg, __patchCodeArgs(args, [0])); } }); } catch (e) {} try { var __rawSetInterval = self.setInterval; self.setInterval = new Proxy(__rawSetInterval, { apply: function (target, thisArg, args) { return Reflect.apply(target, thisArg, __patchCodeArgs(args, [0])); } }); } catch (e) {} /* eslint-enable no-undef */ } function buildWorkerBootstrap(userCode) { // 序列化纯函数到 Worker var serializableFns = [ tryFoldSimpleConcat, tryDecodeBase64Literal, tryDecodeEscapes, normalizeCode, stripDebugger ]; var parts = []; parts.push('(function(){'); parts.push('"use strict";'); parts.push('var __config = ' + JSON.stringify({ AGGRESSIVE_IMPORT_SCRIPTS: CONFIG.AGGRESSIVE_IMPORT_SCRIPTS }) + ';'); for (var i = 0; i < serializableFns.length; i++) { parts.push(serializableFns[i].toString() + ';'); } // 提取 workerPatchBody 的函数体(去掉外层 function 声明) var patchSrc = workerPatchBody.toString(); var bodyMatch = patchSrc.match(/^[^{]*\{([\s\S]*)\}\s*$/); parts.push(bodyMatch ? bodyMatch[1] : patchSrc); parts.push('})();'); return parts.join('\n') + '\n\n' + userCode; } function makeWrappedWorkerUrlFromCode(code, mimeType) { var cleanUserCode = stripDebugger(code); var wrappedCode = buildWorkerBootstrap(cleanUserCode); var blob = new NativeStore.Blob([wrappedCode], { type: mimeType || 'application/javascript' }); var url = NativeStore.URL_createObjectURL.call(win.URL, blob); wrappedWorkerUrlSet.add(url); return url; } // [FIX #7] SafeBlob 去除双重清理:只对各 part 清理一次,不再 join 后再清理 if (typeof NativeStore.Blob === 'function') { var RawBlob = NativeStore.Blob; function SafeBlob(parts, options) { var originalCode = null; var cleanedCode = null; var type = options && options.type ? options.type : ''; try { if (Array.isArray(parts)) { var rawTextParts = []; var cleanTextParts = []; var sanitizedParts = parts.map(function (part) { if (typeof part === 'string') { rawTextParts.push(part); var cleaned = stripDebugger(part); cleanTextParts.push(cleaned); return cleaned; } return part; }); var joinedRaw = rawTextParts.join('\n'); if (joinedRaw) { originalCode = joinedRaw; cleanedCode = cleanTextParts.join('\n'); parts = sanitizedParts; } } } catch (e) { log('SafeBlob sanitize failed:', e); } var blob = new RawBlob(parts, options); try { if (originalCode !== null && (looksSuspicious(originalCode) || detectJsMime(type))) { blobToMeta.set(blob, { originalCode: originalCode, cleanCode: cleanedCode, type: type }); } } catch (e) {} return blob; } SafeBlob.prototype = RawBlob.prototype; try { Object.defineProperty(SafeBlob, 'toString', { configurable: true, value: safeToString('Blob') }); } catch (e) {} try { Object.defineProperty(win, 'Blob', { configurable: true, writable: true, value: SafeBlob }); } catch (e) {} } if (win.URL && typeof NativeStore.URL_createObjectURL === 'function') { patchFunctionLike(win.URL, 'createObjectURL', function (original) { return new Proxy(original, { apply: function (target, thisArg, args) { var blob = args[0]; var url = Reflect.apply(target, thisArg, args); try { var meta = blobToMeta.get(blob); if (meta) { urlToMeta.set(url, meta); // 防止长时间运行页面内存泄漏 if (urlToMeta.size > 1000) { var oldest = urlToMeta.keys().next().value; urlToMeta.delete(oldest); } log('tracked blob url', url); } } catch (e) {} return url; } }); }); } if (win.URL && typeof NativeStore.URL_revokeObjectURL === 'function') { patchFunctionLike(win.URL, 'revokeObjectURL', function (original) { return new Proxy(original, { apply: function (target, thisArg, args) { var url = args[0]; try { if (typeof url === 'string') { urlToMeta.delete(url); wrappedWorkerUrlSet.delete(url); } } catch (e) {} return Reflect.apply(target, thisArg, args); } }); }); } function tryReadSameOriginScript(url) { try { var xhr = new XMLHttpRequest(); xhr.open('GET', url, false); xhr.send(); if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 0) { return xhr.responseText || ''; } } catch (e) {} return null; } if (typeof NativeStore.Worker === 'function') { patchFunctionLike(win, 'Worker', function (original) { return new Proxy(original, { construct: function (target, args, newTarget) { var url = args[0]; try { if (typeof url === 'string' && url.startsWith('javascript:')) { args[0] = stripDebugger(url); return Reflect.construct(target, args, newTarget); } // 1) blob: 且来自已追踪的 createObjectURL if (typeof url === 'string' && url.startsWith('blob:')) { var meta = urlToMeta.get(url); if (meta && meta.cleanCode != null) { args[0] = makeWrappedWorkerUrlFromCode(meta.cleanCode, meta.type); log('worker wrapped from tracked blob url'); return Reflect.construct(target, args, newTarget); } // 2) 兜底:尽量读回 blob: 内容 var blobCode = tryReadSameOriginScript(url); if (typeof blobCode === 'string' && blobCode) { args[0] = makeWrappedWorkerUrlFromCode(blobCode, 'application/javascript'); log('worker wrapped from fallback-read blob url'); return Reflect.construct(target, args, newTarget); } } // [FIX #5] 3) 普通同源 js 文件,附加日志说明跨域失败情况 if (typeof url === 'string' && /^(https?:|\/|\.\/|\.\.\/)/.test(url)) { var srcCode = tryReadSameOriginScript(url); if (typeof srcCode === 'string' && srcCode) { args[0] = makeWrappedWorkerUrlFromCode(srcCode, 'application/javascript'); log('worker wrapped from same-origin script'); return Reflect.construct(target, args, newTarget); } // 跨域或读取失败:直接放行,不阻塞不报错 log('worker script not interceptable (likely cross-origin):', url); } } catch (e) { log('Worker wrap failed:', e); } return Reflect.construct(target, args, newTarget); } }); }); } // ================================================================== // MutationObserver // ================================================================== try { var mo = new MutationObserver(function (mutations) { for (var i = 0; i < mutations.length; i++) { var addedNodes = mutations[i].addedNodes; for (var j = 0; j < addedNodes.length; j++) { try { patchScriptNode(addedNodes[j]); if (addedNodes[j] && addedNodes[j].querySelectorAll) { var scripts = addedNodes[j].querySelectorAll('script'); for (var k = 0; k < scripts.length; k++) patchScriptNode(scripts[k]); } } catch (e) {} } } }); var startObserve = function () { try { mo.observe(doc.documentElement || doc, { childList: true, subtree: true }); } catch (e) {} }; if (doc.documentElement) startObserve(); else doc.addEventListener('DOMContentLoaded', startObserve, { once: true }); } catch (e) {} // [FIX #8] console.clear:修复未使用的 rawClear,增加 CONFIG 开关 if (CONFIG.BLOCK_CONSOLE_CLEAR) { try { var rawClear = console.clear; console.clear = function () { log('console.clear blocked'); // 如需恢复原始行为,取消下面注释: // rawClear.apply(console, arguments); }; try { console.clear.toString = safeToString('clear'); } catch (e) {} } catch (e) {} } // 调试 API if (CONFIG.EXPOSE_DEBUG_API) { try { win.__ANTI_DEBUGGER__ = { version: '1.3.0', config: CONFIG, stripDebugger: stripDebugger, looksSuspicious: looksSuspicious, dumpTrackedBlobUrls: function () { return Array.from(urlToMeta.keys()); }, dumpWrappedWorkerUrls: function () { return Array.from(wrappedWorkerUrlSet); }, test: function () { console.log('Anti-Debugger v1.3 active'); return true; } }; } catch (e) {} } log('Anti Anti-Debugger Enhanced v1.3 loaded'); })();