Greasy Fork is available in English.
根据 https://github.com/AEPKILL/devtools-detector 的检测方法进行了一个逆向反检测...需要在哪些网站上运行,自己添加到脚本编辑器-设置-用户包括里面去
// ==UserScript== // @name 浏览器控制台防检测 // @namespace https://greasyfork.org/users/667968-pyudng // @version 0.10.1 // @description 根据 https://github.com/AEPKILL/devtools-detector 的检测方法进行了一个逆向反检测...需要在哪些网站上运行,自己添加到脚本编辑器-设置-用户包括里面去 // @author PY-DNG // @license MIT // @match http*://AddYour.OwnMatch/ // @match http*://blog.aepkill.com/demos/devtools-detector/ // @require https://update.greasyfork.org/scripts/456034/1546794/Basic%20Functions%20%28For%20userscripts%29.js // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @grant GM_addElement // @run-at document-start // ==/UserScript== /* eslint-disable no-multi-spaces */ /* eslint-disable no-return-assign */ /* global LogLevel DoLog Err $ $All $CrE $AEL $$CrE addStyle detectDom destroyEvent copyProp copyProps parseArgs escJsStr replaceText getUrlArgv dl_browser dl_GM AsyncManager queueTask FunctionLoader loadFuncs require isLoaded */ (function __MAIN__() { 'use strict'; const CONST = { TextAllLang: { DEFAULT: 'zh-CN', 'zh-CN': {} } }; // Init language const i18n = Object.keys(CONST.TextAllLang).includes(navigator.language) ? navigator.language : CONST.TextAllLang.DEFAULT; CONST.Text = CONST.TextAllLang[i18n]; // As a common dependency for almost all funcs, load it synchrously to execute funcs as soon as poosible // If we write utils as a normal func which executes in loadFuncs() and serves as a func module through require('utils'), // func that depends on utils must wait until utils load asynchrously, which will takes a lot of time. // Execute as soon as possible is essential to ensure we have time advantage agains devtools detectors. const utils = (function() { const win = typeof unsafeWindow === 'undefined' ? window : unsafeWindow; const SavedValues = { _targets: ['Object', 'Function', 'Symbol', 'Reflect', 'performance', 'setTimeout', 'Proxy'], _thiskey: Symbol('SavedValues.this-key'), original: {}, // original property descriptors bound: {} // property descriptors with "this" bound for whose value is function }; for (const target_name of SavedValues._targets) { const source = win[target_name]; if (['object', 'function'].includes(typeof source) && source !== null) { // Save original const obj_original = SavedValues.original[target_name] = { [SavedValues._thiskey]: source }; const obj_bound = SavedValues.bound[target_name] = {} for (const prop of Reflect.ownKeys(source)) { const desc_original = Object.getOwnPropertyDescriptor(source, prop); Object.defineProperty(obj_original, prop, desc_original); const desc_bound = { ...desc_original }; if (typeof desc_bound.value === 'function') { desc_bound.value = desc_bound.value.bind(source); } Object.defineProperty(obj_bound, prop, desc_bound); } } else { SavedValues.original[target_name] = source; SavedValues.bound[target_name] = source; } } function hook(obj, prop, func) { const ori_func = obj[prop]; const hook_func = obj[prop] = wrap(ori_func, func); return { hook_func, ori_func, unhook }; function unhook() { obj[prop] = ori_func; } function wrap(ori_func, func) { const toString = ori_func.toString; return new SavedValues.original.Proxy[SavedValues._thiskey](ori_func, { construct(target, argumentsList, newTarget) { return new func(target, ...argumentsList); }, apply(target, thisArg, argumentsList) { return func.call(thisArg, target, ...argumentsList); }, get(target, prop, receiver) { if (prop === 'toString') { return wrap(target[prop], function() { return toString.call(this); }); } return target[prop]; } }); } } return { window: win, SavedValues, hook } }) (); loadFuncs([{ id: 'log-toString-trap', desc: '处理各种通过自定义toString/字符串getter,再log到console的检测方法', func() { const console = utils.window.console; const toStringModified = obj => Function.prototype.toString.call(obj.toString) !== 'function toString() { [native code] }'; utils.hook(console, 'log', function(log, obj) { if (obj instanceof Date && toStringModified(obj)) { DoLog(LogLevel.Success, ['拦截了一次 date-toString-trap']); return log.call(this); } if (obj instanceof Date && toStringModified(obj)) { DoLog(LogLevel.Success, ['拦截了一次 date-toString-trap']); return log.call(this); } if (obj instanceof HTMLElement && utils.SavedValues.original.Object.hasOwnProperty.call(obj, 'id')) { DoLog(LogLevel.Success, ['拦截了一次 element-id-trap']); return log.call(this); } if (obj instanceof Function && toStringModified(obj)) { DoLog(LogLevel.Success, ['拦截了一次 function-toString-trap']); return log.call(this); } if (obj instanceof RegExp && toStringModified(obj)) { DoLog(LogLevel.Success, ['拦截了一次 regexp-toString-trap']); return log.call(this); } const args = [...arguments]; args.shift(); return log.apply(this, args); }); utils.hook(console, 'table', function(table, obj) { for (const prop of utils.SavedValues.bound.Reflect.ownKeys(obj)) { const val = obj[prop]; if (val instanceof RegExp && toStringModified(val)) { DoLog(LogLevel.Success, ['拦截了一次 dep-reg-trap']); return table.call(this); } } const args = [...arguments]; args.shift(); return table.apply(this, args); }); } }, { id: 'time-sync', desc: '处理各种利用时间检测的方法;原理是让获取时间的函数在一次事件循环里返回同一个值', func() { return { getTime: { performance: hookTimeFunc(utils.window.performance, 'now'), date: hookTimeFunc(utils.window.Date, 'now'), dateInstance: hookTimeFunc(utils.window.Date, 'getTime', true) } }; function hookTimeFunc(obj, funcname, construct=false) { // 存储当前事件循环里,performance.time返回的值 // 确切地说,是从一次work调用到下一次work调用之间,返回相同的值 let time; // hook performance.now,始终返回time的值 const getTime = construct ? (function() { const func = obj.prototype[funcname]; return function() { return func.call(new obj(), ...arguments); }; }) () : obj[funcname].bind(obj); Object.defineProperty(construct ? obj.prototype : obj, funcname, { get() { return function() { return time; } }, set() { return true; }, }); // work函数,每次事件循环执行一次,清空 function work() { const setTimeout = utils.SavedValues.original.setTimeout[utils.SavedValues._thiskey].bind(utils.window); setTimeout(work); time = getTime(); } work(); return getTime; } } }, { id: 'no-debugger', desc: '拦截debugger语句', func() { utils.hook(Function.prototype, 'constructor', function(target, ...args) { const code = [...args].pop(); const is_debugger = code.replaceAll(/[\s;]/g, '') === 'debugger'; return is_debugger ? target() : target.apply(this, args); }); utils.hook(utils.window, 'Function', function(target, ...args) { const code = [...args].pop(); const is_debugger = code.replaceAll(/[\s;]/g, '') === 'debugger'; return is_debugger ? target() : target.apply(this, args); }); } }, { id: 'console-no-clear', desc: '防止调用console.clear()循环清除控制台', func() { const console = utils.window.console; const clear = console._clear = console.clear; // 提供选项彻底禁用console.clear let no_clear = isEnabled(); GM_registerMenuCommand('彻底禁用console.clear', function() { no_clear = !no_clear; if (no_clear) { enable(); console._log(`%c已彻底禁用%cconsole.clear\n%c如需清除控制台,请使用控制台清除按钮,或者调用%cconsole._clear()`, 'color: #66cc00;', 'color: orange;', 'color: #66ccff;', 'color: orange;'); } else { disable(); console._log('%c已重新启用%cconsole.clear', 'color: #66cc00;', 'color: orange;'); } }); // 自动拦截高频清除 const min_interval = 750, startup_protection = 5000; let last_clear = getTime(); const hook = utils.hook(console, 'clear', function() { const startup_time = getTime(); const time_past = startup_time - last_clear; const can_clear = !no_clear && time_past > min_interval && startup_time > startup_protection; can_clear && clear(); last_clear = startup_time; }); console.log([ `%c[${GM_info.script.name}] %c已拦截%cconsole.clear%c以防止控制台被高频循环清除`, `当%cconsole.clear%c距离上次调用的时间间隔小于%c${ min_interval / 1000 }秒%c时,将不执行清除`, `同时,页面加载的%c前${ startup_protection / 1000 }秒%c也不会执行清除`, `需要注意的是,当将浏览器/标签页切换到后台时,循环清除任务可能会被浏览器放慢,从而绕过高频清除限制`, '', `您也可通过手动开启 %c"彻底禁用console.clear"%c 功能彻底禁止%cconsole.clear%c清除控制台`, `当前 %c"彻底禁用console.clear"%c 功能%c${ no_clear ? '已开启' : '未开启'}%c` ].join('\n'), ...[ 'color: #9999ff;', '', 'color: orange;', '', 'color: orange;', '', 'color: orange;', '', 'color: orange;', '', 'color: #cc9966;', '', 'color: orange;', '', 'color: #cc9966;', '', 'color: #66cc00;', '' ]); return { clear, get clear_diabled() { return no_clear; }, set clear_diabled(val) { (no_clear = val) ? enable() : disable(); } } // 获取精确的(未被hook修改的)页面加载到现在的时间戳 function getTime() { return isLoaded('time-sync') ? require('time-sync').getTime.performance() : performance.now(); } function getEnabledHostList() { const enabled_list = GM_getValue('noclear-hosts', []); return enabled_list; } function saveEnabledHostList(enabled_list) { GM_setValue('noclear-hosts', enabled_list); } function isEnabled() { const host = location.host; const enabled_list = getEnabledHostList(); return enabled_list.includes(host); } function enable() { const host = location.host; const enabled_list = getEnabledHostList(); !enabled_list.includes(host) && enabled_list.push(host); saveEnabledHostList(enabled_list); } function disable() { const host = location.host; const enabled_list = getEnabledHostList(); enabled_list.includes(host) && enabled_list.splice(enabled_list.indexOf(host), 1); saveEnabledHostList(enabled_list); } } }, { id: 'console-filter', desc: '提供选项供用户拦截控制台日志', func() { let filter_enabled = isEnabled(); GM_registerMenuCommand('禁止网页输出日志', function() { filter_enabled = !filter_enabled; if (filter_enabled) { enable(); console._log(`%c已禁止网页输出日志到控制台\n%c如需调用%cconsole.xxx%c输出,请在xxx前加一个下划线%c"_"%c再调用,如 %cconsole._log('Hello, world');`, 'color: #66cc00;', 'color: #66ccff;', 'color: orange;', 'color: #66ccff;', 'color: orange', 'color: #66ccff;', 'color: orange'); } else { disable(); console._log('%c已允许网页输出日志到控制台', 'color: #66cc00;'); } }); const console = utils.window.console; Object.keys(console).forEach(prop => { if (['clear', '_clear'].includes(prop)) { return; } console['_' + prop] = console[prop]; utils.hook(console, prop, function(func, ...args) { if (!filter_enabled) { return func.apply(this, args); } }); }); return { get enabled() { return filter_enabled; }, set enabled(val) { val ? enable() : disable(); } }; function getEnabledHostList() { const enabled_list = GM_getValue('filtered-hosts', []); return enabled_list; } function saveEnabledHostList(enabled_list) { GM_setValue('filtered-hosts', enabled_list); } function isEnabled() { const host = location.host; const enabled_list = getEnabledHostList(); return enabled_list.includes(host); } function enable() { const host = location.host; const enabled_list = getEnabledHostList(); !enabled_list.includes(host) && enabled_list.push(host); saveEnabledHostList(enabled_list); } function disable() { const host = location.host; const enabled_list = getEnabledHostList(); enabled_list.includes(host) && enabled_list.splice(enabled_list.indexOf(host), 1); saveEnabledHostList(enabled_list); } } }, { id: 'user-code', desc: '在文档开始加载时运行用户提供的自定义代码', func() { // 运行用户代码 const user_code = getUserCode(); GM_addElement(document.head, 'script', { textContent: user_code }); // 接收用户代码 GM_registerMenuCommand('自定义用户代码', function() { const user_input = prompt('您可以在这里输入一些您自己的javascript代码,脚本会在页面开始加载时执行它', getUserCode()); if (user_input === null) { return; } saveUserCode(user_input); }); function getUserCode() { const user_code = GM_getValue('user-code', ''); return user_code; } function saveUserCode(user_code) { GM_setValue('user-code', user_code); } } }]); })();