// ============================================================================= // Stealth Script — инжектируется в MAIN world на каждой странице ДО загрузки // Подменяет все известные маркеры автоматизации браузера // ============================================================================= (function() { 'use strict'; // ------------------------------------------------------------------------- // 1. navigator.webdriver = false (подстраховка к Chrome-флагу) // ------------------------------------------------------------------------- try { Object.defineProperty(navigator, 'webdriver', { get: () => false, configurable: true, }); } catch (e) {} // Также удаляем из prototype try { const proto = Object.getPrototypeOf(navigator); if (proto) { Object.defineProperty(proto, 'webdriver', { get: () => false, configurable: true, }); } } catch (e) {} // ------------------------------------------------------------------------- // 2. navigator.plugins — эмулируем стандартные плагины Chrome // ------------------------------------------------------------------------- try { const makeMimeType = (type, suffixes, description, plugin) => { const mt = Object.create(MimeType.prototype); Object.defineProperties(mt, { type: { get: () => type }, suffixes: { get: () => suffixes }, description: { get: () => description }, enabledPlugin: { get: () => plugin }, }); return mt; }; const makePlugin = (name, description, filename, mimeTypes) => { const p = Object.create(Plugin.prototype); const mimes = []; Object.defineProperties(p, { name: { get: () => name }, description: { get: () => description }, filename: { get: () => filename }, length: { get: () => mimes.length }, }); mimeTypes.forEach((mt, i) => { const mime = makeMimeType(mt.type, mt.suffixes, mt.description, p); mimes.push(mime); Object.defineProperty(p, i, { get: () => mime }); Object.defineProperty(p, mt.type, { get: () => mime }); }); return p; }; const pluginData = [ { name: 'Chrome PDF Plugin', description: 'Portable Document Format', filename: 'internal-pdf-viewer', mimeTypes: [ { type: 'application/x-google-chrome-pdf', suffixes: 'pdf', description: 'Portable Document Format' }, ], }, { name: 'Chrome PDF Viewer', description: '', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', mimeTypes: [ { type: 'application/pdf', suffixes: 'pdf', description: '' }, ], }, { name: 'Native Client', description: '', filename: 'internal-nacl-plugin', mimeTypes: [ { type: 'application/x-nacl', suffixes: '', description: 'Native Client Executable' }, { type: 'application/x-pnacl', suffixes: '', description: 'Portable Native Client Executable' }, ], }, ]; const plugins = pluginData.map(pd => makePlugin(pd.name, pd.description, pd.filename, pd.mimeTypes) ); const pluginArray = Object.create(PluginArray.prototype); Object.defineProperty(pluginArray, 'length', { get: () => plugins.length }); plugins.forEach((p, i) => { Object.defineProperty(pluginArray, i, { get: () => p }); Object.defineProperty(pluginArray, p.name, { get: () => p }); }); Object.defineProperty(pluginArray, 'refresh', { value: () => {} }); Object.defineProperty(pluginArray, 'item', { value: (index) => plugins[index] || null, }); Object.defineProperty(pluginArray, 'namedItem', { value: (name) => plugins.find(p => p.name === name) || null, }); // Итератор Object.defineProperty(pluginArray, Symbol.iterator, { value: function* () { for (const p of plugins) yield p; }, }); Object.defineProperty(navigator, 'plugins', { get: () => pluginArray, configurable: true, }); } catch (e) {} // ------------------------------------------------------------------------- // 3. navigator.mimeTypes — эмулируем // ------------------------------------------------------------------------- try { const mimeData = [ { type: 'application/pdf', suffixes: 'pdf', description: '' }, { type: 'application/x-google-chrome-pdf', suffixes: 'pdf', description: 'Portable Document Format' }, { type: 'application/x-nacl', suffixes: '', description: 'Native Client Executable' }, { type: 'application/x-pnacl', suffixes: '', description: 'Portable Native Client Executable' }, ]; const mimeTypeArray = Object.create(MimeTypeArray.prototype); const mimes = mimeData.map(md => { const mt = Object.create(MimeType.prototype); Object.defineProperties(mt, { type: { get: () => md.type }, suffixes: { get: () => md.suffixes }, description: { get: () => md.description }, enabledPlugin: { get: () => navigator.plugins[0] }, }); return mt; }); Object.defineProperty(mimeTypeArray, 'length', { get: () => mimes.length }); mimes.forEach((m, i) => { Object.defineProperty(mimeTypeArray, i, { get: () => m }); Object.defineProperty(mimeTypeArray, m.type, { get: () => m }); }); Object.defineProperty(mimeTypeArray, 'item', { value: (index) => mimes[index] || null, }); Object.defineProperty(mimeTypeArray, 'namedItem', { value: (name) => mimes.find(m => m.type === name) || null, }); Object.defineProperty(mimeTypeArray, Symbol.iterator, { value: function* () { for (const m of mimes) yield m; }, }); Object.defineProperty(navigator, 'mimeTypes', { get: () => mimeTypeArray, configurable: true, }); } catch (e) {} // ------------------------------------------------------------------------- // 4. navigator.languages // ------------------------------------------------------------------------- try { Object.defineProperty(navigator, 'languages', { get: () => ['ru-RU', 'ru', 'en-US', 'en'], configurable: true, }); } catch (e) {} // ------------------------------------------------------------------------- // 5. navigator.pdfViewerEnabled // ------------------------------------------------------------------------- try { Object.defineProperty(navigator, 'pdfViewerEnabled', { get: () => true, configurable: true, }); } catch (e) {} // ------------------------------------------------------------------------- // 6. window.chrome — эмулируем объект chrome // ------------------------------------------------------------------------- try { if (!window.chrome) { window.chrome = {}; } if (!window.chrome.runtime) { window.chrome.runtime = { connect: function() { return { onMessage: { addListener: function() {} }, postMessage: function() {}, onDisconnect: { addListener: function() {} } }; }, sendMessage: function() {}, onMessage: { addListener: function() {}, removeListener: function() {} }, onConnect: { addListener: function() {}, removeListener: function() {} }, id: undefined, }; } // chrome.app if (!window.chrome.app) { window.chrome.app = { isInstalled: false, InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' }, RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' }, getDetails: function() { return null; }, getIsInstalled: function() { return false; }, runningState: function() { return 'cannot_run'; }, }; } // chrome.csi if (!window.chrome.csi) { window.chrome.csi = function() { return { startE: Date.now(), onloadT: Date.now(), pageT: Math.random() * 1000 + 500, tran: 15, }; }; } // chrome.loadTimes if (!window.chrome.loadTimes) { window.chrome.loadTimes = function() { return { commitLoadTime: Date.now() / 1000, connectionInfo: 'h2', finishDocumentLoadTime: Date.now() / 1000, finishLoadTime: Date.now() / 1000, firstPaintAfterLoadTime: 0, firstPaintTime: Date.now() / 1000, navigationType: 'Other', npnNegotiatedProtocol: 'h2', requestTime: Date.now() / 1000 - 0.16, startLoadTime: Date.now() / 1000 - 0.16, wasAlternateProtocolAvailable: false, wasFetchedViaSpdy: true, wasNpnNegotiated: true, }; }; } } catch (e) {} // ------------------------------------------------------------------------- // 7. Permissions.query — подменяем результат для notifications // ------------------------------------------------------------------------- try { const originalQuery = window.Permissions.prototype.query; window.Permissions.prototype.query = function(parameters) { if (parameters.name === 'notifications') { return Promise.resolve({ state: Notification.permission }); } return originalQuery.call(this, parameters); }; } catch (e) {} // ------------------------------------------------------------------------- // 8. navigator.connection — эмулируем NetworkInformation // ------------------------------------------------------------------------- try { if (!navigator.connection) { Object.defineProperty(navigator, 'connection', { get: () => ({ effectiveType: '4g', rtt: 50, downlink: 10, saveData: false, onchange: null, addEventListener: function() {}, removeEventListener: function() {}, dispatchEvent: function() { return true; }, }), configurable: true, }); } } catch (e) {} // ------------------------------------------------------------------------- // 9. navigator.hardwareConcurrency — реалистичное значение // ------------------------------------------------------------------------- try { Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8, configurable: true, }); } catch (e) {} // ------------------------------------------------------------------------- // 10. navigator.deviceMemory — реалистичное значение // ------------------------------------------------------------------------- try { Object.defineProperty(navigator, 'deviceMemory', { get: () => 8, configurable: true, }); } catch (e) {} // ------------------------------------------------------------------------- // 11. navigator.maxTouchPoints — десктоп = 0 // ------------------------------------------------------------------------- try { Object.defineProperty(navigator, 'maxTouchPoints', { get: () => 0, configurable: true, }); } catch (e) {} // ------------------------------------------------------------------------- // 12. navigator.platform — Windows // ------------------------------------------------------------------------- try { Object.defineProperty(navigator, 'platform', { get: () => 'Win32', configurable: true, }); } catch (e) {} // ------------------------------------------------------------------------- // 13. navigator.vendor — Google Inc. // ------------------------------------------------------------------------- try { Object.defineProperty(navigator, 'vendor', { get: () => 'Google Inc.', configurable: true, }); } catch (e) {} // ------------------------------------------------------------------------- // 14. navigator.appVersion — совпадает с User-Agent // ------------------------------------------------------------------------- try { Object.defineProperty(navigator, 'appVersion', { get: () => '5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', configurable: true, }); } catch (e) {} // ------------------------------------------------------------------------- // 15. navigator.userAgent — подстраховка // ------------------------------------------------------------------------- try { Object.defineProperty(navigator, 'userAgent', { get: () => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', configurable: true, }); } catch (e) {} // ------------------------------------------------------------------------- // 16. WebGL — подменяем vendor и renderer // ------------------------------------------------------------------------- try { const getParameterOrig = WebGLRenderingContext.prototype.getParameter; WebGLRenderingContext.prototype.getParameter = function(param) { // UNMASKED_VENDOR_WEBGL if (param === 0x9245) return 'Google Inc. (NVIDIA)'; // UNMASKED_RENDERER_WEBGL if (param === 0x9246) return 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11)'; return getParameterOrig.call(this, param); }; const getParameter2Orig = WebGL2RenderingContext.prototype.getParameter; WebGL2RenderingContext.prototype.getParameter = function(param) { if (param === 0x9245) return 'Google Inc. (NVIDIA)'; if (param === 0x9246) return 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11)'; return getParameter2Orig.call(this, param); }; } catch (e) {} // ------------------------------------------------------------------------- // 17. Скрываем CDP (Chrome DevTools Protocol) следы // ------------------------------------------------------------------------- try { // Удаляем __playwright_target__ если есть delete window.__playwright_target__; delete window.__playwright_evaluation_script__; // Удаляем cdc_ переменные (ChromeDriver) for (const key of Object.keys(window)) { if (key.match(/^cdc_/) || key.match(/^__cdc_/) || key.match(/^__selenium_/) || key.match(/^__webdriver_/)) { try { delete window[key]; } catch (e) {} } } // Удаляем document.$cdc_ переменные for (const key of Object.keys(document)) { if (key.match(/^\$cdc_/) || key.match(/^cdc_/)) { try { delete document[key]; } catch (e) {} } } } catch (e) {} // ------------------------------------------------------------------------- // 18. Скрываем Headless-маркеры // ------------------------------------------------------------------------- try { // window.outerWidth/outerHeight должны быть > 0 if (window.outerWidth === 0) { Object.defineProperty(window, 'outerWidth', { get: () => 1920 }); } if (window.outerHeight === 0) { Object.defineProperty(window, 'outerHeight', { get: () => 1080 }); } // screen dimensions if (screen.width === 0 || screen.width < 800) { Object.defineProperty(screen, 'width', { get: () => 1920 }); Object.defineProperty(screen, 'height', { get: () => 1080 }); Object.defineProperty(screen, 'availWidth', { get: () => 1920 }); Object.defineProperty(screen, 'availHeight', { get: () => 1040 }); Object.defineProperty(screen, 'colorDepth', { get: () => 24 }); Object.defineProperty(screen, 'pixelDepth', { get: () => 24 }); } } catch (e) {} // ------------------------------------------------------------------------- // 19. Скрываем iframe-детекцию // ------------------------------------------------------------------------- try { // Перехватываем создание iframe и патчим contentWindow const originalCreateElement = document.createElement.bind(document); document.createElement = function(tagName, options) { const el = originalCreateElement(tagName, options); if (tagName.toLowerCase() === 'iframe') { const originalContentWindow = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'contentWindow'); // Не переопределяем, просто убеждаемся что chrome объект есть в iframe } return el; }; // Сохраняем toString document.createElement.toString = () => 'function createElement() { [native code] }'; } catch (e) {} // ------------------------------------------------------------------------- // 20. Защита toString() — все подменённые функции должны выглядеть нативно // ------------------------------------------------------------------------- try { const nativeToString = Function.prototype.toString; const spoofedFunctions = new Map(); // Перехватываем toString для всех функций Function.prototype.toString = function() { if (spoofedFunctions.has(this)) { return spoofedFunctions.get(this); } return nativeToString.call(this); }; // Регистрируем подменённые функции spoofedFunctions.set(Function.prototype.toString, 'function toString() { [native code] }'); if (window.Permissions && window.Permissions.prototype.query) { spoofedFunctions.set(window.Permissions.prototype.query, 'function query() { [native code] }'); } } catch (e) {} // ------------------------------------------------------------------------- // 21. Notification.permission — подстраховка // ------------------------------------------------------------------------- try { if (Notification.permission === 'denied') { Object.defineProperty(Notification, 'permission', { get: () => 'default', configurable: true, }); } } catch (e) {} // ------------------------------------------------------------------------- // 22. Скрываем автоматизацию в User-Agent Client Hints // ------------------------------------------------------------------------- try { if (navigator.userAgentData) { Object.defineProperty(navigator, 'userAgentData', { get: () => ({ brands: [ { brand: 'Google Chrome', version: '131' }, { brand: 'Chromium', version: '131' }, { brand: 'Not_A Brand', version: '24' }, ], mobile: false, platform: 'Windows', getHighEntropyValues: function(hints) { return Promise.resolve({ architecture: 'x86', bitness: '64', brands: [ { brand: 'Google Chrome', version: '131' }, { brand: 'Chromium', version: '131' }, { brand: 'Not_A Brand', version: '24' }, ], fullVersionList: [ { brand: 'Google Chrome', version: '131.0.6778.86' }, { brand: 'Chromium', version: '131.0.6778.86' }, { brand: 'Not_A Brand', version: '24.0.0.0' }, ], mobile: false, model: '', platform: 'Windows', platformVersion: '15.0.0', uaFullVersion: '131.0.6778.86', wow64: false, }); }, toJSON: function() { return { brands: this.brands, mobile: this.mobile, platform: this.platform, }; }, }), configurable: true, }); } } catch (e) {} // ------------------------------------------------------------------------- // 23. Скрываем Brave/automation-специфичные API // ------------------------------------------------------------------------- try { // Убеждаемся что нет window.domAutomation и window.domAutomationController delete window.domAutomation; delete window.domAutomationController; delete window._phantom; delete window.__nightmare; delete window._selenium; delete window.callPhantom; delete window.callSelenium; delete window._Recaptcha; } catch (e) {} // ------------------------------------------------------------------------- // 24. Эмуляция Battery API // ------------------------------------------------------------------------- try { if (navigator.getBattery) { navigator.getBattery = function() { return Promise.resolve({ charging: true, chargingTime: 0, dischargingTime: Infinity, level: 1.0, onchargingchange: null, onchargingtimechange: null, ondischargingtimechange: null, onlevelchange: null, addEventListener: function() {}, removeEventListener: function() {}, dispatchEvent: function() { return true; }, }); }; } } catch (e) {} // ------------------------------------------------------------------------- // 25. Скрываем Automation flags в navigator // ------------------------------------------------------------------------- try { // navigator.brave не должен существовать (мы не Brave) if (navigator.brave) { delete navigator.brave; } } catch (e) {} // ------------------------------------------------------------------------- // 26. AudioContext — автоматический resume для обхода autoplay policy // ------------------------------------------------------------------------- try { const OriginalAudioContext = window.AudioContext || window.webkitAudioContext; if (OriginalAudioContext) { const originalResume = OriginalAudioContext.prototype.resume; const originalConstructor = OriginalAudioContext; // Патчим конструктор чтобы автоматически resume window.AudioContext = function(...args) { const ctx = new originalConstructor(...args); // Автоматически resume если suspended if (ctx.state === 'suspended') { try { ctx.resume(); } catch(e) {} } return ctx; }; window.AudioContext.prototype = originalConstructor.prototype; if (window.webkitAudioContext) { window.webkitAudioContext = window.AudioContext; } } } catch (e) {} // ------------------------------------------------------------------------- // 27. Скрываем факт использования расширений от content scripts detection // ------------------------------------------------------------------------- try { // Некоторые антифроды проверяют chrome.runtime.id — если он есть, значит расширение // Мы уже определили chrome.runtime выше, но убедимся что id = undefined if (window.chrome && window.chrome.runtime) { Object.defineProperty(window.chrome.runtime, 'id', { get: () => undefined, configurable: true, }); } } catch (e) {} // ------------------------------------------------------------------------- // 28. Скрываем sourceURL в стеке вызовов (anti-extension detection) // ------------------------------------------------------------------------- try { const originalError = Error; const originalPrepareStackTrace = Error.prepareStackTrace; Error.prepareStackTrace = function(error, stack) { // Фильтруем фреймы, содержащие chrome-extension:// const filteredStack = stack.filter(frame => { const fileName = frame.getFileName(); return !fileName || !fileName.startsWith('chrome-extension://'); }); if (originalPrepareStackTrace) { return originalPrepareStackTrace(error, filteredStack); } return filteredStack.map(frame => ` at ${frame}`).join('\n'); }; } catch (e) {} // ------------------------------------------------------------------------- // Готово — все патчи применены // ------------------------------------------------------------------------- })();