Назад к блогу
Анти-детекция

Performance Timing Fingerprinting: How Hardware Speed Tracks You

Performance.now() precision, hardware concurrency, and timing patterns reveal your device. Learn how timing-based fingerprinting works.

Introduction

Timing-based fingerprinting exploits the fact that different hardware executes operations at different speeds. By measuring the time taken for specific computations, scripts can infer CPU speed, core count, and even distinguish between hardware generations. Combined with performance.now() precision and SharedArrayBuffer timing, these techniques create a hardware-specific timing profile.

How Timing Fingerprinting Works

performance.now() Precision

The performance.now() API provides high-resolution timestamps:

const start = performance.now();
// Perform some operation
const end = performance.now();
const duration = end - start; // Microsecond precision

Browser vendors have reduced the precision of performance.now() to mitigate Spectre-type attacks:

BrowserPrecision
Chrome5 microseconds (with Site Isolation)
Firefox1 millisecond (reduced from 5us)
Safari100 microseconds

The precision level itself is a browser fingerprinting signal.

Computation Timing

By timing standardized operations, scripts can profile CPU speed:

function benchmarkCPU() {
  const start = performance.now();

  // Standardized workload
  let sum = 0;
  for (let i = 0; i < 1000000; i++) {
    sum += Math.sqrt(i) * Math.sin(i);
  }

  return performance.now() - start;
}

Different CPUs produce measurably different execution times for the same workload.

Worker Thread Timing

Using Web Workers for parallel timing reveals the actual core count:

async function measureCores() {
  const timings = [];

  for (let workers = 1; workers <= 16; workers++) {
    const start = performance.now();
    const promises = [];

    for (let i = 0; i < workers; i++) {
      promises.push(new Promise(resolve => {
        const worker = new Worker('benchmark-worker.js');
        worker.onmessage = () => resolve();
        worker.postMessage('start');
      }));
    }

    await Promise.all(promises);
    timings.push({
      workers,
      time: performance.now() - start,
    });
  }

  // Timing increases non-linearly when workers > physical cores
  return timings;
}

When the number of workers exceeds the physical core count, execution time increases sharply. This reveals the actual core count even if navigator.hardwareConcurrency is spoofed.

SharedArrayBuffer Timing

SharedArrayBuffer enables shared memory between workers, providing a high-precision timing side channel:

const sab = new SharedArrayBuffer(4);
const arr = new Int32Array(sab);

// Worker increments the counter continuously
// Main thread reads it to measure precise time intervals

This technique can achieve sub-microsecond timing precision, bypassing performance.now() restrictions.

Detection Implications

Timing fingerprinting is used to:

  1. Verify hardware claims - Does the CPU speed match the claimed hardwareConcurrency?
  2. Detect virtual machines - VMs often have timing anomalies
  3. Identify headless mode - Some headless configurations produce different timing profiles
  4. Correlate sessions - Consistent timing patterns link sessions to the same hardware

How BotCloud Handles Timing

BotCloud manages timing-based fingerprinting through:

  • Performance.now() calibration aligned with the profile's claimed hardware
  • Consistent timing patterns across sessions with the same profile
  • Worker timing that does not reveal the actual server hardware
  • Timer precision matching normal browser behavior

Best Practices

  1. Match performance characteristics to claimed hardware - An 8-core profile should show 8-core parallelism
  2. Avoid impossibly fast or slow timing - Timing should fall within normal ranges for the claimed CPU
  3. Verify SharedArrayBuffer behavior - If available, its timing should be consistent with performance.now()
  4. Test with timing-based detection tools to verify consistency
#performance#timing#hardware#fingerprinting