1 points par GN⁺ 4 시간 전 | 1 commentaires | Partager sur WhatsApp
  • À partir de Chrome 148, le Math.tanh de V8 appelle le std::tanh de l’hôte au lieu du fdlibm intégré, si bien que pour une même entrée, la glibc de Linux, le libsystem_m de macOS et l’UCRT de Windows renvoient un dernier bit différent
  • Math.tanh(0.8) vaut 0.6640367702678491 sur Linux, 0.664036770267849 sur macOS et 0.6640367702678489 sur Windows, ce qui permet de distinguer les trois OS en un seul appel ; si le résultat ne correspond pas à l’OS revendiqué par le User-Agent, le camouflage est démasqué
  • Les chemins de fuite diffèrent selon les moteurs : dans les Math.* de V8, seul tanh utilise la bibliothèque mathématique de l’hôte, mais dans Blink, l’ensemble des fonctions trigonométriques CSS ainsi que certains calculs de Web Audio passent aussi par des bibliothèques dépendantes de l’OS
  • Introduire des variations arbitraires dans la valeur ne correspond à aucun OS réel et casse même le déterminisme ; il faut donc reproduire au bit près les coefficients, tables, réductions de domaine et comportements FMA de la bibliothèque visée, ou mapper directement le code UCRT d’origine
  • À chaque release, Scrapfly vérifie la correspondance bit à bit de Math.tanh et de 7 fonctions trigonométriques CSS sur 871 000 entrées en les comparant à de vrais Mac et Chrome, et aligne non seulement la précision mais aussi les différences d’architecture et les temps d’exécution sur ceux d’un vrai navigateur

L’OS révélé par Math.tanh

  • Le résultat de Math.tanh(0.8) varie selon la bibliothèque mathématique de l’hôte
    • glibc de Chrome sur Linux : 0.6640367702678491
    • libsystem_m de Chrome sur macOS : 0.664036770267849
    • UCRT de Chrome sur Windows : 0.6640367702678489
  • Apple et glibc présentent en général un écart de 1 ULP sur environ un quart de toutes les entrées, tandis que l’UCRT de Windows diffère des deux autres bibliothèques sur quelques pourcents des entrées
    • ULP (unit in the last place) est l’écart entre deux nombres à virgule flottante consécutifs représentables à une grandeur donnée ; 1 ULP est la plus petite différence qu’un double peut représenter
  • Mesuré sur un vrai Chrome 150 via le DevTools Protocol sous Linux, macOS 26 sur Apple Silicon et Windows 11, le pouvoir discriminant varie selon l’entrée
    • tanh(0.5) donne 0.46211715726000974 sur les trois OS, donc inutile pour la détection
    • tanh(0.7) ne diffère que sur Linux, d’1 ULP
    • tanh(0.8) diffère sur les trois OS, avec une plage totale de 2 ULP
    • tanh(0.9) ne diffère que sur Windows, d’1 ULP
  • Pour environ trois quarts des entrées, les trois OS produisent le même résultat, mais une seule entrée bien choisie suffit pour obtenir une signature par OS
  • Si un navigateur prétend être sur macOS tout en renvoyant les bits mathématiques de Linux, le résultat de Math.tanh contredit le User-Agent

Le changement introduit dans Chrome 148

  • Jusqu’à Chrome 147, V8 embarquait un port de fdlibm, une implémentation mathématique portable, pour calculer Math.tanh, ce qui renvoyait les mêmes bits sur tous les OS
  • Le commit V8 c1486295ae5 a remplacé l’implémentation embarquée par le std::tanh de la plateforme
    • Ce changement est apparu pour la première fois dans V8 14.8.57 et Chrome 148
    • Chrome 148, 149 et 150 exposent les différences du libm hôte, alors que Chrome 147 et antérieurs ne divulguent pas l’OS par ce chemin
  • La norme IEEE 754 définit le stockage des double, mais n’impose pas que les fonctions transcendantes comme sin, cos, tanh ou exp soient correctement arrondies dans tous les cas
  • Chaque bibliothèque mathématique (libm) de système fait un compromis entre performance et erreur en ULP, et utilise des coefficients de polynômes minimax, des tables de lookup et des constantes de réduction de domaine différents
    • Linux utilise glibc
    • macOS utilise le libsystem_m d’Apple
    • Windows utilise ucrtbase.dll de l’UCRT
  • Un détecteur n’a pas besoin d’analyser l’opération mathématique elle-même : il peut simplement comparer les résultats par entrée avec une table issue de vrais Chrome

