Greasy Fork is available in English.

DevTools Bypass

Bypass for website restrictions on DevTools with enhanced protection

  1. // ==UserScript==
  2. // @name DevTools Bypass
  3. // @name:vi Bỏ Qua Chặn DevTools
  4. // @name:zh-CN 开发工具限制绕过
  5. // @name:ru Разблокировка DevTools
  6. // @namespace https://greatest.deepsurf.us/vi/users/1195312-renji-yuusei
  7. // @version 2025.06.10.1
  8. // @description Bypass for website restrictions on DevTools with enhanced protection
  9. // @description:vi Bỏ qua các hạn chế của trang web về DevTools với bảo vệ nâng cao
  10. // @description:zh-CN 绕过网站对开发工具的限制,具有增强的保护功能
  11. // @description:ru Разблокировка DevTools с усиленной защитой
  12. // @author Yuusei
  13. // @match *://*/*
  14. // @grant unsafeWindow
  15. // @run-at document-start
  16. // @license GPL-3.0-only
  17. // ==/UserScript==
  18.  
  19. (() => {
  20. 'use strict';
  21.  
  22. // Constants
  23. const CONSTANTS = {
  24. PREFIX: '[DevTools Bypass]',
  25. LOG_LEVELS: {
  26. INFO: 'info',
  27. WARN: 'warn',
  28. ERROR: 'error',
  29. DEBUG: 'debug'
  30. },
  31. TIME_THRESHOLDS: {
  32. DEBUGGER: 80,
  33. CACHE: 30000
  34. },
  35. CACHE_TTL: {
  36. DEBUGGER_CHECK: 500
  37. }
  38. };
  39.  
  40. // Configuration
  41. const config = {
  42. antiDebugRegex: new RegExp([
  43. // debugger, debug(), etc.
  44. /[;\s]*(?:debugger|debug(?:ger)?|breakpoint)[\s;]*/,
  45. // new Function('debugger')(), etc.
  46. /(?:eval|Function|setTimeout|setInterval)\s*\(\s*['"`].*?debugger.*?['"`]\s*\)/,
  47. // devtools checks
  48. /(?:isDevTools?|devtools?|debugMode|debug_enabled)\s*[=:]\s*(?:true|1|!0|yes)/,
  49. // console checks
  50. /console\.(?:log|warn|error|info|debug|trace|dir|table)/,
  51. // source map urls
  52. /\/\/[#@]\s*source(?:Mapping)?URL\s*=.*/,
  53. // Known anti-debug library patterns
  54. /FuckDevTools|devtools-detector/
  55. ].map(r => r.source).join('|'), 'gi'),
  56.  
  57. consoleProps: ['log', 'warn', 'error', 'info', 'debug', 'trace', 'dir', 'dirxml', 'table', 'profile', 'group', 'groupEnd', 'time', 'timeEnd'],
  58. cutoffs: {
  59. debugger: { amount: 30, within: CONSTANTS.TIME_THRESHOLDS.CACHE },
  60. debuggerThrow: { amount: 30, within: CONSTANTS.TIME_THRESHOLDS.CACHE }
  61. },
  62. bypassTriggers: {
  63. timeThreshold: CONSTANTS.TIME_THRESHOLDS.DEBUGGER,
  64. stackDepth: 30,
  65. recursionLimit: 50
  66. },
  67. debuggerDetector: {
  68. cacheTTL: CONSTANTS.CACHE_TTL.DEBUGGER_CHECK,
  69. historyCleanupInterval: 60000 // 1 minute
  70. },
  71. logging: {
  72. enabled: true,
  73. prefix: CONSTANTS.PREFIX,
  74. levels: Object.values(CONSTANTS.LOG_LEVELS),
  75. detailedErrors: true,
  76. monitorAPI: false,
  77. monitorDOM: false
  78. },
  79. protection: {
  80. preventDevToolsKeys: true,
  81. hideStackTraces: true,
  82. sanitizeErrors: true,
  83. obfuscateTimers: true,
  84. preventRightClick: true,
  85. preventViewSource: true,
  86. preventCopy: true,
  87. preventPaste: true,
  88. preventPrint: true,
  89. preventSave: true
  90. }
  91. };
  92.  
  93. // Logger class
  94. class Logger {
  95. static #instance;
  96. #lastLog = 0;
  97. #logCount = 0;
  98. #logBuffer = [];
  99.  
  100. constructor() {
  101. if (Logger.#instance) {
  102. return Logger.#instance;
  103. }
  104. Logger.#instance = this;
  105. this.#setupBufferFlush();
  106. }
  107.  
  108. #setupBufferFlush() {
  109. setInterval(() => {
  110. if (this.#logBuffer.length) {
  111. this.#flushBuffer();
  112. }
  113. }, 1000);
  114. }
  115.  
  116. #flushBuffer() {
  117. this.#logBuffer.forEach(({level, args}) => {
  118. console[level](config.logging.prefix, ...args);
  119. });
  120. this.#logBuffer = [];
  121. }
  122.  
  123. #shouldLog() {
  124. const now = Date.now();
  125. if (now - this.#lastLog > 1000) {
  126. this.#logCount = 0;
  127. }
  128. this.#lastLog = now;
  129. return ++this.#logCount <= 10;
  130. }
  131.  
  132. #log(level, ...args) {
  133. if (!config.logging.enabled || !this.#shouldLog()) return;
  134. this.#logBuffer.push({ level, args });
  135. }
  136.  
  137. info(...args) { this.#log(CONSTANTS.LOG_LEVELS.INFO, ...args); }
  138. warn(...args) { this.#log(CONSTANTS.LOG_LEVELS.WARN, ...args); }
  139. error(...args) { this.#log(CONSTANTS.LOG_LEVELS.ERROR, ...args); }
  140. debug(...args) { this.#log(CONSTANTS.LOG_LEVELS.DEBUG, ...args); }
  141. }
  142.  
  143. // Original functions store
  144. const OriginalFunctions = {
  145. defineProperty: Object.defineProperty,
  146. getOwnPropertyDescriptor: Object.getOwnPropertyDescriptor,
  147. setTimeout: window.setTimeout,
  148. setInterval: window.setInterval,
  149. Date: window.Date,
  150. now: Date.now,
  151. performance: window.performance,
  152. Function: window.Function,
  153. eval: window.eval,
  154. console: {},
  155. toString: Function.prototype.toString,
  156. preventDefault: Event.prototype.preventDefault,
  157. getComputedStyle: window.getComputedStyle,
  158. addEventListener: window.addEventListener,
  159. removeEventListener: window.removeEventListener,
  160. fetch: window.fetch,
  161. XMLHttpRequest: window.XMLHttpRequest,
  162.  
  163. initConsole() {
  164. config.consoleProps.forEach(prop => {
  165. if (console[prop]) {
  166. this.console[prop] = console[prop].bind(console);
  167. }
  168. });
  169. }
  170. };
  171.  
  172. OriginalFunctions.initConsole();
  173.  
  174. // Debugger detector
  175. class DebuggerDetector {
  176. static #detectionCache = new Map();
  177. static #detectionHistory = [];
  178. static #historyCleanupTimer = null;
  179.  
  180. static isPresent() {
  181. try {
  182. const cacheKey = 'debugger_check';
  183. const cached = this.#detectionCache.get(cacheKey);
  184. if (cached && Date.now() - cached.timestamp < config.debuggerDetector.cacheTTL) {
  185. return cached.result;
  186. }
  187.  
  188. const startTime = OriginalFunctions.now.call(Date);
  189. new Function('debugger;')();
  190. const timeDiff = OriginalFunctions.now.call(Date) - startTime;
  191.  
  192. const result = timeDiff > config.bypassTriggers.timeThreshold;
  193. this.#detectionCache.set(cacheKey, {
  194. result,
  195. timestamp: Date.now()
  196. });
  197.  
  198. this.#detectionHistory.push({
  199. timestamp: Date.now(),
  200. result,
  201. timeDiff
  202. });
  203.  
  204. // Keep history for 5 minutes
  205. const fiveMinutesAgo = Date.now() - 300000;
  206. this.#detectionHistory = this.#detectionHistory.filter(entry => entry.timestamp > fiveMinutesAgo);
  207.  
  208. return result;
  209. } catch {
  210. return false;
  211. }
  212. }
  213.  
  214. static analyzeStack() {
  215. try {
  216. const stack = new Error().stack;
  217. if (!stack) return { depth: 0, hasDebugKeywords: false, isRecursive: false, suspiciousPatterns: [], stackHash: '' };
  218. const frames = stack.split('\n');
  219. const uniqueFrames = new Set(frames);
  220.  
  221. return {
  222. depth: frames.length,
  223. hasDebugKeywords: config.antiDebugRegex.test(stack),
  224. isRecursive: uniqueFrames.size < frames.length,
  225. suspiciousPatterns: this.#detectSuspiciousPatterns(stack),
  226. stackHash: this.#generateStackHash(stack)
  227. };
  228. } catch {
  229. return {
  230. depth: 0,
  231. hasDebugKeywords: false,
  232. isRecursive: false,
  233. suspiciousPatterns: [],
  234. stackHash: ''
  235. };
  236. }
  237. }
  238.  
  239. static #detectSuspiciousPatterns(stack) {
  240. const patterns = [
  241. /eval.*?\(/g,
  242. /Function.*?\(/g,
  243. /debugger/g,
  244. /debug/g,
  245. /DevTools/g,
  246. /console\./g,
  247. /chrome-extension/g
  248. ];
  249. return patterns.filter(pattern => pattern.test(stack));
  250. }
  251.  
  252. static #generateStackHash(stack) {
  253. return Array.from(stack).reduce((hash, char) => {
  254. hash = ((hash << 5) - hash) + char.charCodeAt(0);
  255. return hash & hash;
  256. }, 0).toString(36);
  257. }
  258.  
  259. static getDetectionStats() {
  260. const now = Date.now();
  261. const recentDetections = this.#detectionHistory.filter(entry =>
  262. entry.timestamp > now - 60000
  263. );
  264.  
  265. return {
  266. total: recentDetections.length,
  267. positive: recentDetections.filter(entry => entry.result).length,
  268. averageTime: recentDetections.reduce((acc, curr) =>
  269. acc + curr.timeDiff, 0) / (recentDetections.length || 1)
  270. };
  271. }
  272.  
  273. static startHistoryCleanup() {
  274. if (this.#historyCleanupTimer) return;
  275. this.#historyCleanupTimer = setInterval(() => {
  276. const fiveMinutesAgo = Date.now() - 300000;
  277. this.#detectionHistory = this.#detectionHistory.filter(entry => entry.timestamp > fiveMinutesAgo);
  278. }, config.debuggerDetector.historyCleanupInterval);
  279. }
  280. }
  281.  
  282. // Protection class
  283. class Protection {
  284. static #combinedPattern = null;
  285. static applyAll() {
  286. this.#patchGlobalDebugVariables();
  287. this.#protectTimers();
  288. this.#protectTiming();
  289. this.#protectFunction();
  290. this.#protectStack();
  291. this.#protectEval();
  292. this.#protectConsole();
  293. this.#setupMutationObserver();
  294. this.#protectDeveloperKeys();
  295. this.#protectRightClick();
  296. this.#protectNetwork();
  297. this.#protectStorage();
  298. this.#protectClipboard();
  299. this.#protectPrinting();
  300. this.#protectWebWorkers();
  301. this.#applyDeveloperHelpers();
  302. }
  303.  
  304. static #protectTimers() {
  305. const wrapTimer = original => {
  306. return function(handler, timeout, ...args) {
  307. if (typeof handler !== 'function') {
  308. return original.apply(this, arguments);
  309. }
  310.  
  311. const wrappedHandler = function() {
  312. try {
  313. if (DebuggerDetector.isPresent()) return;
  314. return handler.apply(this, arguments);
  315. } catch (e) {
  316. if (e.message?.includes('debugger')) return;
  317. throw e;
  318. }
  319. };
  320.  
  321. if (config.protection.obfuscateTimers) {
  322. timeout = Math.max(1, timeout + (Math.random() * 20 - 10));
  323. }
  324.  
  325. return original.call(this, wrappedHandler, timeout, ...args);
  326. };
  327. };
  328.  
  329. window.setTimeout = wrapTimer(OriginalFunctions.setTimeout);
  330. window.setInterval = wrapTimer(OriginalFunctions.setInterval);
  331. }
  332.  
  333. static #protectTiming() {
  334. const timeOffset = Math.random() * 25;
  335. const safeNow = () => OriginalFunctions.now.call(Date) + timeOffset;
  336.  
  337. Object.defineProperty(Date, 'now', {
  338. value: safeNow,
  339. configurable: false,
  340. writable: false
  341. });
  342.  
  343. if (window.performance?.now) {
  344. Object.defineProperty(window.performance, 'now', {
  345. value: safeNow,
  346. configurable: false,
  347. writable: false
  348. });
  349. }
  350. }
  351.  
  352. static #protectFunction() {
  353. const handler = {
  354. apply(target, thisArg, args) {
  355. if (typeof args[0] === 'string') {
  356. args[0] = Protection.#cleanCode(args[0]);
  357. }
  358. return Reflect.apply(target, thisArg, args);
  359. },
  360. construct(target, args) {
  361. if (typeof args[0] === 'string') {
  362. args[0] = Protection.#cleanCode(args[0]);
  363. }
  364. return Reflect.construct(target, args);
  365. }
  366. };
  367.  
  368. window.Function = new Proxy(OriginalFunctions.Function, handler);
  369. if (typeof unsafeWindow !== 'undefined') {
  370. unsafeWindow.Function = window.Function;
  371. }
  372. }
  373.  
  374. static #protectStack() {
  375. if (!config.protection.hideStackTraces) return;
  376.  
  377. // V8-specific API for stack trace customization
  378. if ('prepareStackTrace' in Error) {
  379. Error.prepareStackTrace = (error, stack) => {
  380. const stackString = [error.toString(), ...stack.map(frame => ` at ${frame}`)].join('\n');
  381. return Protection.#cleanCode(stackString);
  382. };
  383. return;
  384. }
  385. // Standard-based approach
  386. try {
  387. const originalStackDescriptor = Object.getOwnPropertyDescriptor(Error.prototype, 'stack');
  388. if (originalStackDescriptor?.get) {
  389. Object.defineProperty(Error.prototype, 'stack', {
  390. get() {
  391. const originalStack = originalStackDescriptor.get.call(this);
  392. return Protection.#cleanCode(originalStack);
  393. },
  394. configurable: true,
  395. });
  396. }
  397. } catch (e) {
  398. logger.error('Failed to protect stack traces:', e);
  399. }
  400. }
  401.  
  402. static #protectEval() {
  403. const safeEval = function(code) {
  404. if (typeof code === 'string') {
  405. if (DebuggerDetector.isPresent()) return;
  406. return OriginalFunctions.eval.call(this, Protection.#cleanCode(code));
  407. }
  408. return OriginalFunctions.eval.apply(this, arguments);
  409. };
  410.  
  411. Object.defineProperty(window, 'eval', {
  412. value: safeEval,
  413. configurable: false,
  414. writable: false
  415. });
  416.  
  417. if (typeof unsafeWindow !== 'undefined') {
  418. unsafeWindow.eval = safeEval;
  419. }
  420. }
  421.  
  422. static #protectConsole() {
  423. const consoleHandler = {
  424. get(target, prop) {
  425. if (!config.consoleProps.includes(prop)) return target[prop];
  426.  
  427. return function(...args) {
  428. if (DebuggerDetector.isPresent()) return;
  429. return OriginalFunctions.console[prop]?.apply(console, args);
  430. };
  431. },
  432. set(target, prop, value) {
  433. if (config.consoleProps.includes(prop)) return true;
  434. target[prop] = value;
  435. return true;
  436. }
  437. };
  438.  
  439. window.console = new Proxy(console, consoleHandler);
  440. }
  441.  
  442. static #setupMutationObserver() {
  443. new MutationObserver(mutations => {
  444. mutations.forEach(mutation => {
  445. mutation.addedNodes.forEach(node => {
  446. if (node.nodeType === Node.ELEMENT_NODE) {
  447. // Clean script content
  448. if (node.tagName === 'SCRIPT') {
  449. const originalContent = node.textContent;
  450. const cleanedContent = Protection.#cleanCode(originalContent);
  451. if (originalContent !== cleanedContent) {
  452. node.textContent = cleanedContent;
  453. }
  454. }
  455. // Clean attributes
  456. for (const attr of node.attributes) {
  457. if (attr.name.startsWith('on') && Protection.#cleanCode(attr.value) !== attr.value) {
  458. node.removeAttribute(attr.name);
  459. }
  460. }
  461. }
  462. });
  463. });
  464. }).observe(document.documentElement, {
  465. childList: true,
  466. subtree: true,
  467. });
  468. }
  469.  
  470. static #protectDeveloperKeys() {
  471. if (!config.protection.preventDevToolsKeys && !config.protection.preventViewSource) return;
  472.  
  473. const handler = e => {
  474. const key = e.key.toUpperCase();
  475. const ctrl = e.ctrlKey;
  476. const shift = e.shiftKey;
  477. const alt = e.altKey;
  478.  
  479. const isDevToolsKey =
  480. key === 'F12' ||
  481. (ctrl && shift && (key === 'I' || key === 'J' || key === 'C')) ||
  482. (ctrl && key === 'U');
  483. if (isDevToolsKey) {
  484. e.preventDefault();
  485. e.stopPropagation();
  486. }
  487. };
  488.  
  489. window.addEventListener('keydown', handler, true);
  490. }
  491.  
  492. static #protectRightClick() {
  493. if (!config.protection.preventRightClick) return;
  494.  
  495. window.addEventListener('contextmenu', e => {
  496. e.preventDefault();
  497. e.stopPropagation();
  498. return false;
  499. }, true);
  500. }
  501.  
  502. static #protectNetwork() {
  503. window.fetch = async function(...args) {
  504. if (DebuggerDetector.isPresent()) {
  505. throw new Error('Network request blocked');
  506. }
  507. return OriginalFunctions.fetch.apply(this, args);
  508. };
  509.  
  510. window.XMLHttpRequest = function() {
  511. const xhr = new OriginalFunctions.XMLHttpRequest();
  512. const originalOpen = xhr.open;
  513. xhr.open = function(...args) {
  514. if (DebuggerDetector.isPresent()) {
  515. throw new Error('Network request blocked');
  516. }
  517. return originalOpen.apply(xhr, args);
  518. };
  519. return xhr;
  520. };
  521. }
  522.  
  523. static #protectStorage() {
  524. const storageHandler = {
  525. get(target, prop) {
  526. if (DebuggerDetector.isPresent()) return null;
  527. return target[prop];
  528. },
  529. set(target, prop, value) {
  530. if (DebuggerDetector.isPresent()) return true;
  531. target[prop] = value;
  532. return true;
  533. }
  534. };
  535.  
  536. window.localStorage = new Proxy(window.localStorage, storageHandler);
  537. window.sessionStorage = new Proxy(window.sessionStorage, storageHandler);
  538. }
  539.  
  540. static #protectClipboard() {
  541. const events = [];
  542. if (config.protection.preventCopy) events.push('copy', 'cut');
  543. if (config.protection.preventPaste) events.push('paste');
  544.  
  545. if (events.length) {
  546. events.forEach(eventName => {
  547. document.addEventListener(eventName, e => {
  548. e.preventDefault();
  549. e.stopPropagation();
  550. }, true);
  551. });
  552. }
  553. }
  554.  
  555. static #protectPrinting() {
  556. if (!config.protection.preventPrint) return;
  557.  
  558. window.addEventListener('beforeprint', e => {
  559. e.preventDefault();
  560. }, true);
  561.  
  562. window.addEventListener('afterprint', e => {
  563. e.preventDefault();
  564. }, true);
  565. }
  566.  
  567. static #protectWebWorkers() {
  568. window.Worker = function(scriptURL, options) {
  569. console.log('[Worker Created]', scriptURL);
  570. return new OriginalFunctions.Worker(scriptURL, options);
  571. };
  572. }
  573.  
  574. static #applyDeveloperHelpers() {
  575. if (config.logging.monitorAPI) {
  576. this.#monitorAPICalls();
  577. }
  578. if (config.logging.monitorDOM) {
  579. this.#monitorDOMEvents();
  580. }
  581. }
  582. static #patchGlobalDebugVariables() {
  583. const noop = () => {};
  584. Object.defineProperties(window, {
  585. 'debug': { value: noop, configurable: false, writable: false },
  586. 'debugger': { value: noop, configurable: false, writable: false },
  587. 'isDebuggerEnabled': { value: false, configurable: false, writable: false }
  588. });
  589. }
  590. static #monitorAPICalls() {
  591. const originalFetch = window.fetch;
  592. window.fetch = async (...args) => {
  593. logger.debug('[API Call]', ...args);
  594. return originalFetch.apply(this, args);
  595. };
  596. }
  597.  
  598. static #monitorDOMEvents() {
  599. new MutationObserver(mutations => {
  600. mutations.forEach(mutation => {
  601. logger.debug('[DOM Change]', mutation);
  602. });
  603. }).observe(document.documentElement, {
  604. childList: true,
  605. subtree: true,
  606. attributes: true,
  607. characterData: true
  608. });
  609. }
  610.  
  611. static #cleanCode(code) {
  612. if (typeof code !== 'string') return code;
  613. return code.replace(config.antiDebugRegex, '');
  614. }
  615. }
  616. // Main class
  617. class DevToolsBypass {
  618. static init() {
  619. try {
  620. DebuggerDetector.startHistoryCleanup();
  621. Protection.applyAll();
  622. logger.info('DevTools Bypass initialized successfully');
  623. } catch (e) {
  624. logger.error('Failed to initialize DevTools Bypass:', e);
  625. }
  626. }
  627. }
  628.  
  629. // Initialize
  630. DevToolsBypass.init();
  631. })();