Các flag fingerprint
Danh tính được điều khiển bằng các switch dòng lệnh của Chromium. SDK tự đặt các switch này từ những tùy chọn có tên tương ứng; với bản build mở, bạn cũng có thể truyền chúng dưới dạng args cho launcher của riêng mình.
Hầu hết các switch đều có trong cả hai bản build. Một số chỉ có trên bản build có giấy phép (Miễn phí với GitHub hoặc Pro) — danh sách switch bên dưới đánh dấu những switch này kèm revision engine đã bổ sung chúng. Nếu không có khóa giấy phép, SDK sẽ chạy bản build mở, nơi các tùy chọn đó không có tác dụng.
Mô hình seed
Mọi thứ đều xoay quanh --fingerprint=<seed>. Seed (một số nguyên hoặc một chuỗi bất kỳ) sinh ra persona một cách tất định — đồng thời làm nhiễu hai bề mặt render được liệt kê trong mục những gì được farble bên dưới:
- Cùng seed ⇒ cùng danh tính qua mọi lần khởi chạy — như một khách truy cập quay lại.
- Seed mới ⇒ một danh tính mới và hợp lý.
- Nhiễu canvas và WebGL được tính theo từng site (lấy cảm hứng từ cơ chế farbling của Brave), nên các hash này khác nhau giữa các domain. Bản thân persona — GPU, màn hình, phần cứng, font — thì giống nhau ở mọi nơi, đúng như một máy thật.
Muốn dùng danh tính của một máy thật thay cho danh tính tổng hợp sinh từ seed? Hãy dùng collector để thu thập từ một Chrome mẫu (donor) rồi import qua --fingerprint-profile=<gzip+base64 JSON> (hoặc tùy chọn fingerprint_profile / fingerprintProfile của SDK, vốn tự làm bước gzip+base64 cho bạn). Các trường có trong profile sẽ ghi đè persona; trường nào thiếu sẽ lấy theo persona của seed --fingerprint, hoặc theo giá trị mặc định của trình duyệt nếu không có seed. Cả hai bản build đều đọc được profile đã import; trên bản build có giấy phép, profile được áp dụng đầy đủ từ 151 r19. Xem hướng dẫn Playwright để biết cách import một profile đã thu thập từ SDK.
Những gì được farble — và những gì cố ý không
Làm nhiễu một bề mặt đổi lấy khả năng không bị liên kết (unlinkability) nhưng phải trả giá bằng tính nhất quán, và sự đánh đổi đó không phải chỗ nào cũng đáng. Clearcote chỉ farble hai bề mặt mà ở đó nhiễu gần nhất với sự khác biệt phần cứng thông thường, còn lại thì để nguyên không làm nhiễu — vì trên những bề mặt ấy, một giá trị bị làm nhiễu còn gây chú ý hơn một giá trị dùng chung. Tuy vậy, không làm nhiễu không có nghĩa là không động đến: một vài giá trị vẫn bị thay thế hoàn toàn bằng giá trị của persona, như bảng và ghi chú bên dưới trình bày rõ. Một số công cụ kiểm tra vẫn có thể gắn cờ chính lớp nhiễu; khi điều đó quan trọng, fingerprintNoise: false sẽ tắt nhiễu nhưng vẫn giữ danh tính (xem Cơ chế phát hiện hoạt động ra sao).
| Bề mặt | Nhiễu theo seed | Lý do |
|---|---|---|
Canvas 2D — toDataURL, getImageData và các cách khác để một trang xuất nội dung canvas (trên bản build có giấy phép, mọi đường xuất đều cho kết quả khớp nhau từ 152 r21) | Có | Kết quả readback vốn đã khác nhau giữa các máy thật tùy theo GPU, driver và cách rasterize, nên một chút nhiễu nhỏ vẫn nằm trong biên độ dao động tự nhiên. |
WebGL — readPixels, toDataURL | Có | Cùng lý do như trên. Với disableGpuFingerprint, readPixels trả về pixel thật, nhưng các kết quả xuất của một canvas WebGL (toDataURL() và tương tự) vẫn bị thêm nhiễu — hãy dùng kèm fingerprintNoise: false để mọi lần đọc canvas đều khớp nhau. |
Audio — giá trị sample của AudioContext | Không — có chủ đích | Đầu ra Web Audio là tất định với một pipeline cho trước. Làm nhiễu nó nghĩa là hoặc dùng sample rate sai chuẩn (44099.99 thay vì 44100), hoặc tạo ra những sample mà không renderer nguyên bản nào sinh ra — cả hai đều là giá trị mà một trình duyệt thật không thể phát ra, tức là dấu hiệu lộ còn rõ hơn việc hai profile có chung một hash audio. Các scalar được báo cáo là chuyện khác, và chúng có đi theo persona — xem bên dưới. |
| Client rects & số đo văn bản (text metrics) | Không — có chủ đích | Chrome lượng tử hóa layout lên một lưới gốc 1/512 px, nên mọi rect thật đều là bội số chính xác của 0.001953125. Một hệ số tỷ lệ áp dụng sau đó sẽ đẩy mọi rect lệch khỏi lưới này, và hình học lệch lưới có thể bị nhận ra chỉ từ một lần đo, dù hệ số nhỏ đến đâu. |
| WebGPU — adapter và limits | Không | Đi theo GPU của persona chứ không theo seed, nên luôn khớp với những gì WebGL báo cáo. Hai API GPU trên cùng một trang mà nêu hai vendor khác nhau thì chỉ cần một lệnh gọi là đọc ra được. |
Hệ quả thực tế khi làm việc với nhiều tài khoản: canvas và WebGL tách biệt các danh tính của bạn; audio đã render và client rects thì không. Đó là thuộc tính của chính cỗ máy bên dưới — cùng một máy host sẽ render cùng một audio pipeline và cùng một lưới layout bất kể profile nào đang được nạp, y hệt như với hai người khác nhau dùng phần cứng giống hệt nhau. Nếu trang đích dựa đúng vào những giá trị này để đối chiếu, giải pháp là dùng các máy riêng biệt, chứ không phải một switch khác.
Có một ngoại lệ nên biết: ba scalar của AudioContext thì có đi theo persona. sampleRate, baseLatency và outputLatency được lấy từ persona bất cứ khi nào có persona đang hoạt động — từ seed --fingerprint hoặc từ profile đã import — nên chúng thay đổi theo từng danh tính thay vì để lộ audio backend của máy host. Khi không đặt cái nào, cả ba giá trị đều lấy thẳng từ máy thật. Sample rate mà trang yêu cầu tường minh vẫn được tôn trọng, và outputLatency vẫn đi qua bộ lượng tử hóa phụ thuộc quyền (permission) của chính Chrome. Từ 153 r26, baseLatency còn bám theo kích thước render mà trang yêu cầu, và một context chưa bắt đầu render sẽ báo cùng mức latency như một Chrome thông thường.
Các bề mặt phụ nhất quán
Ngoài các tín hiệu được thêm nhiễu, engine còn giữ cho các bề mặt phụ của persona khớp với một Chrome thật trên nền tảng của persona đó. Theo mặc định, SDK đặt nền tảng theo hệ điều hành của máy host; các giá trị riêng cho Windows dưới đây áp dụng cho persona Windows.
- WebGL limits (WebGL1 + WebGL2) đi theo GPU của persona ở mọi chỗ mà máy này đáp ứng được. Một limit không bao giờ được báo cao hơn mức driver thật áp đặt, nên trên renderer phần mềm, một số giá trị sẽ thấp hơn của GPU được nêu tên.
- UNMASKED_RENDERER / UNMASKED_VENDOR cố định trong suốt phiên — một GPU duy nhất trên mọi site, theo persona, thay vì một dấu hiệu lộ khác nhau theo từng origin.
- navigator.getBattery() báo một máy desktop đang cắm điện (đang sạc, level 1.0, không xả pin), còn navigator.connection báo một profile mạng dân cư (effectiveType 4g, rtt/downlink được làm tròn, saveData tắt).
- navigator.keyboard.getLayoutMap() trả về bố cục US-QWERTY, và trên persona Windows, AudioContext báo sample rate và latency của audio stack trên Windows.
- window.getScreenDetails() báo một màn hình duy nhất và nhất quán;
@media (pointer: fine)/(hover: hover)khớp với một máy desktop dùng chuột. - URL — dưới persona Windows,
new URL("C:/").protocoltrả vềfile:, và từ 153 r26, mọi cách mà một trang có thể tạo URL đều cho cùng một kết quả. navigator.share / canShare có mặt để khớp với UA Windows. - WebGPU — adapter info + limits/features của
navigator.gpubám theo cùng GPU với WebGL (trên bản build có giấy phép, được thu hẹp trong phạm vi máy host đáp ứng được). - Locale —
Accept-Language,navigator.language, toàn bộ danh sáchnavigator.languagesvàIntl(main thread + worker) đều lấy từ cùng một danh sách (acceptLanguagehoặcgeoip). SDK tự đặt--accept-langvà--langcho bạn; nếu dùng switch thô, hãy truyền cả hai. - Giọng nói —
speechSynthesistrả về bộ giọng đọc của persona, gồm cả các giọng Google qua mạng mà Chrome phiên bản đó liệt kê (153 r27).fingerprintVoices: falsesẽ giữ lại giọng đọc sẵn có của máy (152 r22). - Phần cứng — chỉ số tốc độ bộ xử lý (
navigator.cpuPerformance) đi theo persona chứ không theo bộ xử lý thật (152 r20), và giới hạn bộ nhớ cho script (performance.memory.jsHeapSizeLimit) đi theo dung lượng bộ nhớ mà persona khai báo (153 r26). - Codec & thiết bị —
MediaCapabilities.decodingInfo()báo một ma trận codec theo persona, cònenumerateDevices()báo một bộ thiết bị media theo persona (id ổn định theo seed, label để trống khi chưa cấp quyền). - UA-CH high-entropy —
bitness=64 /wow64=false /model; trên persona Linux,platformVersionđể trống, đúng như Chrome thật trên Linux báo (153 r26).navigator.storage.estimate()báo một quota ổ đĩa sát thực tế khi bạn đặtstorageQuota.
Thử nghiệm: canvas bridge với GPU thật
Bạn có thể tùy chọn chuyển tiếp các thao tác canvas / WebGL sang một máy từ xa có GPU thật, để các kết quả readback (getImageData / toDataURL / readPixels / measureText) nhất quán với GPU mà bạn thể hiện — kể cả trên phần cứng không tự render được GPU đó. Vì bridge chuyển tiếp các thao tác (chứ không phải ảnh ghi sẵn), nó xử lý được hầu hết canvas, không chỉ các probe đã biết. Bật bằng --canvas-bridge-url=ws://host:port (kèm --no-sandbox); nếu không đặt thì mọi thứ chạy hoàn toàn cục bộ, y như trước. Render server điều khiển một trình duyệt trên máy có GPU thật (một Chrome thông thường qua CDP, hoặc một Clearcote headless). Xem hướng dẫn canvas-bridge để biết cách thiết lập đầy đủ.
Ghi đè metadata ở cấp native & light_stealth
Bên cạnh persona sinh từ seed, một nhóm override đơn giá trị ở cấp native cho phép bạn spoof trực tiếp từng giá trị navigator/screen riêng lẻ — hardwareConcurrency, deviceMemory, colorDepth, devicePixelRatio, maxTouchPoints, và (phải tự bật) kích thước screen/avail*. Mỗi giá trị được getter của nó đọc thẳng theo thứ tự ưu tiên flag > persona --fingerprint > máy host thật, nên một giá trị hardcode luôn thắng mọi seed, và các override hoạt động dù có hay không có seed --fingerprint — chúng không bao giờ kích hoạt cơ chế persona.
SDK gói một tập con gọn nhẹ sau một flag duy nhất, light_stealth: nó áp dụng một trong số ít bộ metadata nhất quán (hardwareConcurrency, deviceMemory, colorDepth, devicePixelRatio, maxTouchPoints) chỉ thông qua các switch native nói trên. Seed chỉ dùng để chọn bộ nào và không được truyền xuống engine, nên không có nhiễu canvas hay WebGL, và mọi phiên light_stealth trên cùng một máy đều có chung hash canvas của máy host — hãy dùng seed fingerprint riêng cho từng tài khoản mà không bật lightStealth khi cần các hash đó khác nhau. TLS ClientHello và phiên bản thật của trình duyệt không thay đổi. Kích thước screen cố ý không bị spoof theo mặc định (bật qua screenWidth v.v.), vì một màn hình giả không khớp được với bề mặt render thật thì rất dễ bị nhận ra; trên màn hình không scale, hãy kiểm tra xem devicePixelRatio được chọn có phù hợp không, hoặc tự đặt giá trị này. Tùy chọn đặt tường minh luôn thắng preset.
import { launch } from "clearcote";
// one coherent metadata bundle; "my-seed" only picks which one — no seed reaches the engine, so no canvas noise:
const browser = await launch({ lightStealth: true, fingerprint: "my-seed" });
// or set individual values by hand (no --fingerprint needed):
const b2 = await launch({
hardwareConcurrency: 8,
deviceMemory: 8,
devicePixelRatio: 1.25,
maxTouchPoints: 0,
});Tất cả switch
Lọc theo tên hoặc tác dụng, hoặc thu hẹp vào một subsystem. Nhấp vào một switch để xem nó làm gì và dạng hợp lệ ngắn nhất của nó.
Seed chính (số nguyên hoặc chuỗi). Sinh ra toàn bộ persona (GPU, màn hình, phần cứng, font) và điều khiển nhiễu canvas, WebGL theo từng trang. Ngôn ngữ và múi giờ không sinh từ seed: chúng lấy từ --accept-lang / --timezone (SDK: acceptLanguage, timezone hoặc geoip). Audio đã render và client rect không bị làm nhiễu; các giá trị scalar sampleRate/baseLatency/outputLatency của AudioContext được lấy từ persona khi có persona — xem tài liệu fingerprint. Cùng seed ⇒ cùng danh tính.
Ví dụ
Đây là các bộ flag dòng lệnh thô. Để xem workflow Python và Node hoàn chỉnh, hãy xem Ví dụ.
Một danh tính Windows đầy đủ và nhất quán:
--fingerprint=acme-tenant-7 \
--fingerprint-platform=windows \
--fingerprint-brand=Chrome \
--accept-lang=en-US,en \
--lang=en-US \
--timezone=America/New_York \
--fingerprint-hardware-concurrency=8Một danh tính Windows với chuỗi GPU tùy chỉnh, theo đúng định dạng mà ANGLE trên Windows báo cáo:
--fingerprint=42 \
--fingerprint-platform=windows \
--fingerprint-gpu-vendor="Google Inc. (NVIDIA)" \
--fingerprint-gpu-renderer="ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 (0x00002504) Direct3D11 vs_5_0 ps_5_0, D3D11)"Các chuỗi này thay đổi những gì được báo cáo, không thay đổi thứ thực sự vẽ ra: pixel vẫn đến từ GPU của máy này. Hãy khai báo đúng GPU mà bạn thực sự render, hoặc dùng canvas bridge. Nền tảng macos thay đổi các chuỗi danh tính (UA, client hints, navigator.platform) nhưng phía sau không có mô hình máy macOS nào, còn android là một persona di động theo kiểu best-effort.
Cố định vị trí cùng với múi giờ tương ứng để geolocation và đồng hồ khớp nhau:
--fingerprint=nyc-1 \
--timezone=America/New_York \
--fingerprint-location=40.7128,-74.0060Import giá trị từ một máy thật
Thay vì seed tổng hợp, bạn có thể báo chính xác các giá trị mà một Chrome thật đã báo — chuỗi GPU + bảng getParameter, màn hình, font, giọng đọc, audio. Các giá trị được thay thế; việc render vẫn do chính máy của bạn thực hiện, nên một profile không làm cho hai tài khoản có canvas khác nhau — seed --fingerprint riêng cho từng tài khoản mới làm được điều đó. Lấy một profile từ thư viện clearcote-profiles đã được tuyển chọn (hàng nghìn profile từ máy thật, gắn tag theo vendor GPU), hoặc tự thu thập bằng collector, rồi nạp vào và kiểm chứng rằng nó đã được nạp:
import { launch } from "clearcote";
// path to a captured .json profile, a profile object, or a JSON string
const browser = await launch({ fingerprintProfile: "./real-machine.json" });
// fields present in the profile override the persona;
// absent fields fall back to the --fingerprint seed's persona, or the browser's defaults with no seed.Quy trình thu thập: mở trang collector trong một Chrome thật mà bạn muốn nhân bản, export file JSON, rồi truyền nó qua fingerprintProfile (SDK) / --fingerprint-profile (engine). Kiểm tra bằng tools/fingerprint-collect/verify_profile.py.
Mỗi patch làm gì
Mỗi switch của bản build mở tương ứng với một patch engine đọc được: các diff nằm trong patches/, kèm tóm tắt một dòng cho từng patch trong patches/README.md. Các switch được đánh dấu “bản build có giấy phép” đến từ bộ patch riêng của bản build có giấy phép, vốn không công khai.
Tính nhất quán quan trọng hơn bất kỳ giá trị đơn lẻ nào: hãy giữ cho nền tảng, múi giờ, locale và GPU hợp lý khi đặt cạnh nhau. Xem Kiến trúc để biết engine giữ chúng nhất quán như thế nào, và Cơ chế phát hiện hoạt động ra sao để hiểu vì sao cách này hiệu quả hơn spoof bằng JavaScript.
Bài đọc liên quan