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