# browser/fingerprint_protection.py """ Schutz vor Browser-Fingerprinting - Erweiterte Methoden zum Schutz vor verschiedenen Fingerprinting-Techniken """ import random import logging import json import hashlib from datetime import datetime from typing import Dict, List, Any, Optional, Tuple logger = logging.getLogger("fingerprint_protection") class FingerprintProtection: """ Bietet erweiterte Schutzmaßnahmen gegen verschiedene Browser-Fingerprinting-Techniken. Kann mit dem PlaywrightManager integriert werden, um die Anonymität zu verbessern. """ def __init__(self, context=None, stealth_config=None, fingerprint_config=None): """ Initialisiert den Fingerprint-Schutz. Args: context: Der Browser-Kontext, auf den die Schutzmaßnahmen angewendet werden sollen stealth_config: Konfigurationseinstellungen für das Stealth-Verhalten fingerprint_config: Optional - BrowserFingerprint object for account-bound fingerprints """ self.context = context self.stealth_config = stealth_config or {} self.fingerprint_config = fingerprint_config self.scripts = [] self.noise_level = self.stealth_config.get("noise_level", 0.5) # 0.0-1.0 # Use fingerprint config if provided, otherwise use defaults if self.fingerprint_config: self._init_from_fingerprint_config() else: self._init_from_defaults() # Schutzmaßnahmen initialisieren self._init_protections() def _init_from_fingerprint_config(self): """Initialize values from BrowserFingerprint object""" fp = self.fingerprint_config # Use static components if available if fp.static_components: sc = fp.static_components self.defaults = { "webgl_vendor": sc.gpu_vendor, "webgl_renderer": sc.gpu_model, "canvas_noise": False, # Disabled for video compatibility "audio_noise": False, # Disabled for video compatibility "webgl_noise": False, # Disabled for video compatibility "hardware_concurrency": fp.hardware_config.hardware_concurrency, "device_memory": fp.hardware_config.device_memory, "timezone_id": fp.timezone, "platform": fp.navigator_props.platform, "user_agent": fp.navigator_props.user_agent, "languages": fp.navigator_props.languages, "screen_resolution": fp.hardware_config.screen_resolution, "fonts": fp.font_list, "rotation_seed": fp.rotation_seed } else: # Fallback to direct fingerprint values self.defaults = { "webgl_vendor": fp.webgl_vendor, "webgl_renderer": fp.webgl_renderer, "canvas_noise": False, # Disabled for video compatibility "audio_noise": False, # Disabled for video compatibility "webgl_noise": False, # Disabled for video compatibility "hardware_concurrency": fp.hardware_config.hardware_concurrency, "device_memory": fp.hardware_config.device_memory, "timezone_id": fp.timezone, "platform": fp.navigator_props.platform, "user_agent": fp.navigator_props.user_agent, "languages": fp.navigator_props.languages, "screen_resolution": fp.hardware_config.screen_resolution, "fonts": fp.font_list, "rotation_seed": fp.rotation_seed } # Override with stealth config if provided for key, value in self.stealth_config.items(): if key in self.defaults: self.defaults[key] = value def _init_from_defaults(self): """Initialize with default values""" # Standardwerte für Fingerprinting-Schutz self.defaults = { "webgl_vendor": "Google Inc. (Intel)", "webgl_renderer": "Intel Iris OpenGL Engine", "canvas_noise": False, # Disabled for video compatibility "audio_noise": False, # Disabled for video compatibility "webgl_noise": False, # Disabled for video compatibility "hardware_concurrency": 8, "device_memory": 8, "timezone_id": "Europe/Berlin", "rotation_seed": None } # Einstellungen mit benutzerdefinierten Werten überschreiben for key, value in self.stealth_config.items(): if key in self.defaults: self.defaults[key] = value def set_context(self, context): """Setzt den Browser-Kontext nach der Initialisierung.""" self.context = context def _init_protections(self): """Initialisiert alle Fingerprint-Schutzmaßnahmen.""" self._init_canvas_protection() self._init_webgl_protection() self._init_audio_protection() self._init_navigator_protection() self._init_misc_protections() self._init_font_protection() def _init_canvas_protection(self): """ Initialisiert den Schutz gegen Canvas-Fingerprinting. Dies modifiziert das Canvas-Element, um leicht abweichende Werte zurückzugeben. """ # Get deterministic seed for canvas noise canvas_seed = self._get_deterministic_noise_seed('canvas') script = f""" () => {{ // Originalmethoden speichern const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData; const originalToDataURL = HTMLCanvasElement.prototype.toDataURL; const originalToBlob = HTMLCanvasElement.prototype.toBlob; // Deterministic random generator let seed = {canvas_seed}; const deterministicRandom = () => {{ seed = (seed * 9301 + 49297) % 233280; return seed / 233280; }}; // Funktion zum Hinzufügen von Rauschen zu Bilddaten const addNoise = (data, noise) => {{ const noiseFactor = noise || 0.03; // Standardwert für das Rauschlevel // Nur auf sehr große Canvas anwenden, um Video-Canvas nicht zu stören if (data.width > 200 && data.height > 200) {{ const pixelCount = data.width * data.height * 4; // RGBA-Kanäle // Präzise, aber wenig wahrnehmbare Änderungen const minPixels = Math.max(10, Math.floor(pixelCount * 0.005)); // Mindestens 10 Pixel const maxPixels = Math.min(100, Math.floor(pixelCount * 0.01)); // Höchstens 100 Pixel // Deterministische Anzahl an Pixeln auswählen const pixelsToModify = Math.floor(deterministicRandom() * (maxPixels - minPixels)) + minPixels; // Pixel modifizieren for (let i = 0; i < pixelsToModify; i++) {{ // Deterministischen Pixel auswählen const offset = Math.floor(deterministicRandom() * pixelCount / 4) * 4; // Nur RGB modifizieren, Alpha-Kanal unverändert lassen for (let j = 0; j < 3; j++) {{ // Subtile Änderungen hinzufügen (±1-2) const mod = Math.floor(deterministicRandom() * 3) - 1; data.data[offset + j] = Math.max(0, Math.min(255, data.data[offset + j] + mod)); }} }} }} return data; }}; // getImageData überschreiben CanvasRenderingContext2D.prototype.getImageData = function() {{ const imageData = originalGetImageData.apply(this, arguments); return addNoise(imageData, {self.noise_level * 0.1}); }}; // toDataURL überschreiben HTMLCanvasElement.prototype.toDataURL = function() {{ // Temporäre Modifikation für getImageData const tempGetImageData = CanvasRenderingContext2D.prototype.getImageData; CanvasRenderingContext2D.prototype.getImageData = originalGetImageData; // toDataURL aufrufen let dataURL = originalToDataURL.apply(this, arguments); // Nur bei sehr großen Canvas und nicht bei Video-Canvas if (this.width > 200 && this.height > 200 && !this.hasAttribute('data-fingerprint-protect-ignore') && !this.hasAttribute('data-video-canvas')) {{ // Rauschen in binären Teil des DataURL einfügen const parts = dataURL.split(','); if (parts.length === 2) {{ // Binäre Daten dekodieren const binary = atob(parts[1]); // Geringfügige Änderungen bei etwa 0,1% der Bytes let modifiedBinary = ''; for (let i = 0; i < binary.length; i++) {{ if (Math.random() < 0.001 * {self.noise_level}) {{ // Byte leicht ändern const byte = binary.charCodeAt(i); const mod = Math.floor(Math.random() * 3) - 1; modifiedBinary += String.fromCharCode(Math.max(0, Math.min(255, byte + mod))); }} else {{ modifiedBinary += binary[i]; }} }} // Binäre Daten wieder kodieren dataURL = parts[0] + ',' + btoa(modifiedBinary); }} }} // getImageData wiederherstellen CanvasRenderingContext2D.prototype.getImageData = tempGetImageData; return dataURL; }}; // toBlob überschreiben HTMLCanvasElement.prototype.toBlob = function(callback) {{ // Original toBlob aufrufen originalToBlob.apply(this, [function(blob) {{ // Nur bei sehr großen Canvas und nicht bei Video-Canvas if (this.width > 200 && this.height > 200 && !this.hasAttribute('data-fingerprint-protect-ignore') && !this.hasAttribute('data-video-canvas')) {{ // Blob zu ArrayBuffer konvertieren const reader = new FileReader(); reader.onload = function() {{ const arrayBuffer = reader.result; const array = new Uint8Array(arrayBuffer); // Geringfügige Änderungen bei etwa 0,1% der Bytes for (let i = 0; i < array.length; i++) {{ if (Math.random() < 0.001 * {self.noise_level}) {{ // Byte leicht ändern const mod = Math.floor(Math.random() * 3) - 1; array[i] = Math.max(0, Math.min(255, array[i] + mod)); }} }} // Neuen Blob erstellen const modifiedBlob = new Blob([array], {{type: blob.type}}); callback(modifiedBlob); }}; reader.readAsArrayBuffer(blob); }} else {{ // Unveränderten Blob zurückgeben callback(blob); }} }}.bind(this)]); }}; }} """ self.scripts.append(script) def _init_webgl_protection(self): """ Initialisiert den Schutz gegen WebGL-Fingerprinting. Dies modifiziert WebGL-spezifische Werte, die für Fingerprinting verwendet werden. """ webgl_vendor = self.defaults["webgl_vendor"] webgl_renderer = self.defaults["webgl_renderer"] script = f""" () => {{ // WebGL Vendor und Renderer spoofen const getParameterProxies = [ WebGLRenderingContext.prototype, WebGL2RenderingContext.prototype ]; getParameterProxies.forEach(contextPrototype => {{ if (!contextPrototype) return; const originalGetParameter = contextPrototype.getParameter; contextPrototype.getParameter = function(parameter) {{ // WebGL Vendor (VENDOR) if (parameter === 0x1F00) {{ return "{webgl_vendor}"; }} // WebGL Renderer (RENDERER) if (parameter === 0x1F01) {{ return "{webgl_renderer}"; }} // Unshaded Language Version (SHADING_LANGUAGE_VERSION) if (parameter === 0x8B8C) {{ const originalValue = originalGetParameter.call(this, parameter); // Subtile Änderungen an der Version const versionMatch = originalValue.match(/^WebGL GLSL ES ([0-9]\\.[0-9][0-9])/); if (versionMatch) {{ return originalValue.replace(versionMatch[1], (parseFloat(versionMatch[1]) + (Math.random() * 0.01 - 0.005)).toFixed(2)); }} }} // VERSION if (parameter === 0x1F02) {{ const originalValue = originalGetParameter.call(this, parameter); // Subtile Änderungen an der Version const versionMatch = originalValue.match(/^WebGL ([0-9]\\.[0-9])/); if (versionMatch) {{ return originalValue.replace(versionMatch[1], (parseFloat(versionMatch[1]) + (Math.random() * 0.01 - 0.005)).toFixed(1)); }} }} return originalGetParameter.apply(this, arguments); }}; }}); // WebGL Vertex und Fragment Shader spoofen const shaderSourceProxies = [ WebGLRenderingContext.prototype, WebGL2RenderingContext.prototype ]; shaderSourceProxies.forEach(contextPrototype => {{ if (!contextPrototype) return; const originalShaderSource = contextPrototype.shaderSource; contextPrototype.shaderSource = function(shader, source) {{ // Füge geringfügige Unterschiede in Kommentaren ein, ohne die Funktionalität zu beeinträchtigen if (source.indexOf('//') !== -1) {{ // Zufälligen Kommentar leicht modifizieren source = source.replace(/\\/\\/(.*?)\\n/g, (match, comment) => {{ if (Math.random() < 0.1) {{ // Füge ein Leerzeichen oder einen Bindestrich hinzu oder entferne eines const modifications = [ ' ', '-', '', ' ' ]; const mod = modifications[Math.floor(Math.random() * modifications.length)]; return `//` + comment + mod + `\\n`; }} return match; }}); }} // Ersetze bestimmte Whitespace-Muster source = source.replace(/\\s{2,}/g, match => {{ if (Math.random() < 0.05) {{ return ' '.repeat(match.length + (Math.random() < 0.5 ? 1 : -1)); }} return match; }}); return originalShaderSource.call(this, shader, source); }}; }}); // Canvas-Kontext spoofen const getContextProxies = [ HTMLCanvasElement.prototype ]; getContextProxies.forEach(canvasPrototype => {{ const originalGetContext = canvasPrototype.getContext; canvasPrototype.getContext = function(contextType, contextAttributes) {{ const context = originalGetContext.apply(this, arguments); if (context && (contextType === 'webgl' || contextType === 'experimental-webgl' || contextType === 'webgl2')) {{ // WebGL-Noise nur wenn explizit aktiviert (für Video-Kompatibilität deaktiviert) if ({str(self.defaults["webgl_noise"]).lower()}) {{ // Zufällige Modifikation für MAX_VERTEX_UNIFORM_VECTORS const MAX_VERTEX_UNIFORM_VECTORS = context.getParameter(context.MAX_VERTEX_UNIFORM_VECTORS); Object.defineProperty(context, 'MAX_VERTEX_UNIFORM_VECTORS', {{ get: () => MAX_VERTEX_UNIFORM_VECTORS + Math.floor(Math.random() * 3) - 1 }}); // Zufällige Modifikation für MAX_FRAGMENT_UNIFORM_VECTORS const MAX_FRAGMENT_UNIFORM_VECTORS = context.getParameter(context.MAX_FRAGMENT_UNIFORM_VECTORS); Object.defineProperty(context, 'MAX_FRAGMENT_UNIFORM_VECTORS', {{ get: () => MAX_FRAGMENT_UNIFORM_VECTORS + Math.floor(Math.random() * 3) - 1 }}); }} }} return context; }}; }}); }} """ self.scripts.append(script) def _init_audio_protection(self): """ Initialisiert den Schutz gegen Audio-Fingerprinting. Dies modifiziert die Audio-API-Funktionen, die für Fingerprinting verwendet werden. """ script = f""" () => {{ // Audio-Kontext spoofen if (window.AudioContext || window.webkitAudioContext) {{ const AudioContextProxy = window.AudioContext || window.webkitAudioContext; const originalAudioContext = AudioContextProxy; // AudioContext überschreiben window.AudioContext = window.webkitAudioContext = function() {{ const context = new originalAudioContext(); // createOscillator überschreiben const originalCreateOscillator = context.createOscillator; context.createOscillator = function() {{ const oscillator = originalCreateOscillator.apply(this, arguments); // Frequenz leicht modifizieren const originalFrequency = oscillator.frequency; Object.defineProperty(oscillator, 'frequency', {{ get: function() {{ return originalFrequency; }}, set: function(value) {{ if (typeof value === 'number') {{ // Leichte Änderung hinzufügen const noise = (Math.random() * 0.02 - 0.01) * value; originalFrequency.value = value + noise; }} else {{ originalFrequency.value = value; }} }} }}); return oscillator; }}; // getChannelData überschreiben const originalGetChannelData = context.createBuffer.prototype?.getChannelData || OfflineAudioContext.prototype?.getChannelData; if (originalGetChannelData) {{ context.createBuffer.prototype.getChannelData = function(channel) {{ const array = originalGetChannelData.call(this, channel); if ({str(self.defaults["audio_noise"]).lower()} && this.length > 20) {{ // Erweiterte Audio Noise Patterns const noise = {self.noise_level} * 0.0001; // Verschiedene Noise-Patterns const patterns = ['gaussian', 'pink', 'brown', 'white']; const pattern = patterns[Math.floor(Math.random() * patterns.length)]; switch(pattern) {{ case 'gaussian': // Gaussian Noise const samples = Math.min(200, Math.floor(array.length * 0.001)); for (let i = 0; i < samples; i++) {{ const idx = Math.floor(Math.random() * array.length); // Box-Muller Transform für Gaussian Distribution const u1 = Math.random(); const u2 = Math.random(); const gaussian = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2); array[idx] += gaussian * noise; }} break; case 'pink': // Pink Noise (1/f) let b0 = 0, b1 = 0, b2 = 0; for (let i = 0; i < array.length; i += 100) {{ const white = Math.random() * 2 - 1; b0 = 0.99765 * b0 + white * 0.0990460; b1 = 0.96300 * b1 + white * 0.2965164; b2 = 0.57000 * b2 + white * 1.0526913; const pink = (b0 + b1 + b2 + white * 0.1848) * noise; if (i < array.length) {{ array[i] += pink * 0.1; }} }} break; case 'brown': // Brown Noise (1/f²) let lastOut = 0; for (let i = 0; i < array.length; i += 200) {{ const white = Math.random() * 2 - 1; const brown = (lastOut + (0.02 * white)) / 1.02; lastOut = brown; if (i < array.length) {{ array[i] += brown * noise * 3; }} }} break; case 'white': default: // White Noise const whiteSamples = Math.min(150, Math.floor(array.length * 0.0008)); for (let i = 0; i < whiteSamples; i++) {{ const idx = Math.floor(Math.random() * array.length); array[idx] += (Math.random() * 2 - 1) * noise; }} }} }} return array; }}; }} // AnalyserNode.getFloatFrequencyData überschreiben if (context.createAnalyser) {{ const originalCreateAnalyser = context.createAnalyser; context.createAnalyser = function() {{ const analyser = originalCreateAnalyser.apply(this, arguments); const originalGetFloatFrequencyData = analyser.getFloatFrequencyData; analyser.getFloatFrequencyData = function(array) {{ originalGetFloatFrequencyData.call(this, array); if ({str(self.defaults["audio_noise"]).lower()} && array.length > 20) {{ // Erweiterte Frequency Domain Noise const noise = {self.noise_level} * 0.001; // Frequency-aware noise injection // Weniger Noise bei niedrigen Frequenzen, mehr bei hohen for (let i = 0; i < array.length; i++) {{ const frequencyFactor = i / array.length; // 0 bis 1 const adaptiveNoise = noise * (0.5 + frequencyFactor * 0.5); // Nur bei etwa 1% der Frequenzen Noise hinzufügen if (Math.random() < 0.01) {{ array[i] += (Math.random() * 2 - 1) * adaptiveNoise; }} }} }} return array; }}; return analyser; }}; }} // Weitere Audio-Properties für besseren Schutz // Sample Rate Spoofing Object.defineProperty(context, 'sampleRate', {{ get: function() {{ // Gängige Sample Rates: 44100 oder 48000 const rates = [44100, 48000]; return rates[Math.floor(Math.random() * rates.length)]; }} }}); // Destination Properties if (context.destination) {{ Object.defineProperty(context.destination, 'maxChannelCount', {{ get: function() {{ // Typische Werte: 2 (Stereo), 6 (5.1), 8 (7.1) const counts = [2, 2, 2, 6, 8]; // Mehr Gewicht auf Stereo return counts[Math.floor(Math.random() * counts.length)]; }} }}); }} // createDynamicsCompressor mit Variation if (context.createDynamicsCompressor) {{ const originalCreateDynamicsCompressor = context.createDynamicsCompressor; context.createDynamicsCompressor = function() {{ const compressor = originalCreateDynamicsCompressor.apply(this, arguments); // Leichte Variationen in den Default-Werten const properties = ['threshold', 'knee', 'ratio', 'attack', 'release']; properties.forEach(prop => {{ if (compressor[prop] && compressor[prop].value !== undefined) {{ const original = compressor[prop].value; const variation = 1 + (Math.random() - 0.5) * 0.02; // ±1% Variation compressor[prop].value = original * variation; }} }}); return compressor; }}; }} return context; }}; }} }} """ self.scripts.append(script) def _init_navigator_protection(self): """ Initialisiert den Schutz gegen Navigator-Objekt-Fingerprinting. Dies modifiziert verschiedene Navigator-Eigenschaften, die für Fingerprinting verwendet werden. """ hardware_concurrency = self.defaults["hardware_concurrency"] device_memory = self.defaults["device_memory"] script = f""" () => {{ // Navigator-Eigenschaften überschreiben // hardwareConcurrency (CPU-Kerne) if (navigator.hardwareConcurrency) {{ Object.defineProperty(navigator, 'hardwareConcurrency', {{ get: () => {hardware_concurrency} }}); }} // deviceMemory (RAM) if (navigator.deviceMemory) {{ Object.defineProperty(navigator, 'deviceMemory', {{ get: () => {device_memory} }}); }} // language und languages if (navigator.language) {{ const originalLanguage = navigator.language; const originalLanguages = navigator.languages; Object.defineProperty(navigator, 'language', {{ get: () => originalLanguage }}); Object.defineProperty(navigator, 'languages', {{ get: () => originalLanguages }}); }} // userAgent-Konsistenz sicherstellen if (navigator.userAgent) {{ const userAgent = navigator.userAgent; // Wenn der userAgent bereits überschrieben wurde, stellen wir // sicher, dass die appVersion und platform konsistent sind const browserInfo = {{ chrome: /Chrome\\/(\\d+)/.exec(userAgent), firefox: /Firefox\\/(\\d+)/.exec(userAgent), safari: /Safari\\/(\\d+)/.exec(userAgent), edge: /Edg(e|)\\/(\\d+)/.exec(userAgent) }}; // Platform basierend auf userAgent bestimmen let platform = '{self.defaults.get("platform", "Win32")}'; if (/Windows/.test(userAgent)) platform = 'Win32'; else if (/Macintosh/.test(userAgent)) platform = 'MacIntel'; else if (/Linux/.test(userAgent)) platform = 'Linux x86_64'; else if (/Android/.test(userAgent)) platform = 'Linux armv8l'; else if (/iPhone|iPad/.test(userAgent)) platform = 'iPhone'; Object.defineProperty(navigator, 'platform', {{ get: () => platform }}); // appVersion konsistent machen if (navigator.appVersion) {{ Object.defineProperty(navigator, 'appVersion', {{ get: () => userAgent.substring(8) }}); }} // vendor basierend auf Browser setzen let vendor = '{self.defaults.get("vendor", "Google Inc.")}'; if (browserInfo.safari) vendor = 'Apple Computer, Inc.'; else if (browserInfo.firefox) vendor = ''; Object.defineProperty(navigator, 'vendor', {{ get: () => vendor }}); }} """ self.scripts.append(script) def _init_misc_protections(self): """ Initialisiert verschiedene weitere Schutzmaßnahmen gegen Fingerprinting. """ timezone_id = self.defaults["timezone_id"] script = f""" () => {{ // Date.prototype.getTimezoneOffset überschreiben const originalGetTimezoneOffset = Date.prototype.getTimezoneOffset; Date.prototype.getTimezoneOffset = function() {{ // Zeitzonendaten für '{timezone_id}' // Mitteleuropäische Zeit (CET/CEST): UTC+1 / UTC+2 (Sommerzeit) const date = new Date(this); // Prüfen, ob Sommerzeit const jan = new Date(date.getFullYear(), 0, 1); const jul = new Date(date.getFullYear(), 6, 1); const standardOffset = Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset()); // Sommerzeit in Europa: Ende März bis Ende Oktober const isDST = date.getMonth() > 2 && date.getMonth() < 10; // Offset in Minuten: CET = -60, CEST = -120 return isDST ? -120 : -60; }}; // Plugins und MimeTypes Schutz if (navigator.plugins) {{ // Leere oder gefälschte Plugins Object.defineProperty(navigator, 'plugins', {{ get: () => {{ // Plugins-Array-Eigenschaften simulieren const plugins = {{ length: 0, item: () => null, namedItem: () => null, refresh: () => {{}}, [Symbol.iterator]: function* () {{}} }}; return plugins; }} }}); // MimeTypes ebenfalls leeren if (navigator.mimeTypes) {{ Object.defineProperty(navigator, 'mimeTypes', {{ get: () => {{ // MimeTypes-Array-Eigenschaften simulieren const mimeTypes = {{ length: 0, item: () => null, namedItem: () => null, [Symbol.iterator]: function* () {{}} }}; return mimeTypes; }} }}); }} }} // Performance.now() und Date.now() - Schutz gegen Timing-Angriffe if (window.performance && performance.now) {{ const originalNow = performance.now; performance.now = function() {{ const value = originalNow.call(performance); // Subtile Abweichung hinzufügen return value + (Math.random() * 0.01); }}; }} // Date.now() ebenfalls mit subtiler Abweichung const originalDateNow = Date.now; Date.now = function() {{ const value = originalDateNow.call(Date); // Subtile Abweichung hinzufügen (±1ms) return value + (Math.random() < 0.5 ? 1 : 0); }}; // screen-Eigenschaften konsistent machen if (window.screen) {{ const originalWidth = screen.width; const originalHeight = screen.height; const originalColorDepth = screen.colorDepth; const originalPixelDepth = screen.pixelDepth; // Abweichungen verhindern - konsistente Werte liefern Object.defineProperties(screen, {{ 'width': {{ get: () => originalWidth }}, 'height': {{ get: () => originalHeight }}, 'availWidth': {{ get: () => originalWidth }}, 'availHeight': {{ get: () => originalHeight - 40 }}, // Taskleiste simulieren 'colorDepth': {{ get: () => originalColorDepth }}, 'pixelDepth': {{ get: () => originalPixelDepth }} }}); }} // Battery API Spoofing if (navigator.getBattery) {{ // Override getBattery mit realistischen Werten const batteryLevel = Math.random() * 0.7 + 0.3; // 30-100% const charging = Math.random() > 0.3; // 70% Chance nicht am Laden navigator.getBattery = function() {{ return Promise.resolve({{ charging: charging, chargingTime: charging ? Math.floor(Math.random() * 3600) : Infinity, dischargingTime: !charging ? Math.floor(Math.random() * 10800) + 3600 : Infinity, // 1-4 Stunden level: batteryLevel, // Event Handlers onchargingchange: null, onchargingtimechange: null, ondischargingtimechange: null, onlevelchange: null, // Event Target Methoden addEventListener: function() {{}}, removeEventListener: function() {{}}, dispatchEvent: function() {{ return true; }} }}); }}; }} // Network Information API Spoofing if (navigator.connection || navigator.mozConnection || navigator.webkitConnection) {{ const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; const connectionTypes = ['wifi', 'cellular', 'ethernet']; const effectiveTypes = ['4g', '3g']; // Realistische Werte basierend auf Connection Type const connectionType = connectionTypes[Math.floor(Math.random() * connectionTypes.length)]; const effectiveType = effectiveTypes[Math.floor(Math.random() * effectiveTypes.length)]; Object.defineProperties(connection, {{ 'type': {{ get: () => connectionType, configurable: true }}, 'effectiveType': {{ get: () => effectiveType, configurable: true }}, 'downlink': {{ get: () => effectiveType === '4g' ? Math.random() * 10 + 5 : Math.random() * 5 + 1, configurable: true }}, 'downlinkMax': {{ get: () => connectionType === 'wifi' ? 100 : 50, configurable: true }}, 'rtt': {{ get: () => effectiveType === '4g' ? Math.floor(Math.random() * 50) + 20 : Math.floor(Math.random() * 100) + 50, configurable: true }}, 'saveData': {{ get: () => false, configurable: true }} }}); }} }} """ self.scripts.append(script) def _init_font_protection(self): """ Initialisiert Schutz gegen Font Fingerprinting. """ # Realistische Font-Listen für verschiedene Betriebssysteme windows_fonts = [ "Arial", "Arial Black", "Comic Sans MS", "Courier New", "Georgia", "Impact", "Times New Roman", "Trebuchet MS", "Verdana", "Calibri", "Cambria", "Consolas", "Segoe UI" ] mac_fonts = [ "Arial", "Arial Black", "Comic Sans MS", "Courier New", "Georgia", "Helvetica", "Helvetica Neue", "Times New Roman", "Trebuchet MS", "Verdana", "American Typewriter", "Avenir" ] linux_fonts = [ "Arial", "Courier New", "Times New Roman", "DejaVu Sans", "DejaVu Serif", "DejaVu Sans Mono", "Liberation Sans", "Liberation Serif", "Ubuntu", "Noto Sans" ] # Wähle Font-Liste basierend auf User Agent import random fonts_list = random.choice([windows_fonts, mac_fonts, linux_fonts]) fonts_json = json.dumps(fonts_list) script = f""" () => {{ // Font Fingerprinting Protection const fonts = {fonts_json}; // Override document.fonts.check() if (document.fonts && document.fonts.check) {{ const originalCheck = document.fonts.check.bind(document.fonts); document.fonts.check = function(font, text) {{ // Parse font family aus dem font string const fontMatch = font.match(/["']([^"']+)["']/); if (fontMatch) {{ const fontFamily = fontMatch[1]; // Nur true zurückgeben wenn Font in unserer Liste if (fonts.includes(fontFamily)) {{ return originalCheck(font, text); }} return false; }} return originalCheck(font, text); }}; }} // Override CSS Font Loading API if (window.FontFace) {{ const originalFontFace = window.FontFace; window.FontFace = function(family, source, descriptors) {{ // Nur erlauben wenn Font in unserer Liste if (!fonts.includes(family)) {{ throw new Error('Font not available'); }} return new originalFontFace(family, source, descriptors); }}; window.FontFace.prototype = originalFontFace.prototype; }} // Override getComputedStyle für font-family const originalGetComputedStyle = window.getComputedStyle; window.getComputedStyle = function(element, pseudoElt) {{ const style = originalGetComputedStyle(element, pseudoElt); const originalGetter = Object.getOwnPropertyDescriptor(CSSStyleDeclaration.prototype, 'fontFamily').get; Object.defineProperty(style, 'fontFamily', {{ get: function() {{ const fontFamily = originalGetter.call(this); // Filtere Fonts die nicht in unserer Liste sind const fontList = fontFamily.split(',').map(f => f.trim().replace(/['"]/g, '')); const filteredFonts = fontList.filter(f => fonts.includes(f)); if (filteredFonts.length === 0) {{ return '"Arial"'; // Fallback }} return filteredFonts.map(f => `"${{f}}"`).join(', '); }} }}); return style; }}; // Canvas measureText Protection if (CanvasRenderingContext2D.prototype.measureText) {{ const originalMeasureText = CanvasRenderingContext2D.prototype.measureText; CanvasRenderingContext2D.prototype.measureText = function(text) {{ // Füge kleine Variation zu den Messungen hinzu const metrics = originalMeasureText.call(this, text); const variation = 1 + (Math.random() - 0.5) * 0.01; // ±0.5% Variation // Override width getter Object.defineProperty(metrics, 'width', {{ get: function() {{ return metrics.width * variation; }} }}); return metrics; }}; }} }} """ self.scripts.append(script) def apply_to_context(self, context=None): """ Wendet alle Skripte auf den Browser-Kontext an. Args: context: Der Browser-Kontext, falls er noch nicht gesetzt wurde """ if context: self.context = context if not self.context: logger.warning("Kein Browser-Kontext zum Anwenden der Fingerprint-Schutzmaßnahmen") return for script in self.scripts: self.context.add_init_script(script) logger.info(f"Fingerprint-Schutzmaßnahmen auf Browser-Kontext angewendet ({len(self.scripts)} Skripte)") def get_fingerprint_status(self) -> Dict[str, Any]: """ Gibt den aktuellen Status der Fingerprint-Schutzmaßnahmen zurück. Returns: Dict[str, Any]: Status der Fingerprint-Schutzmaßnahmen """ status = { "active": self.context is not None, "script_count": len(self.scripts), "protections": { "canvas": self.defaults["canvas_noise"], "webgl": self.defaults["webgl_noise"], "audio": self.defaults["audio_noise"], "navigator": True, "battery": "getBattery" in self.scripts[-1], "timing": "performance.now" in self.scripts[-1] }, "noise_level": self.noise_level, "custom_values": { "webgl_vendor": self.defaults["webgl_vendor"], "webgl_renderer": self.defaults["webgl_renderer"], "hardware_concurrency": self.defaults["hardware_concurrency"], "device_memory": self.defaults["device_memory"], "timezone_id": self.defaults["timezone_id"] } } return status def _generate_deterministic_value(self, base_value: Any, component: str, date_str: str = None) -> Any: """ Generate deterministic value based on rotation seed, component and date. Args: base_value: The base value to modify component: The component name (e.g., 'canvas', 'audio') date_str: Optional date string, defaults to today Returns: Deterministically modified value """ if not self.defaults.get("rotation_seed"): # No seed = use random values (backward compatible) return base_value if date_str is None: date_str = datetime.now().strftime("%Y-%m-%d") # Create deterministic hash hash_input = f"{self.defaults['rotation_seed']}:{component}:{date_str}" hash_value = hashlib.sha256(hash_input.encode()).hexdigest() # Convert hash to deterministic float between 0 and 1 deterministic_float = int(hash_value[:8], 16) / 0xFFFFFFFF # Apply deterministic modification based on type if isinstance(base_value, (int, float)): # For numbers, add ±10% variation variation = 0.8 + (deterministic_float * 0.4) # 0.8 to 1.2 return type(base_value)(base_value * variation) elif isinstance(base_value, str): # For strings, return the base value (no modification) return base_value elif isinstance(base_value, list): # For lists, deterministically shuffle import random as rand rand.seed(hash_value) shuffled = base_value.copy() rand.shuffle(shuffled) rand.seed() # Reset random seed return shuffled else: return base_value def _get_deterministic_noise_seed(self, component: str) -> int: """Get deterministic noise seed for a component""" if not self.defaults.get("rotation_seed"): return random.randint(1, 1000000) date_str = datetime.now().strftime("%Y-%m-%d") hash_input = f"{self.defaults['rotation_seed']}:noise:{component}:{date_str}" hash_value = hashlib.sha256(hash_input.encode()).hexdigest() return int(hash_value[:8], 16) % 1000000 def rotate_fingerprint(self, noise_level: Optional[float] = None): """ Rotiert den Fingerprint durch Neugenerierung der Schutzmaßnahmen. Args: noise_level: Optionales neues Rauschniveau (0.0-1.0) Returns: bool: True bei Erfolg, False bei Fehler """ if noise_level is not None: self.noise_level = max(0.0, min(1.0, noise_level)) try: # Skripte zurücksetzen self.scripts = [] # Neues WebGL-Vendor/Renderer-Paar generieren webgl_vendors = [ "Google Inc.", "Google Inc. (Intel)", "Google Inc. (NVIDIA)", "Google Inc. (AMD)", "Intel Inc.", "NVIDIA Corporation", "AMD" ] webgl_renderers = [ "ANGLE (Intel, Intel(R) HD Graphics 620 Direct3D11 vs_5_0 ps_5_0)", "ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 Direct3D11 vs_5_0 ps_5_0)", "ANGLE (AMD, AMD Radeon RX 580 Direct3D11 vs_5_0 ps_5_0)", "Intel Iris OpenGL Engine", "NVIDIA GeForce GTX 980 OpenGL Engine", "AMD Radeon Pro 560 OpenGL Engine", "Mesa DRI Intel(R) UHD Graphics 620 (KBL GT2)", "Mesa DRI NVIDIA GeForce GTX 1650" ] # Zufällige Werte wählen self.defaults["webgl_vendor"] = random.choice(webgl_vendors) self.defaults["webgl_renderer"] = random.choice(webgl_renderers) # Hardware-Concurrency und Device-Memory variieren self.defaults["hardware_concurrency"] = random.choice([2, 4, 6, 8, 12, 16]) self.defaults["device_memory"] = random.choice([2, 4, 8, 16]) # Schutzmaßnahmen neu initialisieren self._init_protections() # Auf Kontext anwenden, falls vorhanden if self.context: self.apply_to_context() logger.info(f"Fingerprint erfolgreich rotiert (Noise-Level: {self.noise_level:.2f})") return True except Exception as e: logger.error(f"Fehler bei der Rotation des Fingerprints: {e}") return False