Files
AccountForger-neuerUpload/infrastructure/services/fingerprint/browser_injection_service.py
Claude Project Manager 04585e95b6 Initial commit
2025-08-01 23:50:28 +02:00

481 Zeilen
16 KiB
Python

"""
Browser Injection Service - Handles fingerprint injection into browser contexts.
"""
import json
import base64
from typing import Dict, Any, Optional
from domain.entities.browser_fingerprint import BrowserFingerprint
class BrowserInjectionService:
"""Service for injecting fingerprints into browser contexts."""
def generate_fingerprint_scripts(self, fingerprint: BrowserFingerprint) -> Dict[str, str]:
"""Generate all fingerprint injection scripts."""
return {
"canvas_protection": self._generate_canvas_script(fingerprint),
"webgl_protection": self._generate_webgl_script(fingerprint),
"webrtc_protection": self._generate_webrtc_script(fingerprint),
"navigator_override": self._generate_navigator_script(fingerprint),
"hardware_override": self._generate_hardware_script(fingerprint),
"timezone_override": self._generate_timezone_script(fingerprint),
"audio_protection": self._generate_audio_script(fingerprint),
"font_detection": self._generate_font_script(fingerprint),
"plugin_override": self._generate_plugin_script(fingerprint)
}
def _generate_canvas_script(self, fingerprint: BrowserFingerprint) -> str:
"""Generate canvas fingerprint protection script."""
return f'''
(function() {{
const seed = {fingerprint.canvas_noise.seed};
const noiseLevel = {fingerprint.canvas_noise.noise_level};
const algorithm = "{fingerprint.canvas_noise.algorithm}";
// Deterministic random based on seed
let randomSeed = seed;
function seededRandom() {{
randomSeed = (randomSeed * 9301 + 49297) % 233280;
return randomSeed / 233280;
}}
// Override toDataURL
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function(...args) {{
const context = this.getContext('2d');
if (context) {{
const imageData = context.getImageData(0, 0, this.width, this.height);
const data = imageData.data;
// Apply noise based on algorithm
for (let i = 0; i < data.length; i += 4) {{
if (algorithm === 'gaussian') {{
// Gaussian noise
const noise = (seededRandom() - 0.5) * 2 * noiseLevel * 255;
data[i] = Math.max(0, Math.min(255, data[i] + noise));
data[i+1] = Math.max(0, Math.min(255, data[i+1] + noise));
data[i+2] = Math.max(0, Math.min(255, data[i+2] + noise));
}} else if (algorithm === 'uniform') {{
// Uniform noise
const noise = (seededRandom() - 0.5) * noiseLevel * 255;
data[i] = Math.max(0, Math.min(255, data[i] + noise));
}}
}}
context.putImageData(imageData, 0, 0);
}}
return originalToDataURL.apply(this, args);
}};
// Override getImageData
const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
CanvasRenderingContext2D.prototype.getImageData = function(...args) {{
const imageData = originalGetImageData.apply(this, args);
const data = imageData.data;
// Apply same noise
for (let i = 0; i < data.length; i += 4) {{
const noise = (seededRandom() - 0.5) * noiseLevel * 255;
data[i] = Math.max(0, Math.min(255, data[i] + noise));
}}
return imageData;
}};
}})();
'''
def _generate_webgl_script(self, fingerprint: BrowserFingerprint) -> str:
"""Generate WebGL fingerprint override script."""
return f'''
(function() {{
const overrides = {{
vendor: "{fingerprint.webgl_vendor}",
renderer: "{fingerprint.webgl_renderer}"
}};
// Override WebGL getParameter
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {{
if (parameter === 37445) {{ // UNMASKED_VENDOR_WEBGL
return overrides.vendor;
}}
if (parameter === 37446) {{ // UNMASKED_RENDERER_WEBGL
return overrides.renderer;
}}
return getParameter.apply(this, arguments);
}};
// Same for WebGL2
if (typeof WebGL2RenderingContext !== 'undefined') {{
const getParameter2 = WebGL2RenderingContext.prototype.getParameter;
WebGL2RenderingContext.prototype.getParameter = function(parameter) {{
if (parameter === 37445) return overrides.vendor;
if (parameter === 37446) return overrides.renderer;
return getParameter2.apply(this, arguments);
}};
}}
}})();
'''
def _generate_webrtc_script(self, fingerprint: BrowserFingerprint) -> str:
"""Generate WebRTC protection script."""
if fingerprint.webrtc_config.disable_webrtc:
return '''
(function() {
// Completely disable WebRTC
window.RTCPeerConnection = undefined;
window.RTCSessionDescription = undefined;
window.RTCIceCandidate = undefined;
window.webkitRTCPeerConnection = undefined;
window.mozRTCPeerConnection = undefined;
})();
'''
else:
return f'''
(function() {{
const localIPMask = "{fingerprint.webrtc_config.local_ip_mask}";
// Override RTCPeerConnection
const OriginalRTCPeerConnection = window.RTCPeerConnection ||
window.webkitRTCPeerConnection ||
window.mozRTCPeerConnection;
if (OriginalRTCPeerConnection) {{
window.RTCPeerConnection = function(config, constraints) {{
const pc = new OriginalRTCPeerConnection(config, constraints);
// Override createDataChannel to prevent IP leak
const originalCreateDataChannel = pc.createDataChannel;
pc.createDataChannel = function(...args) {{
return originalCreateDataChannel.apply(pc, args);
}};
// Monitor ICE candidates
pc.addEventListener('icecandidate', function(event) {{
if (event.candidate && event.candidate.candidate) {{
// Mask local IP addresses
event.candidate.candidate = event.candidate.candidate.replace(
/([0-9]{{1,3}}\.){{3}}[0-9]{{1,3}}/g,
function(match) {{
if (match.startsWith('10.') ||
match.startsWith('192.168.') ||
match.startsWith('172.')) {{
return localIPMask;
}}
return match;
}}
);
}}
}});
return pc;
}};
// Copy static properties
Object.keys(OriginalRTCPeerConnection).forEach(key => {{
window.RTCPeerConnection[key] = OriginalRTCPeerConnection[key];
}});
}}
}})();
'''
def _generate_navigator_script(self, fingerprint: BrowserFingerprint) -> str:
"""Generate navigator properties override script."""
nav = fingerprint.navigator_props
languages_json = json.dumps(nav.languages) if nav.languages else '["en-US", "en"]'
return f'''
(function() {{
// Navigator overrides
Object.defineProperty(navigator, 'platform', {{
get: () => "{nav.platform}"
}});
Object.defineProperty(navigator, 'vendor', {{
get: () => "{nav.vendor}"
}});
Object.defineProperty(navigator, 'vendorSub', {{
get: () => "{nav.vendor_sub}"
}});
Object.defineProperty(navigator, 'product', {{
get: () => "{nav.product}"
}});
Object.defineProperty(navigator, 'productSub', {{
get: () => "{nav.product_sub}"
}});
Object.defineProperty(navigator, 'appName', {{
get: () => "{nav.app_name}"
}});
Object.defineProperty(navigator, 'appVersion', {{
get: () => "{nav.app_version}"
}});
Object.defineProperty(navigator, 'userAgent', {{
get: () => "{nav.user_agent}"
}});
Object.defineProperty(navigator, 'language', {{
get: () => "{nav.language}"
}});
Object.defineProperty(navigator, 'languages', {{
get: () => {languages_json}
}});
Object.defineProperty(navigator, 'onLine', {{
get: () => {str(nav.online).lower()}
}});
Object.defineProperty(navigator, 'doNotTrack', {{
get: () => "{nav.do_not_track}"
}});
}})();
'''
def _generate_hardware_script(self, fingerprint: BrowserFingerprint) -> str:
"""Generate hardware properties override script."""
hw = fingerprint.hardware_config
return f'''
(function() {{
// Hardware overrides
Object.defineProperty(navigator, 'hardwareConcurrency', {{
get: () => {hw.hardware_concurrency}
}});
Object.defineProperty(navigator, 'deviceMemory', {{
get: () => {hw.device_memory}
}});
Object.defineProperty(navigator, 'maxTouchPoints', {{
get: () => {hw.max_touch_points}
}});
// Screen overrides
Object.defineProperty(screen, 'width', {{
get: () => {hw.screen_resolution[0]}
}});
Object.defineProperty(screen, 'height', {{
get: () => {hw.screen_resolution[1]}
}});
Object.defineProperty(screen, 'availWidth', {{
get: () => {hw.screen_resolution[0]}
}});
Object.defineProperty(screen, 'availHeight', {{
get: () => {hw.screen_resolution[1] - 40} // Taskbar
}});
Object.defineProperty(screen, 'colorDepth', {{
get: () => {hw.color_depth}
}});
Object.defineProperty(screen, 'pixelDepth', {{
get: () => {hw.color_depth}
}});
Object.defineProperty(window, 'devicePixelRatio', {{
get: () => {hw.pixel_ratio}
}});
}})();
'''
def _generate_timezone_script(self, fingerprint: BrowserFingerprint) -> str:
"""Generate timezone override script."""
return f'''
(function() {{
const timezone = "{fingerprint.timezone}";
const timezoneOffset = {fingerprint.timezone_offset};
// Override Date.prototype.getTimezoneOffset
Date.prototype.getTimezoneOffset = function() {{
return timezoneOffset;
}};
// Override Intl.DateTimeFormat
const OriginalDateTimeFormat = Intl.DateTimeFormat;
Intl.DateTimeFormat = function(...args) {{
if (args.length === 0 || !args[1] || !args[1].timeZone) {{
if (!args[1]) args[1] = {{}};
args[1].timeZone = timezone;
}}
return new OriginalDateTimeFormat(...args);
}};
// Copy static methods
Object.keys(OriginalDateTimeFormat).forEach(key => {{
Intl.DateTimeFormat[key] = OriginalDateTimeFormat[key];
}});
// Override resolvedOptions
Intl.DateTimeFormat.prototype.resolvedOptions = function() {{
const options = OriginalDateTimeFormat.prototype.resolvedOptions.call(this);
options.timeZone = timezone;
return options;
}};
}})();
'''
def _generate_audio_script(self, fingerprint: BrowserFingerprint) -> str:
"""Generate audio context override script."""
return f'''
(function() {{
const audioParams = {{
baseLatency: {fingerprint.audio_context_base_latency},
outputLatency: {fingerprint.audio_context_output_latency},
sampleRate: {fingerprint.audio_context_sample_rate}
}};
// Override AudioContext
const OriginalAudioContext = window.AudioContext || window.webkitAudioContext;
if (OriginalAudioContext) {{
window.AudioContext = function(...args) {{
const context = new OriginalAudioContext(...args);
Object.defineProperty(context, 'baseLatency', {{
get: () => audioParams.baseLatency
}});
Object.defineProperty(context, 'outputLatency', {{
get: () => audioParams.outputLatency
}});
Object.defineProperty(context, 'sampleRate', {{
get: () => audioParams.sampleRate
}});
return context;
}};
// Copy static properties
Object.keys(OriginalAudioContext).forEach(key => {{
window.AudioContext[key] = OriginalAudioContext[key];
}});
}}
}})();
'''
def _generate_font_script(self, fingerprint: BrowserFingerprint) -> str:
"""Generate font detection override script."""
fonts_json = json.dumps(fingerprint.font_list)
return f'''
(function() {{
const allowedFonts = {fonts_json};
// Override font detection methods
const originalGetComputedStyle = window.getComputedStyle;
window.getComputedStyle = function(element, pseudoElt) {{
const style = originalGetComputedStyle.apply(this, arguments);
const originalPropertyGetter = style.getPropertyValue;
style.getPropertyValue = function(prop) {{
if (prop === 'font-family') {{
const value = originalPropertyGetter.apply(this, arguments);
// Filter out non-allowed fonts
const fonts = value.split(',').map(f => f.trim());
const filtered = fonts.filter(f => {{
const fontName = f.replace(/['"]/g, '');
return allowedFonts.some(allowed =>
fontName.toLowerCase().includes(allowed.toLowerCase())
);
}});
return filtered.join(', ');
}}
return originalPropertyGetter.apply(this, arguments);
}};
return style;
}};
}})();
'''
def _generate_plugin_script(self, fingerprint: BrowserFingerprint) -> str:
"""Generate plugin list override script."""
plugins_data = []
for plugin in fingerprint.plugins:
plugins_data.append({
"name": plugin.get("name", ""),
"filename": plugin.get("filename", ""),
"description": plugin.get("description", ""),
"version": plugin.get("version", "")
})
plugins_json = json.dumps(plugins_data)
return f'''
(function() {{
const pluginData = {plugins_json};
// Create fake PluginArray
const fakePlugins = {{}};
fakePlugins.length = pluginData.length;
pluginData.forEach((plugin, index) => {{
const fakePlugin = {{
name: plugin.name,
filename: plugin.filename,
description: plugin.description,
version: plugin.version,
length: 1,
item: function(index) {{ return this; }},
namedItem: function(name) {{ return this; }}
}};
fakePlugins[index] = fakePlugin;
fakePlugins[plugin.name] = fakePlugin;
}});
fakePlugins.item = function(index) {{
return this[index] || null;
}};
fakePlugins.namedItem = function(name) {{
return this[name] || null;
}};
fakePlugins.refresh = function() {{}};
// Override navigator.plugins
Object.defineProperty(navigator, 'plugins', {{
get: () => fakePlugins
}});
}})();
'''
def apply_to_browser_context(self, context: Any, fingerprint: BrowserFingerprint) -> None:
"""Apply fingerprint to a Playwright browser context."""
# Generate all scripts
scripts = self.generate_fingerprint_scripts(fingerprint)
# Combine all scripts
combined_script = '\n'.join(scripts.values())
# Add script to context
context.add_init_script(combined_script)
# Set viewport
context.set_viewport_size({
'width': fingerprint.hardware_config.screen_resolution[0],
'height': fingerprint.hardware_config.screen_resolution[1]
})
# Set locale
context.set_locale(fingerprint.navigator_props.language)
# Set timezone
context.set_timezone_id(fingerprint.timezone)
# Set user agent
context.set_user_agent(fingerprint.navigator_props.user_agent)