// ==UserScript== // @name DevTools Bypass // @name:vi Bỏ Qua Chặn DevTools // @name:zh-CN 开发工具限制绕过 // @name:ru Разблокировка DevTools // @namespace https://greasyfork.org/vi/users/1195312-renji-yuusei // @version 3.5 // @description Bypass for website restrictions on DevTools with enhanced protection // @description:vi Bỏ qua các hạn chế của trang web về DevTools với bảo vệ nâng cao // @description:zh-CN 绕过网站对开发工具的限制,具有增强的保护功能 // @description:ru Разблокировка DevTools с усиленной защитой // @author Yuusei // @match *://*/* // @grant unsafeWindow // @run-at document-start // @license GPL-3.0-only // @downloadURL none // ==/UserScript== (function () { 'use strict'; const CONSTANTS = { PREFIX: '[DevTools Bypass]', LOG_LEVELS: { INFO: 'info', WARN: 'warn', ERROR: 'error', }, TIME_THRESHOLDS: { DEBUGGER: 100, CACHE: 60000, }, }; const config = { debugPatterns: { basic: /;\s*(?:debugger|debug(?:ger)?|breakpoint)\s*;?/gi, advanced: /(?:debugger|debug(?:ger)?|breakpoint)[\s;]*(?:\{[\s\S]*?\})?/gi, timing: /(?:performance|Date)\.now\(\)|new\s+Date(?:\(\))?\.getTime\(\)/gi, eval: /eval\(.*?(?:debugger|debug|breakpoint).*?\)/gi, devtools: /(?:isDevTools|devtools|debugMode|debug_mode)\s*[=:]\s*(?:true|1)/gi, consoleCheck: /console\.[a-zA-Z]+\s*\(.*?\)/gi, functionDebug: /function.*?\{[\s\S]*?debugger[\s\S]*?\}/gi, sourceMap: /\/\/[#@]\s*source(?:Mapping)?URL=.*?$/gm, debugKeywords: /\b(?:debug|debugger|breakpoint|devtools)\b/gi, debugStrings: /"(?:[^"\\]|\\.)*(?:debug|debugger|breakpoint|devtools)(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*(?:debug|debugger|breakpoint|devtools)(?:[^'\\]|\\.)*'/gi, debugComments: /\/\/.*(?:debug|debugger|breakpoint|devtools).*$|\/\*[\s\S]*?(?:debug|debugger|breakpoint|devtools)[\s\S]*?\*\//gim, checkStats: /\b(?:checkStatus|_checkDevToolsBypassStatus|getStatus|checkDevTools)\b/gi, debuggerCheck: /\b(?:isDebuggerEnabled|hasDebugger|checkDebugger)\b/gi, debuggerTiming: /(?:performance\.timing|performance\.now|Date\.now)\(\)/gi, debuggerEval: /(?:eval|new Function)\s*\([^)]*(?:debugger|debug|breakpoint)[^)]*\)/gi, debuggerAsync: /setTimeout\s*\(\s*(?:function\s*\(\)\s*\{[\s\S]*?debugger[\s\S]*?\}|[^,]+,\s*[0-9]+\s*\))/gi, }, consoleProps: ['log', 'warn', 'error', 'info', 'debug', 'trace', 'dir', 'dirxml', 'table', 'profile'], cutoffs: { debugger: { amount: 50, within: CONSTANTS.TIME_THRESHOLDS.CACHE }, debuggerThrow: { amount: 50, within: CONSTANTS.TIME_THRESHOLDS.CACHE }, }, bypassTriggers: { timeThreshold: CONSTANTS.TIME_THRESHOLDS.DEBUGGER, stackDepth: 50, recursionLimit: 100, }, logging: { enabled: false, prefix: CONSTANTS.PREFIX, levels: Object.values(CONSTANTS.LOG_LEVELS), detailedErrors: false, }, protection: { preventDevToolsKeys: true, hideStackTraces: true, sanitizeErrors: true, obfuscateTimers: true, preventRightClick: true, preventViewSource: true, }, }; class Logger { static #instance; #lastLog = 0; #logCount = 0; constructor() { if (Logger.#instance) { return Logger.#instance; } Logger.#instance = this; } #shouldLog() { const now = Date.now(); if (now - this.#lastLog > 1000) { this.#logCount = 0; } this.#lastLog = now; return ++this.#logCount <= 5; } #log(level, ...args) { if (!config.logging.enabled || !this.#shouldLog()) return; console[level](config.logging.prefix, ...args); } info(...args) { this.#log(CONSTANTS.LOG_LEVELS.INFO, ...args); } warn(...args) { this.#log(CONSTANTS.LOG_LEVELS.WARN, ...args); } error(...args) { this.#log(CONSTANTS.LOG_LEVELS.ERROR, ...args); } } const logger = new Logger(); class OriginalFunctions { static defineProperty = Object.defineProperty; static getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; static setTimeout = window.setTimeout; static setInterval = window.setInterval; static Date = window.Date; static now = Date.now; static performance = window.performance; static Function = window.Function; static eval = window.eval; static console = {}; static toString = Function.prototype.toString; static preventDefault = Event.prototype.preventDefault; static getComputedStyle = window.getComputedStyle; static addEventListener = window.addEventListener; static removeEventListener = window.removeEventListener; static initConsole() { config.consoleProps.forEach(prop => { if (console[prop]) { this.console[prop] = console[prop].bind(console); } }); } } OriginalFunctions.initConsole(); class DebuggerDetector { static #detectionCache = new Map(); static isPresent() { try { const cacheKey = 'debugger_check'; const cached = this.#detectionCache.get(cacheKey); if (cached && Date.now() - cached.timestamp < 1000) { return cached.result; } const startTime = OriginalFunctions.now.call(Date); new Function('debugger;')(); const timeDiff = OriginalFunctions.now.call(Date) - startTime; const result = timeDiff > config.bypassTriggers.timeThreshold; this.#detectionCache.set(cacheKey, { result, timestamp: Date.now(), }); return result; } catch (e) { return false; } } static analyzeStack() { try { const stack = new Error().stack; const frames = stack.split('\n'); const uniqueFrames = new Set(frames); return { depth: frames.length, hasDebugKeywords: frames.some(frame => Object.values(config.debugPatterns).some(pattern => pattern.test(frame))), isRecursive: uniqueFrames.size < frames.length, suspiciousPatterns: this.#detectSuspiciousPatterns(stack), }; } catch (e) { return { depth: 0, hasDebugKeywords: false, isRecursive: false, suspiciousPatterns: [], }; } } static #detectSuspiciousPatterns(stack) { const patterns = [/eval.*?\(/g, /Function.*?\(/g, /debugger/g, /debug/g]; return patterns.map(pattern => pattern.test(stack)).filter(Boolean); } } class Protection { static applyAll() { this.#protectTimers(); this.#protectTiming(); this.#protectFunction(); this.#protectStack(); this.#protectEval(); this.#protectConsole(); this.#setupMutationObserver(); this.#protectDevToolsKeys(); this.#protectRightClick(); this.#protectViewSource(); } static #protectTimers() { const wrapTimer = original => { return function (handler, timeout, ...args) { if (typeof handler !== 'function') return original.apply(this, arguments); const wrappedHandler = function () { try { if (DebuggerDetector.isPresent()) return; return handler.apply(this, arguments); } catch (e) { if (e.message?.includes('debugger')) return; throw e; } }; if (config.protection.obfuscateTimers) { timeout = Math.max(1, timeout + Math.random() * 10 - 5); } return original.call(this, wrappedHandler, timeout, ...args); }; }; window.setTimeout = wrapTimer(OriginalFunctions.setTimeout); window.setInterval = wrapTimer(OriginalFunctions.setInterval); } static #protectTiming() { const timeOffset = Math.random() * 15; const safeNow = () => OriginalFunctions.now.call(Date) + timeOffset; Object.defineProperty(Date, 'now', { value: safeNow, configurable: true, writable: true, }); if (window.performance?.now) { Object.defineProperty(window.performance, 'now', { value: safeNow, configurable: true, writable: true, }); } } static #protectFunction() { const handler = { apply(target, thisArg, args) { if (typeof args[0] === 'string') { args[0] = Protection.#cleanCode(args[0]); } return Reflect.apply(target, thisArg, args); }, construct(target, args) { if (typeof args[0] === 'string') { args[0] = Protection.#cleanCode(args[0]); } return Reflect.construct(target, args); }, }; window.Function = new Proxy(OriginalFunctions.Function, handler); if (typeof unsafeWindow !== 'undefined') { unsafeWindow.Function = window.Function; } } static #protectStack() { if (!config.protection.hideStackTraces) return; const errorHandler = { get(target, prop) { if (prop === 'stack') { const stack = target.stack; return Protection.#cleanCode(stack); } return target[prop]; }, }; const originalErrorPrototype = Error.prototype; const proxyErrorPrototype = Object.create(originalErrorPrototype); Object.defineProperty(proxyErrorPrototype, 'stack', { get() { const error = new Error(); return Protection.#cleanCode(error.stack); }, configurable: true, }); try { Error.prototype = proxyErrorPrototype; } catch (e) {} } static #protectEval() { const safeEval = function (code) { if (typeof code === 'string') { if (DebuggerDetector.isPresent()) return; return OriginalFunctions.eval.call(this, Protection.#cleanCode(code)); } return OriginalFunctions.eval.apply(this, arguments); }; Object.defineProperty(window, 'eval', { value: safeEval, configurable: true, writable: true, }); if (typeof unsafeWindow !== 'undefined') { unsafeWindow.eval = safeEval; } } static #protectConsole() { const consoleHandler = { get(target, prop) { if (!config.consoleProps.includes(prop)) return target[prop]; return function (...args) { if (DebuggerDetector.isPresent()) return; return OriginalFunctions.console[prop]?.apply(console, args); }; }, set(target, prop, value) { if (config.consoleProps.includes(prop)) return true; target[prop] = value; return true; }, }; window.console = new Proxy(console, consoleHandler); } static #setupMutationObserver() { new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.type === 'childList') { mutation.addedNodes.forEach(node => { if (node.tagName === 'SCRIPT') { const originalContent = node.textContent; const cleanedContent = Protection.#cleanCode(originalContent); if (originalContent !== cleanedContent) { node.textContent = cleanedContent; } } }); } }); }).observe(document, { childList: true, subtree: true, }); } static #protectDevToolsKeys() { if (!config.protection.preventDevToolsKeys) return; const handler = e => { const { keyCode, ctrlKey, shiftKey } = e; if ( keyCode === 123 || // F12 (ctrlKey && shiftKey && keyCode === 73) || // Ctrl+Shift+I (ctrlKey && shiftKey && keyCode === 74) || // Ctrl+Shift+J (ctrlKey && keyCode === 85) // Ctrl+U ) { e.preventDefault(); } }; window.addEventListener('keydown', handler, true); } static #protectRightClick() { if (!config.protection.preventRightClick) return; window.addEventListener( 'contextmenu', e => { e.preventDefault(); }, true ); } static #protectViewSource() { if (!config.protection.preventViewSource) return; window.addEventListener( 'keydown', e => { if (e.ctrlKey && e.key === 'u') { e.preventDefault(); } }, true ); } static #cleanCode(code) { if (typeof code !== 'string') return code; let cleanCode = code; Object.entries(config.debugPatterns).forEach(([, pattern]) => { cleanCode = cleanCode.replace(pattern, ''); }); return cleanCode; } } class DevToolsBypass { static init() { try { Protection.applyAll(); } catch (e) {} } } DevToolsBypass.init(); })();