Dieser Commit ist enthalten in:
Claude Project Manager
2025-08-01 23:50:28 +02:00
Commit 04585e95b6
290 geänderte Dateien mit 64086 neuen und 0 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,27 @@
"""
Fingerprint services package.
This package contains modular services for browser fingerprinting,
split from the original AdvancedFingerprintService for better
maintainability and testability.
"""
from .fingerprint_profile_service import FingerprintProfileService
from .fingerprint_generator_service import FingerprintGeneratorService
from .fingerprint_rotation_service import FingerprintRotationService
from .fingerprint_validation_service import FingerprintValidationService
from .browser_injection_service import BrowserInjectionService
from .timezone_location_service import TimezoneLocationService
from .account_fingerprint_service import AccountFingerprintService
from .fingerprint_persistence_service import FingerprintPersistenceService
__all__ = [
'FingerprintProfileService',
'FingerprintGeneratorService',
'FingerprintRotationService',
'FingerprintValidationService',
'BrowserInjectionService',
'TimezoneLocationService',
'AccountFingerprintService',
'FingerprintPersistenceService'
]

Datei anzeigen

@ -0,0 +1,217 @@
"""
Account Fingerprint Service - Manages account-bound fingerprints.
"""
import hashlib
import random
from typing import Optional, Dict, Any
from datetime import datetime, timedelta
from domain.entities.browser_fingerprint import BrowserFingerprint
from .fingerprint_generator_service import FingerprintGeneratorService
from .fingerprint_rotation_service import FingerprintRotationService, RotationStrategy
class AccountFingerprintService:
"""Service for managing account-bound fingerprints."""
def __init__(self,
generator_service: Optional[FingerprintGeneratorService] = None,
rotation_service: Optional[FingerprintRotationService] = None):
self.generator_service = generator_service or FingerprintGeneratorService()
self.rotation_service = rotation_service or FingerprintRotationService()
def generate_account_fingerprint(self,
account_id: str,
platform: str,
proxy_location: Optional[str] = None) -> BrowserFingerprint:
"""Generate a fingerprint bound to a specific account."""
# Generate base fingerprint with account binding
fingerprint = self.generator_service.generate_fingerprint(
platform=platform,
proxy_location=proxy_location,
account_id=account_id
)
# Apply deterministic variations based on account ID
self._apply_account_variations(fingerprint, account_id)
return fingerprint
def get_daily_fingerprint(self,
base_fingerprint: BrowserFingerprint,
account_id: str) -> BrowserFingerprint:
"""Get deterministic daily variation of account fingerprint."""
if not base_fingerprint.account_bound:
raise ValueError("Fingerprint must be account-bound for daily variations")
# Calculate days since creation
days_since_creation = (datetime.now() - base_fingerprint.created_at).days
# Generate deterministic seed for today
today_seed = self._generate_daily_seed(account_id, days_since_creation)
# Apply deterministic variations
varied = self._apply_daily_variations(base_fingerprint, today_seed)
return varied
def _apply_account_variations(self, fingerprint: BrowserFingerprint, account_id: str) -> None:
"""Apply account-specific variations to fingerprint."""
# Use account ID to seed variations
account_hash = int(hashlib.md5(account_id.encode()).hexdigest()[:8], 16)
# Deterministic but unique variations
random.seed(account_hash)
# Vary canvas noise seed within range
base_seed = fingerprint.canvas_noise.seed
fingerprint.canvas_noise.seed = base_seed + (account_hash % 1000)
# Vary audio latencies slightly
fingerprint.audio_context_base_latency += (account_hash % 10) * 0.0001
fingerprint.audio_context_output_latency += (account_hash % 10) * 0.0002
# Select subset of fonts deterministically
if len(fingerprint.font_list) > 5:
num_to_remove = account_hash % 3 + 1
for _ in range(num_to_remove):
fingerprint.font_list.pop(random.randint(0, len(fingerprint.font_list) - 1))
# Reset random seed
random.seed()
def _generate_daily_seed(self, account_id: str, day_number: int) -> int:
"""Generate deterministic seed for a specific day."""
# Combine account ID with day number
seed_string = f"{account_id}:{day_number}"
seed_hash = hashlib.sha256(seed_string.encode()).hexdigest()
# Convert to integer seed
return int(seed_hash[:8], 16)
def _apply_daily_variations(self, fingerprint: BrowserFingerprint, daily_seed: int) -> BrowserFingerprint:
"""Apply deterministic daily variations."""
# Use rotation service with controlled randomness
original_seed = random.getstate()
random.seed(daily_seed)
# Minimal rotation for daily changes
varied = self.rotation_service.rotate_fingerprint(fingerprint, RotationStrategy.MINIMAL)
# Additional deterministic changes
self._apply_time_based_changes(varied, daily_seed)
# Restore original random state
random.setstate(original_seed)
return varied
def _apply_time_based_changes(self, fingerprint: BrowserFingerprint, seed: int) -> None:
"""Apply time-based changes that would naturally occur."""
# Browser version might update weekly
week_number = seed % 52
if week_number % 4 == 0: # Every 4 weeks
self._increment_browser_version(fingerprint)
# System uptime affects audio latency
hour_of_day = datetime.now().hour
fingerprint.audio_context_base_latency += (hour_of_day / 24) * 0.001
# Network conditions affect WebRTC
if seed % 3 == 0:
# Change local IP mask (different network)
fingerprint.webrtc_config.local_ip_mask = f"192.168.{seed % 255}.x"
def _increment_browser_version(self, fingerprint: BrowserFingerprint) -> None:
"""Increment browser version number."""
import re
user_agent = fingerprint.navigator_props.user_agent
# Find Chrome version
match = re.search(r'Chrome/(\d+)\.(\d+)\.(\d+)\.(\d+)', user_agent)
if match:
major = int(match.group(1))
minor = int(match.group(2))
build = int(match.group(3))
patch = int(match.group(4))
# Increment build number
build += 1
# Update user agent
old_version = match.group(0)
new_version = f"Chrome/{major}.{minor}.{build}.{patch}"
fingerprint.navigator_props.user_agent = user_agent.replace(old_version, new_version)
def validate_account_binding(self, fingerprint: BrowserFingerprint, account_id: str) -> bool:
"""Validate that a fingerprint is properly bound to an account."""
if not fingerprint.account_bound:
return False
if not fingerprint.static_components:
return False
if not fingerprint.rotation_seed:
return False
# Could add more validation here (e.g., check against database)
return True
def get_fingerprint_age_days(self, fingerprint: BrowserFingerprint) -> int:
"""Get age of fingerprint in days."""
if not fingerprint.created_at:
return 0
return (datetime.now() - fingerprint.created_at).days
def should_rotate_fingerprint(self, fingerprint: BrowserFingerprint) -> bool:
"""Determine if fingerprint should be rotated."""
age_days = self.get_fingerprint_age_days(fingerprint)
# Rotate after 30 days
if age_days > 30:
return True
# Check last rotation
if fingerprint.last_rotated:
days_since_rotation = (datetime.now() - fingerprint.last_rotated).days
if days_since_rotation > 7: # Weekly rotation check
return True
return False
def prepare_session_fingerprint(self,
fingerprint: BrowserFingerprint,
session_data: Dict[str, Any]) -> BrowserFingerprint:
"""Prepare fingerprint for use with existing session."""
# Sessions might have slightly different characteristics
session_fp = self._deep_copy_fingerprint(fingerprint)
# Apply session-specific adjustments
if "browser_version" in session_data:
self._update_to_browser_version(session_fp, session_data["browser_version"])
if "screen_resolution" in session_data:
session_fp.hardware_config.screen_resolution = tuple(session_data["screen_resolution"])
return session_fp
def _update_to_browser_version(self, fingerprint: BrowserFingerprint, version: str) -> None:
"""Update fingerprint to specific browser version."""
import re
user_agent = fingerprint.navigator_props.user_agent
# Replace Chrome version
user_agent = re.sub(r'Chrome/[\d.]+', f'Chrome/{version}', user_agent)
fingerprint.navigator_props.user_agent = user_agent
# Update app version
app_version = fingerprint.navigator_props.app_version
app_version = re.sub(r'Chrome/[\d.]+', f'Chrome/{version}', app_version)
fingerprint.navigator_props.app_version = app_version
def _deep_copy_fingerprint(self, fingerprint: BrowserFingerprint) -> BrowserFingerprint:
"""Create a deep copy of fingerprint."""
# Delegate to rotation service's implementation
return self.rotation_service._deep_copy_fingerprint(fingerprint)