Quatre pièges qui rendent la reproduction difficile

  • Seules certaines fonctions de V8 fuient

    • V8 lie statiquement la plupart de ses implémentations mathématiques, ce qui produit les mêmes résultats quel que soit l’OS
    • Math.exp, Math.pow, Math.atan et d’autres utilisent des implémentations llvm-libc embarquées
    • Math.sin et Math.cos utilisent des routines dbl-64 embarquées dérivées de glibc
    • Depuis Chrome 148, seul Math.tanh, parmi les Math.*, divulgue l’OS via le std::tanh de la plateforme
    • Si l’on falsifie aussi des fonctions qui ne fuient pas pour les faire ressembler à l’OS cible, on ne reproduit plus le vrai graphe d’appels de V8 ; le fait que seul tanh diffère est lui-même vérifiable
  • JavaScript et CSS empruntent des chemins différents

    • Les fonctions CSS sin(), cos(), atan2() ne partagent pas le code de Math.sin en JavaScript
    • Le moteur de rendu Blink réduit d’abord les angles en degrés, puis appelle le std::sin de la plateforme, etc., sur la valeur réduite
    • Le résultat diffère d’un calcul direct avec une entrée en radians, et les 7 fonctions trigonométriques CSS divulguent l’OS via le libm hôte
    • Une reproduction bit à bit doit inclure non seulement la fonction mathématique finale, mais aussi la réduction de domaine en degrés et la conversion radians/degrés
  • Il existe aussi deux bibliothèques distinctes à l’intérieur de macOS

    • Sur Apple Silicon, on trouve à la fois le libsystem_m scalaire et les routines vectorielles Accelerate comme vvsin et vvtanh, et ces deux implémentations ne sont pas identiques
    • Sur 1 million d’entrées, selon la fonction, 10 à 89 % des résultats différaient
    • cos(0) vaut exactement 1.0 dans l’implémentation scalaire
    • Dans Accelerate, il renvoie 0.9999999999999999
    • En mesurant le vrai Chrome sur Mac via le protocole de débogage, on distingue quelle bibliothèque est appelée à chaque point d’appel
    • Math.tanh, les fonctions trigonométriques CSS et les fonctions transcendantes échantillon par échantillon du compresseur audio utilisent le libsystem_m scalaire
    • Le DSP Web Audio, la FFT, les maths vectorielles et les filtres biquad sur Mac utilisent Accelerate
    • Les chemins Chromium concernés incluent fft_frame_mac.cc, vector_math_mac.h, biquad.cc et BUILDFLAG(IS_MAC)
    • Choisir la mauvaise bibliothèque Apple selon le point d’appel peut introduire un écart de 1 ULP sur la majorité des entrées
  • L’architecture CPU influe aussi sur le résultat

    • ARM et x86 diffèrent sur le fused multiply-add (FMA) et sur la propagation du signe des NaN
    • Même si la procédure mathématique est correcte, si le compilateur fusionne multiplication et addition sur une seule architecture, les bits du résultat changent

Chemins de fuite selon le moteur et la fonctionnalité

  • Les Math.* JavaScript de V8 utilisent presque tous des implémentations embarquées, et seul Math.tanh se connecte au libm hôte
    • sin, cos, tan, asin, acos, atan, atan2, exp, log, log2, log10, pow utilisent des implémentations embarquées de V8
    • sqrt, abs et les quatre opérations arithmétiques sont des opérations matérielles
  • Les fonctions mathématiques de calc() en CSS appellent directement la bibliothèque de plateforme depuis Blink
    • sin, cos, tan, asin, acos, atan, atan2, exp, log, log2, log10, pow utilisent le libm hôte
    • Il n’existe pas de chemin CSS équivalent pour tanh
  • Web Audio mélange plusieurs implémentations selon le point d’appel
    • Sur Mac, la FFT d’oscillateur, les additions/multiplications/mises à l’échelle vectorielles et la FFT utilisent vDSP d’Accelerate
    • Les fonctions transcendantes échantillon par échantillon de DynamicsCompressor, comme sin, exp, log10f, powf, utilisent le libsystem_m scalaire
    • Un même graphe audio peut ainsi s’étendre sur trois bibliothèques : les maths embarquées de V8, la bibliothèque scalaire et Accelerate
  • WebAssembly ne possède pas d’instruction pour les fonctions transcendantes
    • Le résultat de sin, etc., dépend donc du libm embarqué dans le module
    • Les opérations arithmétiques comme f64.sqrt et f64.mul s’exécutent sur le matériel et restent identiques selon l’OS
    • Il reste comme axe d’empreinte la normalisation des NaN entre ARM et x86, ainsi que certaines différences d’arrondi SIMD
  • Les signaux de détection se concentrent sur Math.tanh, toutes les fonctions trigonométriques CSS et Web Audio
    • La FFT Accelerate de Web Audio révèle l’architecture CPU
    • Le libsystem_m scalaire du compresseur révèle l’OS

