- A partir do Chrome 148, o
Math.tanhdo V8 passou a chamar ostd::tanhdo host em vez do fdlibm embutido, e assim a mesma entrada retorna um último bit diferente no glibc do Linux, nolibsystem_mdo macOS e no UCRT do Windows Math.tanh(0.8)resulta em0.6640367702678491no Linux,0.664036770267849no macOS e0.6640367702678489no Windows, permitindo distinguir os três sistemas operacionais com uma única chamada; se o resultado não bater com o SO declarado no User-Agent, a camuflagem é exposta- Como cada engine tem um caminho de vazamento diferente, no
Math.*do V8 apenas otanhusa a biblioteca matemática do host, mas todas as funções trigonométricas de CSS no Blink e algumas operações do Web Audio também passam por bibliotecas específicas do SO - Adicionar pequenas perturbações arbitrárias aos valores não os faz coincidir com nenhum SO real e ainda quebra o determinismo; por isso, é necessário reproduzir em nível de bit os coeficientes, tabelas, redução de intervalo e comportamento de FMA da biblioteca-alvo, ou mapear diretamente o código original do UCRT
- A Scrapfly valida, a cada release, 871.000 entradas comparando com Macs reais e Chrome real para verificar a correspondência bit a bit de
Math.tanhe de 7 funções trigonométricas de CSS, ajustando não só a precisão, mas também diferenças de arquitetura e tempo de execução ao nível de um navegador real
O SO revelado por Math.tanh
- O resultado de
Math.tanh(0.8)varia conforme a biblioteca matemática do host- glibc do Chrome no Linux:
0.6640367702678491 libsystem_mdo Chrome no macOS:0.664036770267849- UCRT do Chrome no Windows:
0.6640367702678489
- glibc do Chrome no Linux:
- Apple e glibc diferem em geral por 1 ULP em cerca de um quarto de todas as entradas, e o UCRT do Windows difere das duas bibliotecas em alguns por cento das entradas
- ULP (unit in the last place) é o espaçamento entre números de ponto flutuante consecutivos representáveis em uma determinada magnitude, e 1 ULP é a menor diferença que um
doublepode representar
- ULP (unit in the last place) é o espaçamento entre números de ponto flutuante consecutivos representáveis em uma determinada magnitude, e 1 ULP é a menor diferença que um
- Ao medir o Chrome 150 real em Linux, macOS 26 com Apple Silicon e Windows 11 via DevTools Protocol, o poder de classificação varia conforme a entrada
tanh(0.5)dá0.46211715726000974nos três SOs, então não serve para detecçãotanh(0.7)difere em 1 ULP apenas no Linuxtanh(0.8)é diferente nos três SOs, com amplitude total de 2 ULPstanh(0.9)difere em 1 ULP apenas no Windows
- Em cerca de três quartos das entradas, os três SOs produzem o mesmo resultado, mas com uma entrada adequada é possível obter uma assinatura por SO
- Se um navegador afirmar ser macOS, mas retornar os bits matemáticos do Linux, o resultado de
Math.tanhentra em contradição com o User-Agent
A mudança introduzida no Chrome 148
- Até o Chrome 147, o V8 embutia um port do fdlibm, uma implementação matemática portátil, para calcular
Math.tanh, então todos os SOs retornavam os mesmos bits - O commit do V8
c1486295ae5substituiu a implementação embutida pelostd::tanhda plataforma- Essa mudança entrou pela primeira vez no V8 14.8.57 e no Chrome 148
- Chrome 148, 149 e 150 expõem as diferenças do
libmdo host, mas Chrome 147 ou anterior não vazam o SO por esse caminho
- O IEEE 754 define a forma de armazenamento do
double, mas não exige que funções transcendentais comosin,cos,tanheexpsejam necessariamente arredondadas de forma correta - A biblioteca matemática (
libm) de cada SO faz compromissos entre desempenho e erro em ULP, usando coeficientes diferentes de polinômios minimax, tabelas de consulta e constantes de redução de intervalo- Linux usa glibc
- macOS usa o
libsystem_mda Apple - Windows usa o
ucrtbase.dlldo UCRT
- O detector não precisa analisar a operação matemática em si; ele pode comparar os valores com uma tabela de resultados por entrada obtida do Chrome real
Quatro armadilhas que dificultam a reprodução
-
Só algumas funções do V8 vazam
- O V8 faz link estático da maior parte das implementações matemáticas, então elas produzem os mesmos resultados independentemente do SO
Math.exp,Math.pow,Math.atane outras usam implementações embutidas do llvm-libcMath.sineMath.cosusam rotinas embutidasdbl-64derivadas do glibc- Após o Chrome 148, entre
Math.*, apenasMath.tanhusa ostd::tanhda plataforma e por isso vaza o SO - Forjar até as funções que não vazam como se fossem do SO-alvo quebra a estrutura real de chamadas do V8, e a própria assimetria em que só o
tanhdifere também pode ser verificada
-
JavaScript e CSS usam caminhos diferentes
sin(),cos()eatan2()do CSS não compartilham código comMath.sindo JavaScript- O motor de layout Blink reduz primeiro os ângulos em graus e depois chama
std::sine afins da plataforma sobre o valor reduzido - Isso difere do resultado de calcular diretamente com entrada em radianos, e todas as 7 funções trigonométricas de CSS vazam o SO via
libmdo host - Para reproduzir em nível de bit, é preciso incluir não só a função matemática final, mas também a redução de intervalo em graus e a conversão entre radianos e graus
-
Existem duas bibliotecas diferentes até dentro do macOS
- Em Apple Silicon, coexistem o
libsystem_mescalar e as rotinas vetoriais do Accelerate, comovvsinevvtanh, e as duas implementações não são iguais - Em 1 milhão de entradas, conforme a função, 10% a 89% dos resultados diferiram
cos(0)é exatamente1.0na implementação escalar- No Accelerate, retorna
0.9999999999999999 - O Chrome real em Mac foi medido via protocolo de depuração para distinguir qual biblioteca é usada em cada ponto de chamada
Math.tanh, as funções trigonométricas de CSS e as funções transcendentais por amostra do compressor de áudio usam olibsystem_mescalar- O DSP do Web Audio no Mac, FFT, matemática vetorial e filtros biquad usam Accelerate
- Caminhos relevantes no Chromium incluem
fft_frame_mac.cc,vector_math_mac.h,biquad.cceBUILDFLAG(IS_MAC) - Se a biblioteca da Apple escolhida não corresponder ao ponto de chamada, os resultados podem sair 1 ULP errados na maioria das entradas
- Em Apple Silicon, coexistem o
-
A arquitetura da CPU também interfere nos resultados
- ARM e x86 diferem em FMA (fused multiply-add) e na propagação do sinal de NaN
- Mesmo que o procedimento matemático esteja correto, se o compilador fundir multiplicação e soma apenas em uma arquitetura, os bits de resultado mudam
Caminhos de vazamento por engine e recurso
- O
Math.*do V8 em JavaScript quase todo usa implementações embutidas, e há apenas um ponto que se conecta aolibmdo host:Math.tanhsin,cos,tan,asin,acos,atan,atan2,exp,log,log2,log10,powusam implementações embutidas do V8sqrt,abse as quatro operações aritméticas são operações de hardware
- As funções matemáticas do
calc()no CSS fazem o Blink chamar diretamente a biblioteca da plataformasin,cos,tan,asin,acos,atan,atan2,exp,log,log2,log10,powusam olibmdo host- Não existe um caminho correspondente para
tanhem CSS
- O Web Audio mistura várias implementações dependendo do ponto de chamada
- No Mac, FFT do oscilador, soma/multiplicação/escala vetorial e FFT usam
vDSPdo Accelerate - Funções transcendentais por amostra do DynamicsCompressor, como
sin,exp,log10f,powf, usam olibsystem_mescalar - Um único grafo de áudio pode atravessar três bibliotecas diferentes: matemática embutida do V8, biblioteca escalar e Accelerate
- No Mac, FFT do oscilador, soma/multiplicação/escala vetorial e FFT usam
- WebAssembly não tem instruções de funções transcendentais
- Resultados como
sindependem dolibmincluído no módulo - Operações aritméticas como
f64.sqrtef64.mulsão executadas em hardware e por isso são iguais entre SOs - O eixo de fingerprint restante está na normalização de NaN entre ARM e x86 e em algumas diferenças de arredondamento de SIMD
- Resultados como
- Os sinais de detecção se concentram em
Math.tanh, em todas as funções trigonométricas de CSS e no Web Audio- O FFT do Accelerate no Web Audio revela a arquitetura da CPU
- O
libsystem_mescalar do compressor revela o SO
Em vez de mexer no valor, reproduzir exatamente
-
Por que o ruído falha
- Adicionar ruído ao resultado pode fazer com que ele não corresponda ao valor de nenhum SO real na tabela de referência
- Se o valor aleatório mudar a cada chamada, o determinismo se perde, e isso por si só vira outro sinal de detecção
- O objetivo não é obter um valor parecido, mas sim exatamente o mesmo valor em nível de bit que o SO declarado retornaria
-
Restaurar todos os elementos do algoritmo-alvo
- Recuperar do
libmde destino os coeficientes de aproximação minimax, tabelas de expoentes e constantes de redução de intervalo, e portar isso para código C portátil - É preciso coincidir até com entradas em que a biblioteca-alvo arredonda na direção errada
- A reprodução do
sinda Apple usa o padrão exato de bits dos coeficientes extraídos dolibsystem_me chamadas explícitas afma() - Se os coeficientes forem passados para decimal, podem ser arredondados de novo no processo de transcrição, por isso são preservados como valores hexadecimais de ponto flutuante
- Cada multiplicação-soma fundida que a Apple faz também é fundida explicitamente no código
- Recuperar do
-
Fixar o FMA de forma determinística
- Compilar com
-ffp-contract=offpara impedir que o compilador adicione ou remova FMA arbitrariamente - Assim, só o
fma()explícito no código é executado nos mesmos pontos que na Apple, e é possível obter os mesmos bits mesmo rodando em um servidor x86 enquanto se imita ARM - FMA em hardware e FMA em software com arredondamento correto retornam os mesmos bits
- Compilar com
Uso do código original do UCRT do Windows
- O UCRT do Windows usa a mesma ISA x86-64 que servidores Linux e é position-independent, então é possível mapear o
ucrtbase.dllreal na memória em tempo de execução e chamar diretamente os exports das funções matemáticas - Como o código original é executado, obtêm-se os bits reais do UCRT sem precisar fazer engenharia reversa separada do algoritmo matemático
- É necessário tratar a diferença entre a ABI System V do Linux e a ABI x64 do Windows
- No Windows x64, a função chamada usa 32 bytes de shadow space acima do endereço de retorno
- O conjunto de registradores preservados pelo callee também é diferente do System V
- Se o ponteiro de função não for declarado com
ms_abi, a escrita no shadow space pode corromper o stack frame do clang e fazer a chamada indireta saltar para um endereço incorreto
- O código do DLL mapeado não é um alvo de chamada indireta registrado no CFI
- Em produção,
-fsanitize=cfi-icallpode causar traps#UDeSIGILLa cada chamada - O wrapper que chama o ponteiro de função precisa de
clang::no_sanitize("cfi-icall")
- Em produção,
- As funções matemáticas do UCRT leem no início um flag de dispatch de CPU com
mov eax, [rip+disp32]para escolher entre caminho escalar e caminho com FMA/AVX2- No DLL recém-mapeado, o flag fica em 0 e o caminho lento escalar é escolhido
- Os bits produzidos por esse caminho diferem dos resultados de um Windows moderno
- É preciso localizar o endereço do flag no prólogo do
tanhe forçar o caminho com FMA antes da primeira chamada para coincidir bit a bit com o Windows real
Ponto de patch e restrições de desempenho
- Faz-se hook no gargalo único em que a engine chama o
libm, e escolhe-se o caminho conforme o SO que o navegador afirma ser- Se afirmar ser Linux, mantém-se o glibc
- Se afirmar ser macOS, usa-se a implementação reconstituída da Apple
- Mesmo que o resultado esteja correto, diferenças no tempo de execução em relação a um navegador real também podem ser detectadas
- O primeiro build usava uma baseline x86 padrão antiga demais para FMA em hardware, então todo
fma()era rebaixado a chamada de software, ficando 2,5 a 6 vezes mais lento que o nativo - Comparar a razão de tempo entre loops de
Math.tanheMath.sinpode revelar um padrão de desempenho que não existe no navegador real - Ao ativar FMA em hardware, cada operação fundida virou uma única instrução e ficou cerca de 6 vezes mais rápida, mantendo os mesmos bits e até superando o glibc em velocidade
Validação com 871.000 entradas
- O harness de validação executa, a cada release, 871.000 entradas cobrindo todos os ramos e todo o domínio
- grade densa de entradas
- limites de intervalo
- subnormais
- zero com sinal
- infinito
- NaN
- Dois tipos de ambiente real são usados como referência
- Um Mac real calcula, para todas as entradas, os resultados escalar e Accelerate, permitindo identificar onde as duas implementações divergem
- O Chrome real em Mac é executado pelo protocolo de depuração para coletar os resultados em precisão total de
Math.tanhe de todas as funções trigonométricas de CSS
Math.tanhesin,cos,tan,asin,acos,atan,atan2do CSS coincidem bit a bit com o Chrome real no Mac- Também se verifica se a implementação reproduzida se comporta exatamente como o código de máquina real do binário distribuído
- É necessário coincidir até com o pós-processamento do navegador nos limites de domínio
- No Mac real,
asin(2)no CSS está fora do domínio, vira NaN e o CSS restringe NaN para 0, então o valor final é0 - Uma implementação simplificada de reprodução pode retornar incorretamente 90 graus
- No Mac real,
Por que a matemática importa na camuflagem de navegador
- Resultados matemáticos podem ser verificados de forma determinística e barata, mas para camuflá-los com precisão é preciso conhecer o interior do
libmde cada fornecedor e os caminhos de chamada de cada engine - Para coincidir com um navegador real, é necessário descobrir qual biblioteca matemática V8, Blink e Web Audio escolhem em cada ponto de chamada, e ainda alinhar o último bit, o comportamento por arquitetura e o tempo de execução
- O Scrapium da Scrapfly, quando solicitado a se apresentar como macOS, é configurado para coincidir com o tráfego real do macOS até no bit de arredondamento do cosseno
1 comentários
Comentários do Hacker News
A explicação de que uma única chamada de
tanhcom a entrada correta produz uma assinatura por sistema operacional deixou de lado a possibilidade de identificar um intervalo de versões do navegadorA maioria não falsifica o sistema operacional no User-Agent, e fingerprinting se interessa mais por combinações de características quase únicas do que pelo sistema operacional em si. A descoberta é interessante, mas o texto parece excessivamente escrito por LLM, o que reduz sua credibilidade
Assim eles conseguem passar mais facilmente pela detecção de bots e vender aos clientes dados coletados de outros sites
O fato de ter sido escrito com LLM foi divulgado no post e no blog; não foi escondido nem houve tentativa de se passar por humano. Eu estava sem tempo, então, se não fosse assim, eu provavelmente nem teria publicado o texto, e assumo essa escolha
O navegador continua adicionando recursos e corrigindo bugs, e a maior parte disso pode ser detectada por JavaScript
Estratégia esperta: analisar todas as técnicas de fingerprinting com IA e publicá-las, e depois da controvérsia induzir os navegadores a bloqueá-las, para que o próprio negócio de scraping da empresa ganhe mais dinheiro
Se empresas assim não existissem, o fingerprinting de navegadores não estaria tão disseminado hoje e a internet seria melhor. Eu prefiro até textos do lado oposto, como os da fingerprint.js, onde o conflito de interesses é claro
Mais um motivo para defender funções transcendentais com arredondamento correto
Recentemente descobri que esse problema está praticamente resolvido. Veja a segunda palestra principal em https://arith2026.org/program.html
libmcom arredondamento correto são ótimas, mas não podem ter um pior caso de desempenho terrível como opowantigo da glibcDá para tentar melhorar o pior caso vetorizando manualmente com SLP o caminho alternativo de alta precisão usado perto do limite de arredondamento, mas para a maioria dos usos isso já é suficiente. É surpreendente que engines JavaScript continuem sem usar o
fdlibmrecomendado pela especificação ECMAScript, e seMath.tanhfor um gargalo em JavaScript, esse é um código bastante incomumEm engenharia, ponto fixo era usado com frequência porque roda em hardware muito mais simples e os erros podem ser modelados matematicamente com facilidade. IEEE 754 é teoricamente questionável, e quando há perda de precisão, inteiros pequenos, isto é, abaixo de 24 bits, às vezes são melhores do que ponto flutuante de 32 bits
Seria bom se essa técnica fosse adicionada ao https://coveryourtracks.eff.org/ para eu poder verificar quão únicos são os resultados das minhas funções matemáticas em uma população maior
Não sei se isso é verdade, mas imagino que o coveryourtracks.eff.org use algo como 25 sinais
O texto tem cara de ter sido escrito pelo Claude
Ao clicar no link do Claude, é enviado o prompt
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/O Tor Browser e o Mullvad Browser acabaram desistindo de ocultar o sistema operacional, mas talvez não devessem ter feito isso
Parece haver caminhos de fingerprinting demais
Há diferenças demais de comportamento por sistema operacional dentro e fora do navegador, e é difícil cobrir tudo. Mesmo bloqueando extração de canvas ou adicionando ruído, diferenças de renderização ainda podem vazar, e os desenvolvedores do Tor Browser já confirmaram que não conseguem esconder nem sequer a diferença entre X11 e Wayland, quanto mais entre sistemas operacionais totalmente distintos. https://forum.torproject.org/t/linux-is-it-alright-to-run-th...
navigator.platform, então é muito fácil descobrir um ambiente que não seja WindowsBasta inserir o seguinte código com seu plugin favorito de injeção de JavaScript:
let oldTanh = Math.tanh; Math.tanh = x => oldTanh(x) + Math.random()/10000000;Math.tanh = Math.random;A glibc mais recente usa o
tanhcom arredondamento correto do CORE-MATH, então retorna resultados diferentes dos citados no textoAinda não está claro se outras funções transcendentais também conseguem implementar arredondamento correto com desempenho razoável, então cada função continua deixando seu próprio fingerprint característico
O Chrome tem centenas de MB só de código executável, então eu achava que ele tinha linkado estaticamente pelo menos metade das bibliotecas de espaço de usuário
Além disso, eu pensava que
tanhnão era uma chamada de função, mas uma operação embutida emitida pelo JIT de JavaScript como instrução de CPU, então é estranho ter de desviar para uma função viadlsym()para uma operação matemática. As próprias instruções de CPU também podem ser fingerprintadasO microcódigo não se beneficia de coisas como predição de desvio, e na prática pode ser mais lento do que uma implementação em software
Fico em dúvida se dá para vencer essa briga
Se você executar funções suficientes, parece possível combinar proporções de tempo de execução e resultados de arredondamento para inferir não só o sistema operacional e o modelo exato, mas até outras tarefas rodando na mesma máquina. Parece que no máximo dá para dificultar um pouco, não bloquear totalmente
No fim, a sociedade e a lei precisam alcançar isso. Assim como fechaduras não impedem totalmente invasões, mas são complementadas por reprovação social e punição criminal, deveríamos tornar ilegal esse tipo de rastreamento pessoal e ostracizar socialmente as empresas que lucram com isso e quem trabalha nelas
Em lugares como Rússia, Myanmar e Coreia do Norte, o Estado de direito não funciona, e as autoridades locais às vezes protegem ativamente criminosos que enganam estrangeiros, então a analogia com fechaduras não se sustenta