Datei anzeigen

@ -0,0 +1,481 @@
"""
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)

Datei anzeigen

@ -0,0 +1,243 @@
"""
Fingerprint Generator Service - Core fingerprint generation logic.
"""
import random
import uuid
from typing import Optional, Dict, Any, List
from datetime import datetime
from domain.entities.browser_fingerprint import (
BrowserFingerprint, CanvasNoise, WebRTCConfig,
HardwareConfig, NavigatorProperties, StaticComponents
)
from .fingerprint_profile_service import FingerprintProfileService
from .timezone_location_service import TimezoneLocationService
class FingerprintGeneratorService:
"""Service for generating browser fingerprints."""
def __init__(self,
profile_service: Optional[FingerprintProfileService] = None,
timezone_service: Optional[TimezoneLocationService] = None):
self.profile_service = profile_service or FingerprintProfileService()
self.timezone_service = timezone_service or TimezoneLocationService()
def generate_fingerprint(self,
profile_type: Optional[str] = None,
platform: Optional[str] = None,
proxy_location: Optional[str] = None,
account_id: Optional[str] = None) -> BrowserFingerprint:
"""Generate a new browser fingerprint."""
# Get base profile
profile = self.profile_service.get_profile(profile_type)
# Get location data
location_data = self.timezone_service.get_consistent_location_data(proxy_location)
# Generate components
fingerprint_id = str(uuid.uuid4())
hardware_config = self._generate_hardware_config(profile)
navigator_props = self._generate_navigator_properties(profile, location_data)
canvas_noise = self._generate_canvas_noise()
webrtc_config = self._generate_webrtc_config(profile["platform"])
webgl_info = self._generate_webgl_info(profile)
audio_context = self._generate_audio_context(profile_type)
fonts = self.profile_service.get_fonts_for_platform(profile["platform"])
plugins = self.profile_service.get_plugin_list(profile["platform"])
# Generate static components if account-bound
static_components = None
rotation_seed = None
if account_id:
static_components = self._generate_static_components(profile, location_data)
rotation_seed = random.randint(1000000, 9999999)
# Platform-specific config
platform_config = self._generate_platform_specific_config(platform, profile)
return BrowserFingerprint(
fingerprint_id=fingerprint_id,
canvas_noise=canvas_noise,
webrtc_config=webrtc_config,
font_list=fonts,
hardware_config=hardware_config,
navigator_props=navigator_props,
webgl_vendor=webgl_info["vendor"],
webgl_renderer=webgl_info["renderer"],
audio_context_base_latency=audio_context["base_latency"],
audio_context_output_latency=audio_context["output_latency"],
audio_context_sample_rate=audio_context["sample_rate"],
timezone=location_data["timezone"],
timezone_offset=location_data["timezone_offset"],
plugins=plugins,
created_at=datetime.now(),
last_rotated=datetime.now(),
static_components=static_components,
rotation_seed=rotation_seed,
account_bound=bool(account_id),
platform_specific_config=platform_config
)
def _generate_hardware_config(self, profile: Dict[str, Any]) -> HardwareConfig:
"""Generate hardware configuration."""
return HardwareConfig(
hardware_concurrency=random.choice(profile["hardware_concurrency"]),
device_memory=random.choice(profile["device_memory"]),
max_touch_points=10 if "mobile" in profile["name"].lower() else 0,
screen_resolution=random.choice(profile["screen_resolution"]),
color_depth=random.choice([24, 32]),
pixel_ratio=random.choice([1.0, 1.5, 2.0, 3.0])
)
def _generate_navigator_properties(self, profile: Dict[str, Any],
location_data: Dict[str, Any]) -> NavigatorProperties:
"""Generate navigator properties."""
ua_components = self.profile_service.get_user_agent_components(profile["platform"])
# Build user agent
if "Chrome" in ua_components["browser"]:
user_agent = f"Mozilla/5.0 ({ua_components['os']}) {ua_components['engine']} {ua_components['browser']} Safari/537.36"
else:
user_agent = f"Mozilla/5.0 ({ua_components['os']}) {ua_components['engine']} {ua_components['browser']}"
return NavigatorProperties(
platform=profile["platform"],
vendor=profile["vendor"],
vendor_sub="",
product="Gecko",
product_sub="20030107",
app_name="Netscape",
app_version="5.0 ({})".format(ua_components["os"]),
user_agent=user_agent,
language=location_data["language"],
languages=location_data["languages"],
online=True,
do_not_track=random.choice(["1", "unspecified"])
)
def _generate_canvas_noise(self) -> CanvasNoise:
"""Generate canvas noise configuration."""
config = self.profile_service.get_canvas_noise_config()
return CanvasNoise(
noise_level=config["noise_level"],
seed=random.randint(1000, 99999),
algorithm=config["algorithm"]
)
def _generate_webrtc_config(self, platform: str) -> WebRTCConfig:
"""Generate WebRTC configuration."""
config = self.profile_service.get_webrtc_config(platform)
return WebRTCConfig(
enabled=config["enabled"],
ice_servers=config["ice_servers"],
local_ip_mask=config["local_ip_mask"],
disable_webrtc=config["disable_webrtc"]
)
def _generate_webgl_info(self, profile: Dict[str, Any]) -> Dict[str, str]:
"""Generate WebGL vendor and renderer."""
renderer = random.choice(profile["renderer"])
# Ensure vendor matches renderer
if "Intel" in renderer:
vendor = "Intel Inc."
elif "NVIDIA" in renderer or "GeForce" in renderer:
vendor = "NVIDIA Corporation"
elif "AMD" in renderer or "Radeon" in renderer:
vendor = "AMD"
elif "Apple" in renderer:
vendor = "Apple Inc."
else:
vendor = profile["vendor"]
return {
"vendor": vendor,
"renderer": renderer
}
def _generate_audio_context(self, profile_type: Optional[str]) -> Dict[str, Any]:
"""Generate audio context parameters."""
audio_type = "mobile" if profile_type == "mobile" else "default"
base_config = self.profile_service.get_audio_context(audio_type)
# Add slight variations
return {
"base_latency": base_config["base_latency"] + random.uniform(-0.001, 0.001),
"output_latency": base_config["output_latency"] + random.uniform(-0.002, 0.002),
"sample_rate": base_config["sample_rate"]
}
def _generate_static_components(self, profile: Dict[str, Any],
location_data: Dict[str, Any]) -> StaticComponents:
"""Generate static components for account-bound fingerprints."""
# Determine device type
if "mobile" in profile["name"].lower():
device_type = "mobile"
elif "tablet" in profile["name"].lower():
device_type = "tablet"
else:
device_type = "desktop"
# Determine OS family
if "Win" in profile["platform"]:
os_family = "windows"
elif "Mac" in profile["platform"] or "iPhone" in profile["platform"]:
os_family = "macos"
elif "Android" in profile["platform"] or "Linux" in profile["platform"]:
os_family = "linux"
else:
os_family = "other"
# Determine browser family
browser_family = "chromium" # Most common
# Select base fonts (these won't change during rotation)
all_fonts = self.profile_service.get_fonts_for_platform(profile["platform"])
base_fonts = random.sample(all_fonts, min(10, len(all_fonts)))
return StaticComponents(
device_type=device_type,
os_family=os_family,
browser_family=browser_family,
gpu_vendor=profile["vendor"],
gpu_model=profile["renderer"][0] if profile["renderer"] else "Unknown",
cpu_architecture="x86_64" if device_type == "desktop" else "arm64",
base_fonts=base_fonts,
base_resolution=profile["screen_resolution"][0],
base_timezone=location_data["timezone"]
)
def _generate_platform_specific_config(self, platform: Optional[str],
profile: Dict[str, Any]) -> Dict[str, Any]:
"""Generate platform-specific configuration."""
config = {
"platform": platform or "unknown",
"profile_name": profile["name"],
"protection_level": "standard"
}
if platform == "instagram":
config.update({
"app_id": "936619743392459",
"ajax_id": "1234567890",
"ig_did": str(uuid.uuid4()).upper(),
"claim": "0"
})
elif platform == "facebook":
config.update({
"fb_api_version": "v18.0",
"fb_app_id": "256281040558",
"fb_locale": "en_US"
})
elif platform == "tiktok":
config.update({
"tt_webid": str(random.randint(10**18, 10**19)),
"tt_csrf_token": str(uuid.uuid4()),
"browser_name": "chrome",
"browser_version": "120"
})
return config

Datei anzeigen

@ -0,0 +1,259 @@
"""
Fingerprint Persistence Service - Handles fingerprint storage and retrieval.
"""
import logging
from typing import Optional, List, Dict, Any
from datetime import datetime, timedelta
from domain.entities.browser_fingerprint import BrowserFingerprint
from domain.repositories.fingerprint_repository import IFingerprintRepository
logger = logging.getLogger("fingerprint_persistence_service")
class FingerprintPersistenceService:
"""Service for fingerprint persistence operations."""
def __init__(self, repository: IFingerprintRepository):
self.repository = repository
self._cache = {} # Simple in-memory cache
self._cache_ttl = 300 # 5 minutes
def save_fingerprint(self, fingerprint: BrowserFingerprint) -> str:
"""Save a fingerprint and return its ID."""
try:
fingerprint_id = self.repository.save(fingerprint)
# Update cache
self._cache[fingerprint_id] = {
'fingerprint': fingerprint,
'timestamp': datetime.now()
}
logger.info(f"Saved fingerprint: {fingerprint_id}")
return fingerprint_id
except Exception as e:
logger.error(f"Failed to save fingerprint: {e}")
raise
def load_fingerprint(self, fingerprint_id: str) -> Optional[BrowserFingerprint]:
"""Load a fingerprint by ID."""
# Check cache first
if fingerprint_id in self._cache:
cache_entry = self._cache[fingerprint_id]
if (datetime.now() - cache_entry['timestamp']).seconds < self._cache_ttl:
logger.debug(f"Loaded fingerprint from cache: {fingerprint_id}")
return cache_entry['fingerprint']
# Load from repository
try:
fingerprint = self.repository.find_by_id(fingerprint_id)
if fingerprint:
# Update cache
self._cache[fingerprint_id] = {
'fingerprint': fingerprint,
'timestamp': datetime.now()
}
logger.info(f"Loaded fingerprint from repository: {fingerprint_id}")
else:
logger.warning(f"Fingerprint not found: {fingerprint_id}")
return fingerprint
except Exception as e:
logger.error(f"Failed to load fingerprint {fingerprint_id}: {e}")
return None
def load_fingerprint_for_account(self, account_id: str) -> Optional[BrowserFingerprint]:
"""Load fingerprint associated with an account."""
try:
fingerprint = self.repository.find_by_account_id(account_id)
if fingerprint:
# Update cache
self._cache[fingerprint.fingerprint_id] = {
'fingerprint': fingerprint,
'timestamp': datetime.now()
}
logger.info(f"Loaded fingerprint for account {account_id}")
else:
logger.warning(f"No fingerprint found for account {account_id}")
return fingerprint
except Exception as e:
logger.error(f"Failed to load fingerprint for account {account_id}: {e}")
return None
def update_fingerprint(self, fingerprint: BrowserFingerprint) -> bool:
"""Update an existing fingerprint."""
try:
success = self.repository.update(fingerprint)
if success:
# Update cache
self._cache[fingerprint.fingerprint_id] = {
'fingerprint': fingerprint,
'timestamp': datetime.now()
}
logger.info(f"Updated fingerprint: {fingerprint.fingerprint_id}")
else:
logger.warning(f"Failed to update fingerprint: {fingerprint.fingerprint_id}")
return success
except Exception as e:
logger.error(f"Failed to update fingerprint {fingerprint.fingerprint_id}: {e}")
return False
def delete_fingerprint(self, fingerprint_id: str) -> bool:
"""Delete a fingerprint."""
try:
success = self.repository.delete(fingerprint_id)
if success:
# Remove from cache
self._cache.pop(fingerprint_id, None)
logger.info(f"Deleted fingerprint: {fingerprint_id}")
else:
logger.warning(f"Failed to delete fingerprint: {fingerprint_id}")
return success
except Exception as e:
logger.error(f"Failed to delete fingerprint {fingerprint_id}: {e}")
return False
def list_fingerprints(self, limit: int = 100) -> List[BrowserFingerprint]:
"""List all fingerprints."""
try:
fingerprints = self.repository.find_all()
# Limit results
if len(fingerprints) > limit:
fingerprints = fingerprints[:limit]
logger.info(f"Listed {len(fingerprints)} fingerprints")
return fingerprints
except Exception as e:
logger.error(f"Failed to list fingerprints: {e}")
return []
def list_recent_fingerprints(self, limit: int = 10) -> List[BrowserFingerprint]:
"""List recently created fingerprints."""
try:
fingerprints = self.repository.find_recent(limit)
logger.info(f"Listed {len(fingerprints)} recent fingerprints")
return fingerprints
except Exception as e:
logger.error(f"Failed to list recent fingerprints: {e}")
return []
def list_fingerprints_by_platform(self, platform: str) -> List[BrowserFingerprint]:
"""List fingerprints for a specific platform."""
try:
fingerprints = self.repository.find_by_platform(platform)
logger.info(f"Listed {len(fingerprints)} fingerprints for platform {platform}")
return fingerprints
except Exception as e:
logger.error(f"Failed to list fingerprints for platform {platform}: {e}")
return []
def get_fingerprint_pool(self, size: int = 10) -> List[BrowserFingerprint]:
"""Get a pool of random fingerprints."""
try:
# Get more than needed to filter
candidates = self.repository.find_recent(size * 3)
# Filter for quality
quality_fingerprints = []
for fp in candidates:
# Skip if too old
if fp.created_at:
age_days = (datetime.now() - fp.created_at).days
if age_days > 30:
continue
# Skip if account-bound
if fp.account_bound:
continue
quality_fingerprints.append(fp)
if len(quality_fingerprints) >= size:
break
logger.info(f"Created fingerprint pool of size {len(quality_fingerprints)}")
return quality_fingerprints
except Exception as e:
logger.error(f"Failed to create fingerprint pool: {e}")
return []
def cleanup_old_fingerprints(self, days_to_keep: int = 90) -> int:
"""Clean up fingerprints older than specified days."""
try:
# Calculate cutoff date
cutoff = datetime.now() - timedelta(days=days_to_keep)
# Get all fingerprints to check
all_fingerprints = self.repository.find_all()
deleted_count = 0
for fp in all_fingerprints:
if fp.created_at and fp.created_at < cutoff:
# Skip if account-bound
if fp.account_bound:
continue
if self.repository.delete(fp.fingerprint_id):
deleted_count += 1
# Remove from cache
self._cache.pop(fp.fingerprint_id, None)
logger.info(f"Cleaned up {deleted_count} old fingerprints")
return deleted_count
except Exception as e:
logger.error(f"Failed to cleanup old fingerprints: {e}")
return 0
def clear_cache(self) -> None:
"""Clear the in-memory cache."""
self._cache.clear()
logger.info("Cleared fingerprint cache")
def get_statistics(self) -> Dict[str, Any]:
"""Get fingerprint statistics."""
try:
total = self.repository.count()
recent = len(self.repository.find_recent(100))
# Platform breakdown
platforms = {}
for platform in ['instagram', 'facebook', 'tiktok', 'twitter']:
count = len(self.repository.find_by_platform(platform))
if count > 0:
platforms[platform] = count
return {
'total_fingerprints': total,
'recent_fingerprints': recent,
'platforms': platforms,
'cache_size': len(self._cache)
}
except Exception as e:
logger.error(f"Failed to get statistics: {e}")
return {
'total_fingerprints': 0,
'recent_fingerprints': 0,
'platforms': {},
'cache_size': len(self._cache)
}

Datei anzeigen

@ -0,0 +1,182 @@
"""
Fingerprint Profile Service - Manages predefined fingerprint profiles and configurations.
"""
import random
from typing import List, Dict, Any, Optional, Tuple
class FingerprintProfileService:
"""Service for managing fingerprint profiles and configurations."""
DESKTOP_PROFILES = [
{
"name": "Windows Chrome User",
"platform": "Win32",
"hardware_concurrency": [4, 8, 16],
"device_memory": [4, 8, 16],
"screen_resolution": [(1920, 1080), (2560, 1440), (1366, 768)],
"vendor": "Google Inc.",
"renderer": ["ANGLE (Intel HD Graphics)", "ANGLE (NVIDIA GeForce GTX)", "ANGLE (AMD Radeon)"]
},
{
"name": "MacOS Safari User",
"platform": "MacIntel",
"hardware_concurrency": [4, 8, 12],
"device_memory": [8, 16, 32],
"screen_resolution": [(1440, 900), (2560, 1600), (5120, 2880)],
"vendor": "Apple Inc.",
"renderer": ["Apple M1", "Intel Iris", "AMD Radeon Pro"]
}
]
MOBILE_PROFILES = [
{
"name": "Android Chrome",
"platform": "Linux armv8l",
"hardware_concurrency": [4, 6, 8],
"device_memory": [3, 4, 6, 8],
"screen_resolution": [(360, 740), (375, 812), (414, 896)],
"vendor": "Google Inc.",
"renderer": ["Adreno", "Mali", "PowerVR"]
},
{
"name": "iOS Safari",
"platform": "iPhone",
"hardware_concurrency": [2, 4, 6],
"device_memory": [2, 3, 4],
"screen_resolution": [(375, 667), (375, 812), (414, 896)],
"vendor": "Apple Inc.",
"renderer": ["Apple GPU"]
}
]
COMMON_FONTS = {
"windows": [
"Arial", "Arial Black", "Comic Sans MS", "Courier New",
"Georgia", "Impact", "Times New Roman", "Trebuchet MS",
"Verdana", "Webdings", "Wingdings", "Calibri", "Cambria",
"Consolas", "Segoe UI", "Tahoma"
],
"mac": [
"Arial", "Arial Black", "Comic Sans MS", "Courier New",
"Georgia", "Helvetica", "Helvetica Neue", "Times New Roman",
"Trebuchet MS", "Verdana", "American Typewriter", "Avenir",
"Baskerville", "Big Caslon", "Futura", "Geneva", "Gill Sans"
],
"linux": [
"Arial", "Courier New", "Times New Roman", "DejaVu Sans",
"DejaVu Serif", "DejaVu Sans Mono", "Liberation Sans",
"Liberation Serif", "Ubuntu", "Droid Sans", "Noto Sans"
]
}
AUDIO_CONTEXTS = {
"default": {
"base_latency": 0.01,
"output_latency": 0.02,
"sample_rate": 48000
},
"high_quality": {
"base_latency": 0.005,
"output_latency": 0.01,
"sample_rate": 96000
},
"mobile": {
"base_latency": 0.02,
"output_latency": 0.04,
"sample_rate": 44100
}
}
def get_profile(self, profile_type: Optional[str] = None) -> Dict[str, Any]:
"""Get a fingerprint profile based on type."""
if profile_type == "mobile":
return random.choice(self.MOBILE_PROFILES)
else:
return random.choice(self.DESKTOP_PROFILES)
def get_fonts_for_platform(self, platform: str) -> List[str]:
"""Get common fonts for a specific platform."""
if "Win" in platform:
base_fonts = self.COMMON_FONTS["windows"]
elif "Mac" in platform or "iPhone" in platform:
base_fonts = self.COMMON_FONTS["mac"]
else:
base_fonts = self.COMMON_FONTS["linux"]
# Randomly select 80-95% of fonts to add variation
num_fonts = random.randint(int(len(base_fonts) * 0.8), int(len(base_fonts) * 0.95))
return random.sample(base_fonts, num_fonts)
def get_audio_context(self, profile_type: str = "default") -> Dict[str, Any]:
"""Get audio context configuration."""
return self.AUDIO_CONTEXTS.get(profile_type, self.AUDIO_CONTEXTS["default"])
def get_user_agent_components(self, platform: str) -> Dict[str, str]:
"""Get user agent components for a platform."""
components = {
"Win32": {
"os": "Windows NT 10.0; Win64; x64",
"browser": "Chrome/120.0.0.0",
"engine": "AppleWebKit/537.36 (KHTML, like Gecko)"
},
"MacIntel": {
"os": "Macintosh; Intel Mac OS X 10_15_7",
"browser": "Chrome/120.0.0.0",
"engine": "AppleWebKit/537.36 (KHTML, like Gecko)"
},
"Linux armv8l": {
"os": "Linux; Android 13",
"browser": "Chrome/120.0.0.0 Mobile",
"engine": "AppleWebKit/537.36 (KHTML, like Gecko)"
},
"iPhone": {
"os": "iPhone; CPU iPhone OS 17_0 like Mac OS X",
"browser": "Version/17.0 Mobile/15E148",
"engine": "AppleWebKit/605.1.15 (KHTML, like Gecko)"
}
}
return components.get(platform, components["Win32"])
def get_canvas_noise_config(self, profile_type: str = "default") -> Dict[str, Any]:
"""Get canvas noise configuration."""
configs = {
"default": {"noise_level": 0.02, "algorithm": "gaussian"},
"aggressive": {"noise_level": 0.05, "algorithm": "perlin"},
"minimal": {"noise_level": 0.01, "algorithm": "uniform"}
}
return configs.get(profile_type, configs["default"])
def get_webrtc_config(self, platform: str) -> Dict[str, Any]:
"""Get WebRTC configuration for platform."""
if "mobile" in platform.lower() or "android" in platform.lower() or "iphone" in platform.lower():
return {
"enabled": True,
"ice_servers": ["stun:stun.l.google.com:19302"],
"local_ip_mask": "192.168.1.x",
"disable_webrtc": False
}
else:
return {
"enabled": True,
"ice_servers": ["stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302"],
"local_ip_mask": "10.0.0.x",
"disable_webrtc": False
}
def get_plugin_list(self, platform: str) -> List[Dict[str, str]]:
"""Get plugin list for platform."""
if "Win" in platform:
return [
{"name": "Chrome PDF Plugin", "filename": "internal-pdf-viewer"},
{"name": "Chrome PDF Viewer", "filename": "mhjfbmdgcfjbbpaeojofohoefgiehjai"},
{"name": "Native Client", "filename": "internal-nacl-plugin"}
]
elif "Mac" in platform:
return [
{"name": "Chrome PDF Plugin", "filename": "internal-pdf-viewer"},
{"name": "Chrome PDF Viewer", "filename": "mhjfbmdgcfjbbpaeojofohoefgiehjai"}
]
else:
return []

Datei anzeigen

@ -0,0 +1,356 @@
"""
Fingerprint Rotation Service - Handles fingerprint rotation and modification.
"""
import random
import copy
from typing import List, Dict, Any, Optional
from datetime import datetime, timedelta
from enum import Enum
from domain.entities.browser_fingerprint import (
BrowserFingerprint, CanvasNoise, WebRTCConfig,
HardwareConfig, NavigatorProperties
)
class RotationStrategy(Enum):
"""Fingerprint rotation strategies."""
MINIMAL = "minimal" # Only rotate most volatile attributes
GRADUAL = "gradual" # Gradual changes over time
COMPLETE = "complete" # Complete regeneration (new device)
class FingerprintRotationService:
"""Service for rotating and modifying fingerprints."""
def __init__(self):
self.rotation_history = {} # Track rotation history per fingerprint
def rotate_fingerprint(self,
fingerprint: BrowserFingerprint,
strategy: RotationStrategy = RotationStrategy.MINIMAL) -> BrowserFingerprint:
"""Rotate a fingerprint based on the specified strategy."""
# Create a deep copy to avoid modifying the original
rotated = self._deep_copy_fingerprint(fingerprint)
# Track rotation
self._track_rotation(fingerprint.fingerprint_id, strategy)
# Apply rotation based on strategy
if strategy == RotationStrategy.MINIMAL:
self._apply_minimal_rotation(rotated)
elif strategy == RotationStrategy.GRADUAL:
self._apply_gradual_rotation(rotated)
elif strategy == RotationStrategy.COMPLETE:
self._apply_complete_rotation(rotated)
# Update rotation timestamp
rotated.last_rotated = datetime.now()
return rotated
def _apply_minimal_rotation(self, fingerprint: BrowserFingerprint) -> None:
"""Apply minimal rotation - only most volatile attributes."""
# Rotate canvas noise seed (most commonly changed)
fingerprint.canvas_noise.seed = random.randint(1000, 99999)
# Slight audio context variations
fingerprint.audio_context_base_latency += random.uniform(-0.001, 0.001)
fingerprint.audio_context_output_latency += random.uniform(-0.001, 0.001)
# Update timezone offset if DST might have changed
if self._should_update_dst(fingerprint):
fingerprint.timezone_offset += 60 if fingerprint.timezone_offset > 0 else -60
# Minor font list changes (add/remove 1-2 fonts)
self._rotate_fonts_minimal(fingerprint)
def _apply_gradual_rotation(self, fingerprint: BrowserFingerprint) -> None:
"""Apply gradual rotation - simulate natural changes over time."""
# All minimal changes
self._apply_minimal_rotation(fingerprint)
# WebGL renderer might get driver updates
if random.random() < 0.3:
self._update_webgl_version(fingerprint)
# Browser version update
if random.random() < 0.4:
self._update_browser_version(fingerprint)
# Screen resolution might change (external monitor)
if random.random() < 0.1:
self._rotate_screen_resolution(fingerprint)
# More significant font changes
self._rotate_fonts_gradual(fingerprint)
def _apply_complete_rotation(self, fingerprint: BrowserFingerprint) -> None:
"""Apply complete rotation - simulate device change."""
# Keep only static components if account-bound
if fingerprint.account_bound and fingerprint.static_components:
# Maintain same device class but change specifics
self._rotate_within_device_class(fingerprint)
else:
# Complete change - new device simulation
self._rotate_to_new_device(fingerprint)
# New canvas noise configuration
fingerprint.canvas_noise = CanvasNoise(
noise_level=random.choice([0.01, 0.02, 0.03]),
seed=random.randint(1000, 99999),
algorithm=random.choice(["gaussian", "uniform"])
)
# New audio context
fingerprint.audio_context_base_latency = random.uniform(0.005, 0.02)
fingerprint.audio_context_output_latency = random.uniform(0.01, 0.04)
# Complete font list regeneration
self._rotate_fonts_complete(fingerprint)
def _rotate_fonts_minimal(self, fingerprint: BrowserFingerprint) -> None:
"""Minimal font rotation - add/remove 1-2 fonts."""
current_fonts = fingerprint.font_list.copy()
# Remove 1-2 random fonts
if len(current_fonts) > 10:
for _ in range(random.randint(0, 2)):
if current_fonts:
current_fonts.remove(random.choice(current_fonts))
# Add 1-2 new fonts
possible_additions = ["Segoe UI Light", "Segoe UI Semibold", "Arial Narrow",
"Century Gothic", "Franklin Gothic Medium"]
for _ in range(random.randint(0, 2)):
new_font = random.choice(possible_additions)
if new_font not in current_fonts:
current_fonts.append(new_font)
fingerprint.font_list = current_fonts
def _rotate_fonts_gradual(self, fingerprint: BrowserFingerprint) -> None:
"""Gradual font rotation - change 20-30% of fonts."""
current_fonts = fingerprint.font_list.copy()
num_to_change = int(len(current_fonts) * random.uniform(0.2, 0.3))
# Remove some fonts
for _ in range(num_to_change // 2):
if current_fonts:
current_fonts.remove(random.choice(current_fonts))
# Add new fonts
base_fonts = self._get_base_fonts_for_platform(fingerprint.navigator_props.platform)
for _ in range(num_to_change // 2):
available = [f for f in base_fonts if f not in current_fonts]
if available:
current_fonts.append(random.choice(available))
fingerprint.font_list = current_fonts
def _rotate_fonts_complete(self, fingerprint: BrowserFingerprint) -> None:
"""Complete font rotation - regenerate font list."""
base_fonts = self._get_base_fonts_for_platform(fingerprint.navigator_props.platform)
num_fonts = random.randint(int(len(base_fonts) * 0.7), int(len(base_fonts) * 0.9))
fingerprint.font_list = random.sample(base_fonts, num_fonts)
def _update_webgl_version(self, fingerprint: BrowserFingerprint) -> None:
"""Update WebGL renderer version (driver update)."""
renderer = fingerprint.webgl_renderer
# Update version numbers in renderer string
if "ANGLE" in renderer:
# Update Direct3D version
if "Direct3D11" in renderer:
renderer = renderer.replace("Direct3D11", "Direct3D11.1")
elif "Direct3D9" in renderer:
renderer = renderer.replace("Direct3D9", "Direct3D11")
# Update driver versions
import re
version_pattern = r'\d+\.\d+\.\d+\.\d+'
match = re.search(version_pattern, renderer)
if match:
old_version = match.group()
parts = old_version.split('.')
# Increment minor version
parts[2] = str(int(parts[2]) + random.randint(1, 10))
new_version = '.'.join(parts)
renderer = renderer.replace(old_version, new_version)
fingerprint.webgl_renderer = renderer
def _update_browser_version(self, fingerprint: BrowserFingerprint) -> None:
"""Update browser version in user agent."""
user_agent = fingerprint.navigator_props.user_agent
# Update Chrome version
if "Chrome/" in user_agent:
import re
match = re.search(r'Chrome/(\d+)\.', user_agent)
if match:
current_version = int(match.group(1))
new_version = current_version + random.randint(1, 3)
user_agent = user_agent.replace(f'Chrome/{current_version}', f'Chrome/{new_version}')
fingerprint.navigator_props.user_agent = user_agent
def _rotate_screen_resolution(self, fingerprint: BrowserFingerprint) -> None:
"""Rotate screen resolution (external monitor change)."""
common_resolutions = [
(1920, 1080), (2560, 1440), (3840, 2160), # 16:9
(1920, 1200), (2560, 1600), # 16:10
(1366, 768), (1600, 900) # Laptop
]
current = fingerprint.hardware_config.screen_resolution
available = [res for res in common_resolutions if res != current]
if available:
fingerprint.hardware_config.screen_resolution = random.choice(available)
def _rotate_within_device_class(self, fingerprint: BrowserFingerprint) -> None:
"""Rotate within the same device class (for account-bound fingerprints)."""
static = fingerprint.static_components
if static.device_type == "desktop":
# Change to different desktop configuration
fingerprint.hardware_config.hardware_concurrency = random.choice([4, 8, 12, 16])
fingerprint.hardware_config.device_memory = random.choice([8, 16, 32])
elif static.device_type == "mobile":
# Change to different mobile configuration
fingerprint.hardware_config.hardware_concurrency = random.choice([4, 6, 8])
fingerprint.hardware_config.device_memory = random.choice([3, 4, 6])
# Update renderer within same GPU vendor
if "Intel" in static.gpu_vendor:
fingerprint.webgl_renderer = random.choice([
"ANGLE (Intel HD Graphics 620)",
"ANGLE (Intel UHD Graphics 630)",
"ANGLE (Intel Iris Xe Graphics)"
])
elif "NVIDIA" in static.gpu_vendor:
fingerprint.webgl_renderer = random.choice([
"ANGLE (NVIDIA GeForce GTX 1060)",
"ANGLE (NVIDIA GeForce RTX 3060)",
"ANGLE (NVIDIA GeForce GTX 1660)"
])
def _rotate_to_new_device(self, fingerprint: BrowserFingerprint) -> None:
"""Rotate to completely new device."""
# This would typically regenerate most components
# For now, we'll do significant changes
# New hardware configuration
fingerprint.hardware_config = HardwareConfig(
hardware_concurrency=random.choice([4, 8, 12, 16]),
device_memory=random.choice([4, 8, 16, 32]),
max_touch_points=0,
screen_resolution=random.choice([(1920, 1080), (2560, 1440)]),
color_depth=random.choice([24, 32]),
pixel_ratio=random.choice([1.0, 1.5, 2.0])
)
# New WebGL
vendors = ["Intel Inc.", "NVIDIA Corporation", "AMD"]
fingerprint.webgl_vendor = random.choice(vendors)
if fingerprint.webgl_vendor == "Intel Inc.":
fingerprint.webgl_renderer = "ANGLE (Intel HD Graphics)"
elif fingerprint.webgl_vendor == "NVIDIA Corporation":
fingerprint.webgl_renderer = "ANGLE (NVIDIA GeForce GTX)"
else:
fingerprint.webgl_renderer = "ANGLE (AMD Radeon)"
def _should_update_dst(self, fingerprint: BrowserFingerprint) -> bool:
"""Check if DST update might be needed."""
# Simple check - in reality would check actual DST dates
if fingerprint.last_rotated:
days_since_rotation = (datetime.now() - fingerprint.last_rotated).days
# DST changes roughly every 6 months
return days_since_rotation > 180
return False
def _get_base_fonts_for_platform(self, platform: str) -> List[str]:
"""Get base fonts for platform."""
if "Win" in platform:
return ["Arial", "Times New Roman", "Verdana", "Tahoma", "Segoe UI",
"Calibri", "Consolas", "Georgia", "Impact", "Comic Sans MS"]
elif "Mac" in platform:
return ["Arial", "Helvetica", "Times New Roman", "Georgia",
"Verdana", "Monaco", "Courier", "Geneva", "Futura"]
else:
return ["Arial", "Times New Roman", "Liberation Sans", "DejaVu Sans",
"Ubuntu", "Droid Sans", "Noto Sans"]
def _track_rotation(self, fingerprint_id: str, strategy: RotationStrategy) -> None:
"""Track rotation history."""
if fingerprint_id not in self.rotation_history:
self.rotation_history[fingerprint_id] = []
self.rotation_history[fingerprint_id].append({
"timestamp": datetime.now(),
"strategy": strategy.value
})
# Keep only last 100 rotations
self.rotation_history[fingerprint_id] = self.rotation_history[fingerprint_id][-100:]
def _deep_copy_fingerprint(self, fingerprint: BrowserFingerprint) -> BrowserFingerprint:
"""Create a deep copy of a fingerprint."""
# Manual deep copy to ensure all nested objects are copied
return BrowserFingerprint(
fingerprint_id=fingerprint.fingerprint_id,
canvas_noise=CanvasNoise(
noise_level=fingerprint.canvas_noise.noise_level,
seed=fingerprint.canvas_noise.seed,
algorithm=fingerprint.canvas_noise.algorithm
),
webrtc_config=WebRTCConfig(
enabled=fingerprint.webrtc_config.enabled,
ice_servers=fingerprint.webrtc_config.ice_servers.copy(),
local_ip_mask=fingerprint.webrtc_config.local_ip_mask,
disable_webrtc=fingerprint.webrtc_config.disable_webrtc
),
font_list=fingerprint.font_list.copy(),
hardware_config=HardwareConfig(
hardware_concurrency=fingerprint.hardware_config.hardware_concurrency,
device_memory=fingerprint.hardware_config.device_memory,
max_touch_points=fingerprint.hardware_config.max_touch_points,
screen_resolution=fingerprint.hardware_config.screen_resolution,
color_depth=fingerprint.hardware_config.color_depth,
pixel_ratio=fingerprint.hardware_config.pixel_ratio
),
navigator_props=NavigatorProperties(
platform=fingerprint.navigator_props.platform,
vendor=fingerprint.navigator_props.vendor,
vendor_sub=fingerprint.navigator_props.vendor_sub,
product=fingerprint.navigator_props.product,
product_sub=fingerprint.navigator_props.product_sub,
app_name=fingerprint.navigator_props.app_name,
app_version=fingerprint.navigator_props.app_version,
user_agent=fingerprint.navigator_props.user_agent,
language=fingerprint.navigator_props.language,
languages=fingerprint.navigator_props.languages.copy() if fingerprint.navigator_props.languages else [],
online=fingerprint.navigator_props.online,
do_not_track=fingerprint.navigator_props.do_not_track
),
webgl_vendor=fingerprint.webgl_vendor,
webgl_renderer=fingerprint.webgl_renderer,
audio_context_base_latency=fingerprint.audio_context_base_latency,
audio_context_output_latency=fingerprint.audio_context_output_latency,
audio_context_sample_rate=fingerprint.audio_context_sample_rate,
timezone=fingerprint.timezone,
timezone_offset=fingerprint.timezone_offset,
plugins=fingerprint.plugins.copy() if fingerprint.plugins else [],
created_at=fingerprint.created_at,
last_rotated=fingerprint.last_rotated,
static_components=fingerprint.static_components, # This is immutable
rotation_seed=fingerprint.rotation_seed,
account_bound=fingerprint.account_bound,
platform_specific_config=fingerprint.platform_specific_config.copy() if fingerprint.platform_specific_config else {}
)

Datei anzeigen

@ -0,0 +1,245 @@
"""
Fingerprint Validation Service - Validates fingerprint consistency and quality.
"""
import logging
from typing import Dict, List, Tuple, Optional, Any
from datetime import datetime, timedelta
from domain.entities.browser_fingerprint import BrowserFingerprint
logger = logging.getLogger("fingerprint_validation_service")
class FingerprintValidationService:
"""Service for validating fingerprint consistency and quality."""
def validate_fingerprint(self, fingerprint: BrowserFingerprint) -> Tuple[bool, List[str]]:
"""Validate a fingerprint for consistency and realism."""
errors = []
# Hardware consistency
hw_errors = self._validate_hardware_consistency(fingerprint)
errors.extend(hw_errors)
# Platform consistency
platform_errors = self._validate_platform_consistency(fingerprint)
errors.extend(platform_errors)
# WebGL consistency
webgl_errors = self._validate_webgl_consistency(fingerprint)
errors.extend(webgl_errors)
# Canvas consistency
canvas_errors = self._validate_canvas_consistency(fingerprint)
errors.extend(canvas_errors)
# Timezone consistency
tz_errors = self._validate_timezone_consistency(fingerprint)
errors.extend(tz_errors)
# Mobile-specific validation
if self._is_mobile_fingerprint(fingerprint):
mobile_errors = self._validate_mobile_fingerprint(fingerprint)
errors.extend(mobile_errors)
is_valid = len(errors) == 0
return is_valid, errors
def _validate_hardware_consistency(self, fp: BrowserFingerprint) -> List[str]:
"""Validate hardware configuration consistency."""
errors = []
# CPU cores should be power of 2 or common values
valid_cores = [1, 2, 4, 6, 8, 10, 12, 16, 20, 24, 32]
if fp.hardware_config.hardware_concurrency not in valid_cores:
errors.append(f"Unusual CPU core count: {fp.hardware_config.hardware_concurrency}")
# Device memory should be reasonable
valid_memory = [0.5, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 64]
if fp.hardware_config.device_memory not in valid_memory:
errors.append(f"Unusual device memory: {fp.hardware_config.device_memory}GB")
# Screen resolution should be common
width, height = fp.hardware_config.screen_resolution
common_resolutions = [
(1366, 768), (1920, 1080), (2560, 1440), (3840, 2160), # Desktop
(375, 667), (375, 812), (414, 896), (390, 844), # Mobile
(1440, 900), (2560, 1600), (2880, 1800) # Mac
]
if (width, height) not in common_resolutions:
# Check if it's at least a reasonable aspect ratio
aspect_ratio = width / height
if aspect_ratio < 1.2 or aspect_ratio > 2.5:
errors.append(f"Unusual screen resolution: {width}x{height}")
return errors
def _validate_platform_consistency(self, fp: BrowserFingerprint) -> List[str]:
"""Validate platform and navigator consistency."""
errors = []
platform = fp.navigator_props.platform
user_agent = fp.navigator_props.user_agent
# Check platform matches user agent
if "Win" in platform and "Windows" not in user_agent:
errors.append("Platform claims Windows but user agent doesn't")
elif "Mac" in platform and "Mac" not in user_agent:
errors.append("Platform claims Mac but user agent doesn't")
elif "Linux" in platform and "Android" not in user_agent and "Linux" not in user_agent:
errors.append("Platform claims Linux but user agent doesn't match")
# Check vendor consistency
if fp.navigator_props.vendor == "Google Inc." and "Chrome" not in user_agent:
errors.append("Google vendor but not Chrome browser")
elif fp.navigator_props.vendor == "Apple Inc." and "Safari" not in user_agent:
errors.append("Apple vendor but not Safari browser")
return errors
def _validate_webgl_consistency(self, fp: BrowserFingerprint) -> List[str]:
"""Validate WebGL renderer and vendor consistency."""
errors = []
vendor = fp.webgl_vendor
renderer = fp.webgl_renderer
# Common vendor/renderer pairs
if "Intel" in vendor and "Intel" not in renderer:
errors.append("WebGL vendor/renderer mismatch for Intel")
elif "NVIDIA" in vendor and "NVIDIA" not in renderer and "GeForce" not in renderer:
errors.append("WebGL vendor/renderer mismatch for NVIDIA")
elif "AMD" in vendor and "AMD" not in renderer and "Radeon" not in renderer:
errors.append("WebGL vendor/renderer mismatch for AMD")
# Platform-specific WebGL
if "Mac" in fp.navigator_props.platform:
if "ANGLE" in renderer:
errors.append("ANGLE renderer unexpected on Mac")
elif "Win" in fp.navigator_props.platform:
if "ANGLE" not in renderer and "Direct3D" not in renderer:
errors.append("Expected ANGLE or Direct3D renderer on Windows")
return errors
def _validate_canvas_consistency(self, fp: BrowserFingerprint) -> List[str]:
"""Validate canvas noise configuration."""
errors = []
noise_level = fp.canvas_noise.noise_level
if noise_level < 0 or noise_level > 0.1:
errors.append(f"Canvas noise level out of range: {noise_level}")
algorithm = fp.canvas_noise.algorithm
valid_algorithms = ["gaussian", "uniform", "perlin"]
if algorithm not in valid_algorithms:
errors.append(f"Invalid canvas noise algorithm: {algorithm}")
return errors
def _validate_timezone_consistency(self, fp: BrowserFingerprint) -> List[str]:
"""Validate timezone consistency with locale."""
errors = []
timezone = fp.timezone
language = fp.navigator_props.language
# Basic timezone/language consistency
tz_lang_map = {
"Europe/Berlin": ["de", "de-DE"],
"Europe/London": ["en-GB"],
"America/New_York": ["en-US"],
"Asia/Tokyo": ["ja", "ja-JP"]
}
for tz, langs in tz_lang_map.items():
if timezone == tz:
if not any(lang in language for lang in langs):
# It's okay if it's not a perfect match, just log it
logger.debug(f"Timezone {timezone} with language {language} might be unusual")
return errors
def _is_mobile_fingerprint(self, fp: BrowserFingerprint) -> bool:
"""Check if fingerprint is for a mobile device."""
mobile_indicators = ["Android", "iPhone", "iPad", "Mobile", "Tablet"]
platform = fp.navigator_props.platform
user_agent = fp.navigator_props.user_agent
return any(indicator in platform or indicator in user_agent for indicator in mobile_indicators)
def _validate_mobile_fingerprint(self, fp: BrowserFingerprint) -> List[str]:
"""Validate mobile-specific fingerprint attributes."""
errors = []
# Mobile should have touch points
if fp.hardware_config.max_touch_points == 0:
errors.append("Mobile device with no touch points")
# Mobile screen should be portrait or square-ish
width, height = fp.hardware_config.screen_resolution
if width > height:
errors.append("Mobile device with landscape resolution")
# Mobile typically has less memory
if fp.hardware_config.device_memory > 8:
errors.append("Mobile device with unusually high memory")
return errors
def calculate_fingerprint_quality_score(self, fp: BrowserFingerprint) -> float:
"""Calculate a quality score for the fingerprint (0.0 to 1.0)."""
score = 1.0
is_valid, errors = self.validate_fingerprint(fp)
# Deduct points for each error
score -= len(errors) * 0.1
# Bonus points for completeness
if fp.static_components:
score += 0.1
if fp.platform_specific_config:
score += 0.05
if fp.rotation_seed:
score += 0.05
# Ensure score is between 0 and 1
return max(0.0, min(1.0, score))
def assess_fingerprint_risk(self, fp: BrowserFingerprint) -> Dict[str, Any]:
"""Assess the risk level of using this fingerprint."""
risk_factors = []
risk_score = 0.0
# Check age
if fp.created_at:
age_days = (datetime.now() - fp.created_at).days
if age_days > 30:
risk_factors.append("Fingerprint is over 30 days old")
risk_score += 0.2
# Check validation
is_valid, errors = self.validate_fingerprint(fp)
if not is_valid:
risk_factors.extend(errors)
risk_score += len(errors) * 0.1
# Check for common/overused values
if fp.webgl_renderer == "ANGLE (Intel HD Graphics)":
risk_factors.append("Very common WebGL renderer")
risk_score += 0.1
# Determine risk level
if risk_score < 0.3:
risk_level = "low"
elif risk_score < 0.6:
risk_level = "medium"
else:
risk_level = "high"
return {
"risk_level": risk_level,
"risk_score": min(1.0, risk_score),
"risk_factors": risk_factors
}

Datei anzeigen

@ -0,0 +1,160 @@
"""
Timezone Location Service - Manages timezone and location consistency.
"""
import random
from typing import Dict, Optional, Tuple, Any
class TimezoneLocationService:
"""Service for managing timezone and location relationships."""
TIMEZONE_MAPPING = {
"de": {
"timezones": ["Europe/Berlin", "Europe/Munich"],
"offset": -60, # UTC+1
"dst_offset": -120 # UTC+2 during DST
},
"us": {
"timezones": ["America/New_York", "America/Chicago", "America/Los_Angeles", "America/Denver"],
"offset": 300, # UTC-5 (New York)
"dst_offset": 240 # UTC-4 during DST
},
"uk": {
"timezones": ["Europe/London"],
"offset": 0, # UTC
"dst_offset": -60 # UTC+1 during DST
},
"jp": {
"timezones": ["Asia/Tokyo"],
"offset": -540, # UTC+9
"dst_offset": -540 # No DST
},
"au": {
"timezones": ["Australia/Sydney", "Australia/Melbourne"],
"offset": -600, # UTC+10
"dst_offset": -660 # UTC+11 during DST
}
}
CITY_TO_TIMEZONE = {
"Berlin": "Europe/Berlin",
"Munich": "Europe/Munich",
"Frankfurt": "Europe/Berlin",
"Hamburg": "Europe/Berlin",
"New York": "America/New_York",
"Los Angeles": "America/Los_Angeles",
"Chicago": "America/Chicago",
"London": "Europe/London",
"Tokyo": "Asia/Tokyo",
"Sydney": "Australia/Sydney"
}
LANGUAGE_TO_LOCATION = {
"de-DE": ["de", "at", "ch"],
"en-US": ["us"],
"en-GB": ["uk"],
"ja-JP": ["jp"],
"en-AU": ["au"],
"fr-FR": ["fr"],
"es-ES": ["es"],
"it-IT": ["it"]
}
def get_timezone_for_location(self, location: Optional[str] = None) -> Tuple[str, int]:
"""Get timezone and offset for a location."""
if not location:
location = random.choice(list(self.TIMEZONE_MAPPING.keys()))
location_lower = location.lower()
# Check if it's a city
for city, tz in self.CITY_TO_TIMEZONE.items():
if city.lower() in location_lower:
# Find the offset from the mapping
for country, data in self.TIMEZONE_MAPPING.items():
if tz in data["timezones"]:
return tz, data["offset"]
return tz, 0
# Check if it's a country code
if location_lower in self.TIMEZONE_MAPPING:
data = self.TIMEZONE_MAPPING[location_lower]
timezone = random.choice(data["timezones"])
return timezone, data["offset"]
# Default to Berlin
return "Europe/Berlin", -60
def get_location_for_language(self, language: str) -> str:
"""Get a suitable location for a language."""
if language in self.LANGUAGE_TO_LOCATION:
locations = self.LANGUAGE_TO_LOCATION[language]
return random.choice(locations)
# Extract base language
base_lang = language.split('-')[0]
for lang, locations in self.LANGUAGE_TO_LOCATION.items():
if lang.startswith(base_lang):
return random.choice(locations)
# Default
return "us"
def validate_timezone_consistency(self, timezone: str, language: str) -> bool:
"""Validate if timezone is consistent with language."""
expected_location = self.get_location_for_language(language)
# Get timezones for the expected location
if expected_location in self.TIMEZONE_MAPPING:
expected_timezones = self.TIMEZONE_MAPPING[expected_location]["timezones"]
return timezone in expected_timezones
# If we can't determine, assume it's valid
return True
def get_locale_for_timezone(self, timezone: str) -> str:
"""Get appropriate locale for a timezone."""
tz_to_locale = {
"Europe/Berlin": "de-DE",
"Europe/Munich": "de-DE",
"America/New_York": "en-US",
"America/Los_Angeles": "en-US",
"America/Chicago": "en-US",
"Europe/London": "en-GB",
"Asia/Tokyo": "ja-JP",
"Australia/Sydney": "en-AU"
}
return tz_to_locale.get(timezone, "en-US")
def calculate_timezone_offset(self, timezone: str, is_dst: bool = False) -> int:
"""Calculate timezone offset in minutes from UTC."""
# Find the timezone in our mapping
for country, data in self.TIMEZONE_MAPPING.items():
if timezone in data["timezones"]:
return data["dst_offset"] if is_dst else data["offset"]
# Default to UTC
return 0
def get_consistent_location_data(self, proxy_location: Optional[str] = None) -> Dict[str, Any]:
"""Get consistent location data including timezone, locale, and language."""
timezone, offset = self.get_timezone_for_location(proxy_location)
locale = self.get_locale_for_timezone(timezone)
# Extract language from locale
language = locale.split('-')[0]
languages = [locale, language]
# Add fallback languages
if language != "en":
languages.extend(["en-US", "en"])
return {
"timezone": timezone,
"timezone_offset": offset,
"locale": locale,
"language": language,
"languages": languages
}