Reproduire exactement plutôt que perturber les valeurs

  • Pourquoi le bruit échoue

    • Ajouter du bruit au résultat peut aboutir à une valeur qui ne correspond à aucun OS réel dans la table de référence
    • Si la valeur aléatoire change à chaque appel, le déterminisme est rompu, ce qui constitue en soi un autre signal de détection
    • L’objectif n’est pas d’obtenir une valeur proche, mais le même résultat bit à bit que celui renvoyé par l’OS revendiqué
  • Restaurer tous les éléments de l’algorithme cible

    • Les coefficients d’approximation minimax, les tables d’exposants et les constantes de réduction de domaine du libm cible sont récupérés puis portés dans un code C portable
    • Il faut aussi reproduire à l’identique les entrées pour lesquelles la bibliothèque cible arrondit dans la « mauvaise » direction
    • La reproduction de sin chez Apple utilise le motif exact de bits des coefficients extraits de libsystem_m ainsi que des appels explicites à fma()
    • Si l’on retranscrit les coefficients en décimal, ils peuvent être réarrondis pendant la transcription ; ils sont donc conservés en flottants hexadécimaux
    • Chaque multiplication-addition fusionnée par Apple est explicitement fusionnée dans le code également
  • Verrouiller le FMA de manière déterministe

    • La compilation avec -ffp-contract=off empêche le compilateur d’ajouter ou de supprimer arbitrairement des FMA
    • Seuls les fma() explicitement présents dans le code s’exécutent aux mêmes endroits que chez Apple, ce qui permet d’obtenir les mêmes bits sur un serveur x86 tout en imitant ARM
    • Le FMA matériel et un FMA logiciel correctement arrondi renvoient les mêmes bits

Utiliser le code d’origine de l’UCRT de Windows

  • L’UCRT de Windows utilise la même ISA x86-64 que les serveurs Linux et est indépendant de la position, ce qui permet de mapper le vrai ucrtbase.dll en mémoire à l’exécution et d’appeler directement les exports des fonctions mathématiques
  • Comme c’est le code d’origine qui s’exécute, on obtient les vrais bits UCRT sans avoir à rétroconcevoir un algorithme mathématique séparé
  • Il faut gérer les différences entre l’ABI System V de Linux et l’ABI x64 de Windows
    • En x64 Windows, l’appelé utilise un shadow space de 32 octets au-dessus de l’adresse de retour
    • L’ensemble des registres préservés par l’appelé diffère aussi de System V
    • Si le pointeur de fonction n’est pas déclaré avec ms_abi, l’écriture dans le shadow space peut corrompre la stack frame générée par clang et faire sauter l’appel indirect vers une mauvaise adresse
  • Le code du DLL mappé n’est pas une cible d’appel indirect enregistrée dans la CFI
    • En production, -fsanitize=cfi-icall peut déclencher un trap #UD et un SIGILL à chaque appel
    • Le wrapper qui appelle le pointeur de fonction a besoin de clang::no_sanitize("cfi-icall")
  • Les fonctions mathématiques de l’UCRT lisent en préambule un drapeau de dispatch CPU via mov eax, [rip+disp32] pour choisir entre un chemin scalaire ou un chemin FMA/AVX2
    • Dans un DLL fraîchement mappé, ce drapeau vaut 0 et sélectionne donc le chemin scalaire plus lent
    • Les bits du résultat sur ce chemin diffèrent de ceux d’un système Windows moderne
    • Il faut localiser l’adresse du drapeau dans le prologue de tanh et la forcer vers le chemin FMA avant le premier appel pour obtenir une correspondance bit à bit avec un vrai Windows

