Greasy Fork

开发工具限制绕过

绕过网站对开发工具的限制,具有增强的保护功能

当前为 2024-12-05 提交的版本,查看 最新版本

// ==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
// ==/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();
})();