反 devtools-detector 反调试 2

麻麻再也不怕 https://github.com/AEPKILL/devtools-detector 不让我调试啦!

/* eslint-disable no-multi-spaces */
/* eslint-disable no-loop-func */
/* eslint-disable no-return-assign */

// ==UserScript==
// @name               反 devtools-detector 反调试 2
// @name:zh-CN         反 devtools-detector 反调试 2
// @name:en            Anti devtools-detector 2
// @namespace          Anti-devtools-detector 2
// @version            0.2
// @description        麻麻再也不怕 https://github.com/AEPKILL/devtools-detector 不让我调试啦!
// @description:zh-CN  麻麻再也不怕 https://github.com/AEPKILL/devtools-detector 不让我调试啦!
// @description:en     Anti https://github.com/AEPKILL/devtools-detector
// @author             PY-DNG
// @license            GPL-v3
// @match              http*://blog.aepkill.com/demos/devtools-detector/*
// @icon               none
// @grant              none
// @unwrap
// @run-at             document-start
// ==/UserScript==

/* global eruda */

(function __MAIN__() {
    'use strict';

	const LOG_KAWAII_HIKARI = true;
	const TEXT_HIKARI = ['%cdevtools-detector has been disabled', 'color: green;'] //'嘿嘿嘿,hikari,嘿嘿嘿…';
	const HIKARI_CODE = 'https://greasyfork.org/scripts/456001/code/script.js';
	const launchHikari = function() {
		if (LOG_KAWAII_HIKARI) {
			// Draw an cute image of hikari in the console when devtools-detector attempting to launch
			// Use xhr instead of @require / @resource / in-script-code aims to speed up
			// userscript loading, make disable_launch as fast as possible
			const xhr = new XMLHttpRequest();
			xhr.open('GET', HIKARI_CODE);
			xhr.onerror = logTextHikari;
			xhr.onload = function() {
				try {
					const code = xhr.responseText + '\nconsole.log.apply(console, KAWAII_HIKARI)';
					const func = new Function(code);
					func();
				} catch(err) {
					logTextHikari();
				}
			}
			xhr.send();
		} else {
			// Or, just log some text
			logTextHikari();
		}

		function logTextHikari() {
			console.log.apply(console, TEXT_HIKARI);
		}
	}
	//disable_launch(launchHikari);
	console.log('%cdisabling devtools detector...', 'color: green;');
	disable_checkers();

	// Disable devtools-detector by hooking devtoolsDetector.launch
	function disable_launch(alt) {
		console.log('disabling launch');

		let map = new Map();
		const func = function() {
			delete Object.prototype.launch;
			Object.getPrototypeOf(this).launch = alt;
			this.launch();
		};
		Object.defineProperty(Object.prototype, 'launch', {
			set: function(f) {
				const checked = funcChecked(f);
				map.set(this, checked ? func : f);
				return true;
			},
			get: function() {
				return getVal(this);

				function getVal(obj) {
					return obj ? (map.has(obj) ? map.get(obj) : getVal(Object.getPrototypeOf(obj))) : undefined;
				}
			},
			configurable: true,
			enumerable: false
		});

		function funcChecked(f) {
			if (f && typeof f.toString === 'function') {
				const str = f.toString();
				if (typeof str === 'string' && str.includes('_detectLoopDelay')) {
					return true;
				}
			}
			return false;
		}
	}

	function disable_checkers() {
		const disablers = [function() {
			// Prevent hooked properties being overwritten
			const defineProperty = Object.defineProperty;
			const defineProperties = Object.defineProperties;
			hookValue(Object, ['defineProperty', 'defineProperties'], function() {
				const args = [...arguments];
				const target = args[0];
				if (args.length === 3) {
					args[1] = { [args[1]]: args[2] };
					delete args[2];
				}

				for (const [prop, desc] of Object.entries(args[1])) {
					if (hookValue.hookedObjs.has(target) && hookValue.hookedObjs.get(target).hasOwnProperty(prop)) {
						return dealDesc(target, prop, desc);
					} else {
						try {
							return defineProperties.apply(this, args);
						} catch(err) {
							console.error(err);
							debugger;
						}
					}
				}

				function dealDesc(obj, prop, desc) {
					if ('value' in desc) {
						// Allow setting value
						console.log('Allowed: setting value', obj, prop, desc);
						hookValue.hookedObjs.get(target)[prop] = desc.value;
					} else {
						// Block setting setter/getter
						// Do nothing
						debugger;
						console.log('Blocked: setting getter/setter', obj, prop, desc);
					}
					return obj;
				}
			});
		}, function() {
			const toString = Date.prototype.toString;
			hookValue(Date.prototype, 'toString');
			hookConsole('log', args => {
				const date = args[0];
				return date instanceof Date;
			});
		}, function() {
			hookValue(RegExp.prototype, 'toString');
			hookConsole('table', args => {
				const obj = args[0];
				return isObject(obj) && Object.keys(obj).every(key => key === 'dep') && obj.dep.toString() === '/ /';
			});
		}, function() {
			hookConsole('log', args => {
				const div = args[0];
				return div instanceof HTMLDivElement && typeof Object.getOwnPropertyDescriptor(div, 'id')?.get === 'function';
			});
		}, function() {
			if (typeof eruda !== 'undefined' && eruda?._devTools?._isShow === true) {
				eruda._devTools._isShow = 1;
			}
		}, function() {
			hookValue(Function.prototype, 'toString');
			hookConsole('log', args => {
				const func = args[0];
				return typeof func === 'function' && /function \w(){}/.test(func.toString());
			});
		}, function() {
			['log', 'table'].forEach(logger => {
				hookConsole(logger, args => {
					const arr = args[0];
					return Array.isArray(arr) && arr.length === 50 && arr.every(obj => isObject(obj) && Object.keys(obj).every(key => obj[key] === key));
				});
			});
		}, function() {
			console.clear = function() {};
		}];

		disablers.forEach(disabler => disabler());

		function hookConsole(logger, checker) {
			const log = console[logger];
			console[logger] = function() {
				if (checker(arguments)) {
					return;
				}
				log.apply(this, arguments);
			}
		}

		function hookValue(obj, props, value) {
			!hookValue.hookedObjs && (hookValue.hookedObjs = new Map());
			!Array.isArray(props) && (props = [props]);

			for (const prop of props) {
				const val = value === undefined ? obj[prop] : value;

				Object.defineProperty(obj, prop, {
					get: () => hookStore[prop],
					set: e => {},
					configurable: false,
					enumerable: Object.getOwnPropertyDescriptor(obj, prop).enumerable
				});

				!hookValue.hookedObjs.has(obj) && hookValue.hookedObjs.set(obj, {});
				const hookStore = hookValue.hookedObjs.get(obj);
				hookStore[prop] = val;
			}
		}
	}

	function isObject(val) {
		return typeof val === 'object' && val !== null;
	}
})();