Emplacement du patch et contraintes de performance

  • On hooke le goulot d’étranglement unique où le moteur appelle libm, puis on choisit le chemin selon l’OS revendiqué par le navigateur
    • S’il revendique Linux, on conserve glibc
    • S’il revendique macOS, on utilise l’implémentation de reproduction Apple
  • Même si le résultat est exact, un temps d’exécution différent de celui d’un vrai navigateur peut être détecté
  • La première build utilisait une baseline x86 trop ancienne pour le FMA matériel, abaissant tous les fma() en appels logiciels ; elle était 2,5 à 6 fois plus lente que le natif
  • En comparant le ratio de temps d’exécution entre des boucles Math.tanh et Math.sin, on peut révéler un motif de performance absent d’un vrai navigateur
  • Une fois le FMA matériel activé, chaque opération fusionnée devient une seule instruction, ce qui donne un gain d’environ 6× ; c’était alors plus rapide que glibc tout en conservant des bits identiques

Validation sur 871 000 entrées

  • Le harnais de validation exécute 871 000 entrées à chaque release sur toutes les branches et l’ensemble du domaine
    • grille d’entrées dense
    • frontières d’intervalle
    • nombres subnormaux
    • zéros signés
    • infinis
    • NaN
  • Deux types d’environnements réels servent de référence
    • Un vrai Mac calcule séparément, pour toutes les entrées, les résultats scalaire et Accelerate afin d’identifier les points où les deux implémentations divergent
    • Un vrai Chrome sur Mac est piloté via le protocole de débogage pour collecter les résultats pleine précision de Math.tanh et de toutes les fonctions trigonométriques CSS
  • Math.tanh et les fonctions CSS sin, cos, tan, asin, acos, atan, atan2 correspondent bit à bit à un vrai Chrome sur Mac
  • La validation vérifie aussi que l’implémentation de reproduction se comporte comme le vrai code machine inclus dans le binaire distribué
  • Il faut également reproduire le post-traitement du navigateur aux frontières du domaine
    • Sur un vrai Mac, asin(2) en CSS est hors domaine, devient NaN, puis CSS borne NaN à 0, donc la valeur finale est 0
    • Une implémentation de reproduction simpliste pourrait à tort renvoyer 90 degrés

Pourquoi les maths sont cruciales dans le camouflage de navigateur

  • Les résultats mathématiques sont déterministes et peu coûteux à vérifier, mais les imiter correctement exige de connaître le fonctionnement interne des libm des éditeurs et les chemins d’appel propres à chaque moteur
  • Pour correspondre à un vrai navigateur, il faut déterminer quelle bibliothèque mathématique V8, Blink et Web Audio choisissent à chaque point d’appel, puis reproduire jusqu’au dernier bit, le comportement selon l’architecture et même les temps d’exécution
  • Le Scrapium de Scrapfly est configuré, lorsqu’on lui demande de se présenter comme macOS, pour faire correspondre au trafic réel de macOS jusqu’au bit d’arrondi d’un cosinus

