From 38b1e176281e884beaf6118c9b5c2f5fdefc2e11 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Thu, 15 May 2025 15:13:25 +0200 Subject: [PATCH] feat : better browser spoofing --- sources/browser.py | 36 +++-- sources/web_scripts/spoofing.js | 231 +++++++++----------------------- 2 files changed, 83 insertions(+), 184 deletions(-) diff --git a/sources/browser.py b/sources/browser.py index 30415b0..1f3bd9d 100644 --- a/sources/browser.py +++ b/sources/browser.py @@ -127,21 +127,22 @@ def create_driver(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx user_data_dir = tempfile.mkdtemp() user_agent = get_random_user_agent() chrome_options.add_argument(f"--user-data-dir={user_data_dir}") + chrome_options.add_argument("accept=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8") + chrome_options.add_argument("--accept-lang=fr-FR,fr;q=0.9") + chrome_options.add_argument("--timezone=Europe/Paris") chrome_options.add_argument("--use-gl=egl") - chrome_options.add_argument("--enable-webgl") + chrome_options.add_argument("--enable-webgl") # no webgl is red flag for some anti-bot chrome_options.add_argument("--enable-3d-apis") + chrome_options.add_argument("--use-gpu-driver-bug-workaround") + chrome_options.add_argument("--allow-webgl-developer-extensions") chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--disable-dev-shm-usage") chrome_options.add_argument("--mute-audio") chrome_options.add_argument("--disable-notifications") chrome_options.add_argument("--autoplay-policy=user-gesture-required") + chrome_options.add_argument("--disable-features=SitePerProcess,IsolateOrigins") + chrome_options.add_argument("--enable-features=NetworkService,NetworkServiceInProcess") # Essential WebGL arguments - chrome_options.add_argument("--ignore-gpu-blocklist") - chrome_options.add_argument("--enable-webgl") - chrome_options.add_argument("--enable-webgl-developer-extensions") - chrome_options.add_argument("--enable-webgl-draft-extensions") - chrome_options.add_argument("--disable-webgl-anti-fingerprinting") - chrome_options.add_argument("--allow-webgl-developer-extensions") chrome_options.add_argument("--font-render-hinting=none") chrome_options.add_argument("--disable-blink-features=AutomationControlled") chrome_options.add_argument(f'user-agent={user_agent["ua"]}') @@ -237,16 +238,13 @@ class Browser: actions.click().perform() def human_scroll(self): - scroll_pixels = random.randint(200, 800) - scroll_time = random.uniform(0.5, 2.0) - self.driver.execute_script(f""" - window.scrollBy({{ - top: {scroll_pixels}, - behavior: 'smooth', - duration: {scroll_time} - }}); - """) - time.sleep(scroll_time + random.uniform(0.2, 0.5)) + for _ in range(random.randint(1, 3)): + scroll_pixels = random.randint(150, 1200) + self.driver.execute_script(f"window.scrollBy(0, {scroll_pixels});") + time.sleep(random.uniform(0.5, 2.0)) + if random.random() < 0.4: + self.driver.execute_script(f"window.scrollBy(0, -{random.randint(50, 300)});") + time.sleep(random.uniform(0.3, 1.0)) def patch_browser_fingerprint(self) -> None: script = self.load_js("spoofing.js") @@ -258,6 +256,7 @@ class Browser: try: initial_handles = self.driver.window_handles self.driver.get(url) + time.sleep(random.uniform(0.01, 0.3)) try: wait = WebDriverWait(self.driver, timeout=10) wait.until( @@ -269,6 +268,7 @@ class Browser: except TimeoutException: self.logger.warning("Timeout while waiting for page to bypass 'checking your browser'") #self.apply_web_safety() + time.sleep(random.uniform(0.01, 0.2)) self.human_scroll() self.logger.log(f"Navigated to: {url}") return True @@ -702,8 +702,6 @@ if __name__ == "__main__": input("press enter to continue") print("AntiCaptcha / Form Test") - browser.go_to("https://bot.sannysoft.com/") - time.sleep(50) browser.go_to("https://antoinevastel.com/bots/") time.sleep(5) #txt = browser.get_text() diff --git a/sources/web_scripts/spoofing.js b/sources/web_scripts/spoofing.js index 96faf2b..f7c6a67 100644 --- a/sources/web_scripts/spoofing.js +++ b/sources/web_scripts/spoofing.js @@ -1,55 +1,35 @@ // Core automation masking -Object.defineProperty(navigator, 'webdriver', {get: () => undefined}); delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array; delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise; delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol; +window.RTCPeerConnection = undefined; +window.webkitRTCPeerConnection = undefined; +window.mozRTCPeerConnection = undefined; + +window.Notification = class Notification { + constructor(title, options = {}) { + this.title = title; + this.options = options; + } + static permission = 'granted'; + static requestPermission = () => Promise.resolve('granted'); + close() {} + onclick = null; + onerror = null; + onclose = null; + onshow = null; +}; + Object.keys(window).forEach((key) => { if (key.includes("webdriver") || key.includes("selenium") || key.includes("driver")) { delete window[key]; } }); -// Forceful chrome object spoofing -try { - Object.defineProperty(window, 'chrome', { - writable: true, - configurable: true, - value: { - app: {isInstalled: false}, - webstore: {onInstallStageChanged: {}, onDownloadProgress: {}}, - runtime: { - PlatformOs: {MAC: 'mac', WIN: 'win', ANDROID: 'android'}, - PlatformArch: {ARM: 'arm', X86_32: 'x86-32', X86_64: 'x86-64'}, - PlatformNaclArch: {ARM: 'arm', X86_32: 'x86-32', X86_64: 'x86-64'}, - RequestUpdateCheckStatus: { - THROTTLED: 'throttled', - NO_UPDATE: 'no_update', - UPDATE_AVAILABLE: 'update_available' - }, - OnInstalledReason: { - INSTALL: 'install', - UPDATE: 'update', - SHARED_MODULE_UPDATE: 'shared_module_update' - }, - OnRestartRequiredReason: { - APP_UPDATE: 'app_update', - OS_UPDATE: 'os_update', - PERIODIC: 'periodic' - } - } - } - }); -} catch (e) { - console.log("Error in defining window.chrome: ", e); - // Fallback: direct assignment - window.chrome = window.chrome || {}; - window.chrome.app = {isInstalled: false}; - window.chrome.webstore = {onInstallStageChanged: {}, onDownloadProgress: {}}; -} - // Randomize plugins + const pluginsList = [ {type: 'application/x-google-chrome-pdf', description: 'Portable Document Format', filename: 'internal-pdf-viewer', name: 'Chrome PDF Plugin'}, {type: 'application/x-nacl', description: 'Native Client Executable', filename: 'internal-nacl-plugin', name: 'Native Client'}, @@ -59,7 +39,8 @@ Object.defineProperty(navigator, 'plugins', { get: () => pluginsList.slice(0, Math.floor(Math.random() * pluginsList.length) + 1) }); -// Font spoofing with randomized font list +// Font spoofing + const fontList = ['Arial', 'Helvetica', 'Times New Roman', 'Courier New', 'Verdana']; Object.defineProperty(document, 'fonts', { value: { @@ -74,64 +55,53 @@ Object.defineProperty(document, 'fonts', { }); // Canvas fingerprint spoofing -const originalToDataURL = HTMLCanvasElement.prototype.toDataURL; + HTMLCanvasElement.prototype.toDataURL = function() { const ctx = this.getContext('2d'); - ctx.fillStyle = `rgba(${Math.random() * 10},${Math.random() * 10},${Math.random() * 10},0.01)`; - ctx.fillRect(0, 0, 1, 1); + // Add varied noise to avoid consistent fingerprints + for (let i = 0; i < 10; i++) { + ctx.fillStyle = `rgba(${Math.random() * 5}, ${Math.random() * 5}, ${Math.random() * 5}, 0.005)`; + ctx.fillRect(Math.random() * this.width, Math.random() * this.height, 1, 1); + } return originalToDataURL.apply(this, arguments); }; -// Screen resolution spoofing -const resolutions = [[1920, 1080], [1366, 768], [1440, 900]]; -const [w, h] = resolutions[Math.floor(Math.random() * resolutions.length)]; +const [w, h] = [1920, 1080]; Object.defineProperty(window, 'screen', { - value: { - width: w, - height: h, - availWidth: w, - availHeight: h - 50, - colorDepth: 24, - pixelDepth: 24 - } + value: { + width: w, + height: h, + availWidth: w - 20, + availHeight: h - 100, + colorDepth: 24, + pixelDepth: 24 + } }); -// Timezone and language spoofing -Object.defineProperty(navigator, 'language', {get: () => 'en-US'}); -Intl.DateTimeFormat = function() { - return {resolvedOptions: () => ({timeZone: 'America/New_York'})}; + +// ===== WebGL Consistency ===== +const os = navigator.userAgent.includes('Windows') ? 'Windows' : 'Mac'; +const webGLParams = { + 'Windows': { + 37445: 'Google Inc. (NVIDIA)', // VENDOR + 37446: 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3060)', // RENDERER + 36349: 'NVIDIA Corporation', // UNMASKED_VENDOR_WEBGL + 37444: 'NVIDIA GeForce RTX 3060', // UNMASKED_RENDERER_WEBGL + 35661: 'WebGL 2.0' // VERSION + }, + 'Mac': { + 37445: 'Apple Inc.', + 37446: 'Apple M1 Pro', + 36349: 'Apple', + 37444: 'Apple M1 Pro', + 35661: 'WebGL 2.0 (Metal)' + } }; -// AudioContext spoofing -const originalCreate = window.AudioContext || window.webkitAudioContext; -window.AudioContext = window.webkitAudioContext = function() { - const context = new originalCreate(); - const dest = context.createAnalyser(); - dest.fake = true; - return context; -}; - -// WebRTC spoofing -Object.defineProperty(navigator, 'mediaDevices', { - get: () => undefined -}); -Object.defineProperty(navigator, 'getUserMedia', { - get: () => undefined -}); - -// web gl spoofing -const getParameter = WebGLRenderingContext.prototype.getParameter; +// replace WebGL parameters WebGLRenderingContext.prototype.getParameter = function(parameter) { - // Common WebGL parameters - const params = { - 37445: 'Intel Open Source Technology Center', // VENDOR - 37446: 'Mesa DRI IntelĀ® HD Graphics 4000', // RENDERER - 34076: 'WebKit WebGL', // SHADING_LANGUAGE_VERSION - 35661: '2.1 INTEL-16.4.5', // VERSION - 36349: 'Intel Inc.' // UNMASKED_VENDOR_WEBGL - }; - return params[parameter] || getParameter.call(this, parameter); -}; + return webGLParams[os][parameter] || getParameter.call(this, parameter); + }; // Performance API spoofing if ('performance' in window) { @@ -145,81 +115,12 @@ if ('performance' in window) { }); } - // Battery API spoofing -if ('getBattery' in navigator) { - Object.defineProperty(navigator, 'getBattery', { - value: () => Promise.resolve({ - level: 0.85, - charging: true, - chargingTime: 1800, - dischargingTime: Infinity, - onchargingchange: null, - onchargingtimechange: null, - ondischargingtimechange: null, - onlevelchange: null - }), - configurable: true - }); - } - -window.RTCPeerConnection = undefined; -window.webkitRTCPeerConnection = undefined; -window.mozRTCPeerConnection = undefined; - -Object.defineProperty(navigator, 'permissions', { - value: { - query: () => Promise.resolve({ state: 'denied' }) - } -}); -Object.defineProperty(navigator, 'languages', { - get: () => ['en-US', 'en'], -}); - -const canvas = document.createElement('canvas'); -const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); - -if (gl) { - // Store original getParameter function - const originalGetParameter = gl.getParameter; - // Override getParameter to spoof values - gl.getParameter = function(parameter) { - // UNMASKED_VENDOR_WEBGL - if (parameter === 37445) return 'Intel Inc.'; - // UNMASKED_RENDERER_WEBGL - if (parameter === 37446) return 'Intel Iris Pro OpenGL Engine'; - // Other common fingerprint points - if (parameter === gl.VERSION) return 'WebGL 2.0 (OpenGL ES 3.0 Intel)'; - if (parameter === gl.SHADING_LANGUAGE_VERSION) return 'WebGL GLSL ES 3.00 (OpenGL ES GLSL ES 3.0)'; - - // Default to original behavior - return originalGetParameter.call(gl, parameter); - }; - - // Spoof debug renderer info extension - const debugInfo = { - getExtension: function(name) { - if (name === 'WEBGL_debug_renderer_info') { - return { - UNMASKED_VENDOR_WEBGL: 37445, - UNMASKED_RENDERER_WEBGL: 37446, - __proto__: WebGLExtension.prototype - }; - } - return null; - }, - __proto__: WebGLRenderingContext.prototype - }; - - // Override getExtension - const originalGetExtension = gl.getExtension; - gl.getExtension = function(name) { - if (name === 'WEBGL_debug_renderer_info') { - return debugInfo; - } - return originalGetExtension.call(gl, name); - }; - - // Freeze objects to prevent detection - Object.freeze(gl); - Object.freeze(debugInfo); -} \ No newline at end of file +const originalCreate = window.AudioContext || window.webkitAudioContext; +window.AudioContext = window.webkitAudioContext = function() { + const context = new originalCreate(); + const analyser = context.createAnalyser(); + analyser.fake = true; // Mark as spoofed + // Spoof common methods + analyser.getFloatFrequencyData = () => new Float32Array(1024).fill(Math.random() * -100); + return context; +}; \ No newline at end of file