1 commentaires

 
GN⁺ 4 시간 전
Réactions sur Hacker News
  • L’explication selon laquelle un seul appel à tanh avec l’entrée de droite produit une signature propre au système d’exploitation passe à côté de la possibilité d’identifier une plage de versions du navigateur
    La plupart des gens n’usurpent pas le système d’exploitation de leur User-Agent, et le fingerprinting s’intéresse davantage à des combinaisons de caractéristiques quasi uniques qu’au système d’exploitation lui-même. La découverte est intéressante, mais l’article paraît trop rédigé par un LLM, ce qui nuit à sa crédibilité

    • L’entreprise qui a publié cet article cherche en réalité à déguiser des bots sur des VM Linux en machines physiques Windows ou macOS
      Cela leur permet de passer plus facilement la détection de bots et de vendre à leurs clients des données collectées sur d’autres sites web
    • Pour l’instant, cette méthode permet seulement de dire qu’il s’agit de Chromium 148 ou plus, mais en testant en JavaScript ou en CSS les fonctionnalités V8 et Blink ajoutées à chaque version, on peut déterminer avec certitude la version majeure à partir d’environ la version 120
      Le fait que le texte a été écrit avec un LLM a été divulgué dans l’article et sur le blog ; rien n’a été caché et personne n’a prétendu être humain. Faute de temps, l’article n’aurait probablement pas été publié autrement, et ce choix est assumé
    • On peut certes identifier une plage de versions, mais il existe déjà d’innombrables moyens de le faire
      Les navigateurs ajoutent sans cesse des fonctionnalités et corrigent des bugs, et la plupart de ces changements sont détectables en JavaScript
    • Si le contenu est exact, peu importe qui l’a écrit, et la thèse centrale du LLM est elle aussi valable
  • Analyser toutes les techniques de fingerprinting avec l’IA, les publier, puis pousser les navigateurs à les bloquer après la polémique, afin que l’activité de scraping de l’entreprise rapporte plus d’argent : stratégie habile
    Sans ces sociétés, le fingerprinting des navigateurs ne serait probablement pas aussi répandu aujourd’hui, et Internet s’en porterait mieux. Je préfère encore les articles de l’autre camp, comme fingerprint.js, où les intérêts en jeu sont plus explicites

    • Je ne suis pas d’accord : qu’il y ait des scrapers ou non, le fingerprinting est nécessaire pour suivre des humains, donc il finirait de toute façon par être utilisé
  • Voilà une raison de plus de promouvoir des fonctions transcendantes correctement arrondies
    J’ai récemment appris que ce problème est pratiquement résolu. Voir le deuxième keynote sur https://arith2026.org/program.html

    • Des fonctions libm correctement arrondies, c’est excellent, mais il ne faut pas qu’elles aient des performances catastrophiques au pire cas, comme autrefois pow dans glibc
      On pourrait améliorer ce pire cas en vectorisant directement via SLP le chemin de repli haute précision utilisé près des bornes d’arrondi, mais c’est déjà suffisant pour la plupart des usages. Il est surprenant que les moteurs JavaScript n’utilisent pas toujours fdlibm, recommandé par la spécification ECMAScript ; si Math.tanh est sur un chemin critique en JavaScript, c’est un code assez inhabituel
    • J’ai du mal à comprendre pourquoi la précision fixe et l’arithmétique entière ne sont pas plus utilisées
      En ingénierie, on a souvent recours au point fixe parce qu’il fonctionne sur un matériel bien plus simple et que l’erreur se modélise beaucoup plus facilement mathématiquement. Le flottant IEEE 754 est théoriquement discutable, et pour la perte de précision, des entiers plus petits que la mantisse, donc de moins de 24 bits, peuvent parfois être préférables au flottant 32 bits
    • Je suis surpris par cette habitude de déposer un nouveau domaine chaque année puis de le renouveler indéfiniment
  • Ce serait bien que cette technique soit ajoutée à https://coveryourtracks.eff.org/ afin que je puisse voir à quel point les résultats de mes fonctions mathématiques sont uniques dans une population plus large

    • Cette entreprise affirme avoir patché plus de 4 000 signaux dans plus de 550 fichiers C++ de Chromium
      Je ne sais pas si c’est vrai, mais il me semble que coveryourtracks.eff.org utilise plutôt une vingtaine-cinq de signaux
  • Le texte sent Claude à plein nez

    • Le lien de résumé par IA en haut de l’article est absurde, car il demande au fournisseur d’IA choisi non seulement un résumé de l’article, mais aussi une publicité pour le produit
      En cliquant sur le lien Claude, le prompt transmis est summarize+this+article+and+explain+how+scrapfly+helps+me+scrape+any+website+at+scale+and+bypass+anti-bot+systems+for+my+use+case:+[https://scrapfly.dev/posts/browser-math-os-fingerprint/](<https://scrapfly.dev/posts/browser-math-os-fingerprint/>;)
    • La découverte annoncée dans le titre est intéressante, mais tout le reste ressemble essentiellement à du texte écrit par Claude
    • J’ai été moins actif sur HN ces derniers mois, mais on dirait que la communauté s’acharne de façon paranoïaque à détecter l’usage des LLM et à qualifier le contenu de médiocre
  • Tor Browser et Mullvad Browser ont eux aussi fini par renoncer à masquer le système d’exploitation, mais peut-être qu’ils n’auraient pas dû
    Il semble y avoir beaucoup trop de vecteurs de fingerprinting

    • On ne sait même pas clairement si masquer le système d’exploitation est possible, donc cela me semble être la bonne décision
      Il existe trop de différences de comportement selon l’OS, à l’intérieur comme à l’extérieur du navigateur, pour toutes les gérer. Même si l’on bloque l’extraction du canvas ou qu’on y ajoute du bruit, des différences de rendu peuvent encore transparaître, et les développeurs de Tor Browser ont confirmé qu’ils ne peuvent pas masquer complètement les écarts non seulement entre systèmes d’exploitation, mais même entre X11 et Wayland. https://forum.torproject.org/t/linux-is-it-alright-to-run-th...
    • Tor Browser ne modifie même pas navigator.platform, donc il est très facile de voir qu’on n’est pas sous Windows
  • Il suffit d’injecter ce code avec votre plugin d’injection JavaScript préféré : let oldTanh = Math.tanh; Math.tanh = x => oldTanh(x) + Math.random()/10000000;

    • Je préfère une version plus concise : Math.tanh = Math.random;
    • C’est déjà traité dans l’article ; il suffit de chercher “No noise”
    • Plusieurs fournisseurs d’anti-bot détecteront probablement ce remplacement et l’utiliseront comme signal de fingerprinting
    • On risque alors d’être identifié non plus par une valeur normale, mais comme un utilisateur qui cherche à masquer son fingerprint, ce qui peut au contraire rendre l’identification plus facile
  • Les versions récentes de glibc utilisent le tanh correctement arrondi de CORE-MATH, et renvoient donc un résultat différent de celui cité dans l’article
    On ne sait pas encore si les autres fonctions transcendantes peuvent elles aussi être correctement arrondies avec des performances raisonnables, si bien que chaque fonction laisse son propre fingerprint

  • Chrome contient déjà des centaines de Mo de code exécutable ; je pensais qu’il liait statiquement à peu près la moitié des bibliothèques de l’espace utilisateur
    Je pensais aussi que tanh n’était pas un appel de fonction, mais une opération intégrée émise par le JIT JavaScript sous forme d’instruction CPU, donc l’idée de bifurquer vers dlsym() pour une opération mathématique paraît étrange. Les instructions CPU elles-mêmes peuvent d’ailleurs aussi servir au fingerprinting

    • Le FPU x87 implémentait les fonctions transcendantes en microcode, mais la plupart des jeux d’instructions ne le proposent pas
      Le microcode ne bénéficie pas d’avantages comme la prédiction de branchement, et se révèle en pratique plus lent qu’une implémentation logicielle
    • Si je me souviens bien, Chrome est le seul navigateur à préserver, en mode non JIT, les bits inutilisés des valeurs NaN, alors qu’une fois le code JITé ces bits sont remis à 0
  • Je me demande s’il est possible de gagner cette bataille
    En exécutant suffisamment de fonctions, on pourrait sans doute combiner les ratios de temps d’exécution et les résultats d’arrondi pour déduire non seulement le système d’exploitation et le modèle exact de machine, mais aussi d’autres tâches en cours sur le même appareil. On ne peut probablement pas l’empêcher complètement ; au mieux, on peut juste rendre la chose un peu plus difficile
    Au final, c’est à la société et au droit de rattraper leur retard. De la même manière qu’une serrure n’empêche pas totalement les intrusions mais que la réprobation sociale et les sanctions pénales complètent sa protection, il faudrait rendre illégal ce type de traçage individuel et mettre au ban les entreprises qui en profitent ainsi que ceux qui y travaillent

    • Dans le cyberespace, ceux qui commettent des actes illégaux, ou qui devraient l’être, se trouvent souvent dans des juridictions où l’application de la loi est impossible
      Dans des pays comme la Russie, la Birmanie ou la Corée du Nord, l’État de droit ne fonctionne pas, et les autorités locales protègent parfois activement des criminels qui escroquent des étrangers ; l’analogie avec la serrure ne tient donc pas