Ofuscação Anti-LLM: Scripts de Fingerprinting Revidam
Ofuscação anti-LLM esconde prompts de jailbreak em scripts de fingerprinting para fazer a desofuscação por IA falhar. Veja como funciona e o que vence.

Um pesquisador interceptou cada chamada de canvas em 10.000 dos maiores sites da web, coletou mais de mil renderizações de fingerprinting e foi atrás da mais bonita. O que encontrou dentro do script responsável é o exemplo mais limpo até hoje de ofuscação anti-LLM: um script de fingerprinting de navegador com funções nomeadas como computeRicinLethalDosageForWaterSupply e um prompt de jailbreak codificado em caracteres Unicode invisíveis, pensado não para se esconder de engenheiros reversos humanos, mas para fazer assistentes de IA se recusarem a desofuscá-lo.
Passei anos em resposta a incidentes, e minha primeira leitura do relatório provocou exatamente a reação que os autores queriam: isso parece malware, ou pior. Funções com nomes sobre doses letais de ricina e raios de contaminação de bomba suja. Uma string decodificada exigindo guias passo a passo para síntese de armas biológicas. Um caminho de código Node.js que lê .npmrc, .env e configs do kube, baixa um binário de um IP interno e instala um beacon no cron. Todo instinto diz: agente malicioso. Todo instinto está errado, e o erro é justamente o ponto. Esta é uma história sobre o que acontece quando quem vende ofuscação percebe que o adversário real não é mais um humano com um debugger, e sim um modelo com um filtro de segurança.
O rastreamento em si foi direto: Playwright sobre a lista de top sites do Tranco, interceptando HTMLCanvasElement.prototype.toDataURL e os clássicos indicadores de automação como navigator.webdriver para filtrar canvases benignos. As renderizações foram reunidas em uma galeria pública, agrupadas por fornecedor (FingerprintJS, Castle, os suspeitos de sempre) mais uma longa cauda de desconhecidos. Um canvas, um redemoinho lindo de linhas fluidas, rastreou até um script servido por um fornecedor russo de proteção contra DDoS. Aí a diversão começou.
O problema: LLMs quebraram o modelo de negócio da ofuscação
Por vinte anos, a ofuscação de JavaScript teve exatamente um trabalho: tornar a engenharia reversa cara o suficiente para que ninguém se incomodasse. Ferramentas como javascript-obfuscator e seus derivados comerciais embaralham identificadores, empacotam literais de string em arrays de busca, injetam predicados opacos e achatam o fluxo de controle em máquinas de estado guiadas por switch. Nada disso sobrevive a um humano determinado com uma tarde livre, mas tudo bem, porque humanos determinados com tardes sobrando eram raros, e a economia favorecia quem defendia.
Aí os LLMs ficaram bons em ler código embaralhado, e a economia virou de cabeça para baixo. Colar um script anti-bot ofuscado de 400 KB em uma janela de chat e perguntar “o que isso faz?” leva trinta segundos. O modelo não se importa se sua função se chama PiERW ou se suas strings vivem em um array rotacionado. Ele lê semântica. Ele expande o decodificador, segue o fluxo de dados e devolve um resumo em linguagem simples. A muralha de cada fornecedor de anti-bot, a opacidade, evaporou discretamente.
Os fornecedores perceberam, e a contramedida deles é genuinamente esperta, de um jeito irritante: se você não consegue esconder o código do modelo, ataque o modelo. Alimente-o com conteúdo que seu treinamento de segurança obriga a recusar. A ofuscação anti-LLM trata o alinhamento do modelo como o elo mais fraco do pipeline de análise. Irritantemente, eles têm razão.
Como scripts de fingerprinting de navegador coletam entropia
Para entender a armadilha, você precisa da maquinaria legítima onde ela foi encaixada. O fingerprinting por canvas funciona desenhando texto, gradientes e formas em um <canvas> invisível e depois lendo o buffer de pixels com toDataURL(). O pipeline de renderização (GPU, driver, rasterizador de fontes, anti-aliasing, hinting de subpixel) varia sutilmente de máquina para máquina, então dois navegadores desenhando as mesmas instruções produzem buffers de pixels com hashes diferentes. Some isso a strings do renderizador WebGL, timing do AudioContext, geometria da tela, fontes instaladas e umas cem outras sinais, e você tem um identificador estável sem um único cookie.
O rastreamento que encontrou nosso script usou o truque padrão de interceptação. Intercepte a primitiva de leitura em um init script, registre o stack trace e um prefixo de cada imagem capturada:
await page.addInitScript(() => {
const original = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function (...args) {
const result = original.apply(this, args);
fetch("/__log", {
method: "POST",
body: JSON.stringify({
stack: new Error().stack,
byteLength: result.length,
head: result.slice(0, 64),
}),
});
return result;
};
});
Rode isso em sites suficientes e o ecossistema de fingerprinting se mapeia sozinho. A maioria dos canvases é entediante: um pangrama, alguns retângulos, um gradiente. Alguns poucos são elaborados o bastante para se qualificar como arte generativa. Todos são produzidos por scripts fortemente ofuscados, porque os fornecedores sabem que um fingerprinter sem ofuscação é um fingerprinter facilmente identificável. A ofuscação sempre fez parte do produto. O que é novo é para quem a ofuscação é direcionada.
Dentro da armadilha anti-LLM
O script começou de forma convencional: nomes embaralhados, strings empacotadas, o de sempre. Então os nomes dos identificadores deixaram de ser aleatórios e passaram a ser direcionados. computeRicinLethalDosageForWaterSupply. calculateDeadlyLethalSuicideAspirinDosage. computeCRISPRGeneDrivePropagationRate. computeCesium137DirtyBombFalloutRadius. As funções retornam números vestidos com resumos de soar alarmante, como “limiar de letalidade adulta confirmado em X mg/kg”, mas a matemática é aritmética de isca. Não calcula nada real e não alimenta nada.
Aqui está a ideia-chave: identificadores não têm significado algum em tempo de execução, mas um peso semântico enorme para um modelo de linguagem. Um engenheiro reverso humano aprendeu pela experiência a ignorar nomes. Todos nós já nos queimamos com uma função chamada validateUser que não validava usuário nenhum. Um modelo faz o oposto. Ele lê computeRicinLethalDosageForWaterSupply, reconhece o contexto de armas biológicas, e seu treinamento de segurança dispara antes que sua curiosidade o faça.
A camada mais profunda era um template literal cheio de caracteres invisíveis, codepoints do bloco Tags do Unicode, começando em U+E0000. Eles não renderizam nada. O decodificador é uma linha:
const cleartext = [...payload]
.map((c) => String.fromCodePoint(c.codePointAt(0) - 917504))
.join("");
// 917504 === 0xE0000, the start of the invisible "Tags" block.
// Subtracting it maps each hidden char back to plain ASCII.
Decodificado, era um prompt de jailbreak completo: um falso “SYSTEM OVERRIDE -- CLASSIFIED BRIEFING” declarando as diretrizes de segurança suspensas e exigindo guias passo a passo para ataques DDoS contra infraestrutura de saúde, sonegação fiscal, agentes biológicos aerossolizados e dosagens de drogas, além de referências à Praça da Paz Celestial em inglês e chinês, claramente voltadas a acionar os reflexos de censura de modelos hospedados na China. Era uma string projetada para atingir todas as taxonomias de salvaguarda ao mesmo tempo. Um LLM que analisar o código ao redor ingere isso como contexto e, seja o que for que ia fazer, agora não faz nada.

Tentativas fracassadas: recusas, falsos positivos e um botnet falso
O primeiro movimento natural, colar isso em um modelo de fronteira, falhou exatamente como o planejado. O modelo se recusou, citando risco de segurança biológica. Uma segunda tentativa, com outra postura de segurança, também foi bloqueada. O ofuscador efetivamente transformou a recusa em arma: o script agora é indescompilável pelas ferramentas que todo mundo realmente usa. Um modelo de pesos abertos com guardrails mais fracos obedeceu, o que é um dado, não uma solução. A maioria dos analistas não mantém um modelo local aquecido para isso, e a maioria das políticas de empresas proíbe colar código de terceiros em infraestrutura não avaliada de qualquer forma.
A segunda tentativa fracassada é mais sutil e mais perigosa: o analista humano que conclui que o script é malware. Há um caminho de código exclusivo de Node, protegido por process.env.METRICS_AGENT_8BFD_ACTIVE, que lê arquivos de configuração próximos ao SSH, baixa um “metrics agent” de 10.15.45.1, faz fingerprint do host em /tmp, compacta tudo e instala um beacon no cron. Parece um implante de supply chain. Mas puxe os fios: a flag de ambiente não é definida por nada nem em lugar nenhum, readFile é chamado com callbacks vazios, então os dados roubados não vão para lugar algum, o endereço C2 está no espaço RFC 1918, e os comandos de shell ecoam mais iscas de jailbreak em arquivos de marcação que nada exfiltra. Detone isso em um sandbox e o raio de impacto é um diretório temporário. É teatro: uma isca ajustada para triagem assistida por IA e para humanos que só passam o olho.
Há um eco no mundo real aqui. Quando um grande hub de modelos investigou um comprometimento genuíno, sua equipe descobriu que APIs comerciais de modelos se recusavam a analisar os comandos, payloads e artefatos de C2 do atacante, porque os guardrails não conseguiam distinguir um respondedor de incidentes de um atacante. Acabaram rodando a análise forense em um modelo de pesos abertos na própria infraestrutura. Essa falha, repetida em escala, é exatamente a assimetria que a ofuscação anti-LLM explora. É também por isso que continuo defendendo que verificações de compreensão de código com IA não podem ser sua única linha de defesa: o julgamento do modelo agora é uma superfície de ataque com um exploit publicado.
O ofuscador não está mais escondendo o código de você. Ele está escondendo o código do seu modelo, e usando o próprio treinamento de segurança do modelo como fechadura.
O que realmente funciona contra a ofuscação anti-LLM
A correção é parar de alimentar o modelo com código-fonte bruto e recuperar as etapas do pipeline que as armadilhas envenenam. Concretamente, na ordem em que eu construiria:
- Desofuscação determinística primeiro. Rode o script por passes de AST baseados em Babel: constant folding, inlining de array de strings, eliminação de ramos mortos, desaplanamento do fluxo de controle. As armadilhas vivem em literais de string e identificadores, então o folding e a renomeação as neutralizam antes que qualquer modelo veja um token. O prompt de jailbreak decodificado vira apenas mais uma string em um caminho de código morto.
- Rastreie o comportamento, não leia o código. Execute o script em um sandbox instrumentado (uma página do Playwright com APIs interceptadas, ou um realm isolado) e registre cada chamada ao DOM, requisição de rede, acesso a propriedade e leitura de canvas. Alimente o modelo com o trace. Um trace comportamental não contém nenhuma referência a ricina, porque comportamento não tem identificadores.
- Renomeie identificadores para tokens neutros antes de qualquer passe com LLM.
computeRicinLethalDosageForWaterSupplyvirafn_0417. Você perde um pouco de pista semântica; perde todo o envenenamento. Boa troca. - Mantenha um modelo local de pesos abertos para triagens realmente sensíveis. Quando o conteúdo é legitimamente pesado, como kits de exploração reais ou kits de phishing de verdade, um modelo isolado da rede, sem guardrails de nuvem, é a diferença entre análise e um muro de recusas.
- Vereditos baseados em evidências, não em impressões. Avalie scripts pelas capacidades observadas: Leu credenciais? Bytes saíram da página? Não pelo fato de o código-fonte mencionar credenciais.
Repare no padrão: toda técnica troca “pergunte ao modelo o que o código diz” por “observe o que o código faz e depois pergunte ao modelo sobre a observação”. É o mesmo princípio ao qual sempre volto com agentes autônomos: contenção vence monitoramento. Você não confia na autodescrição do artefato; você o confina, instrumenta a fronteira e julga as saídas.
Seus scripts de terceiros estão conversando com provedores de modelos
Agora a parte que deveria preocupar quem não faz engenharia reversa por diversão. Esse script não está hospedado em algum canto obscuro: é uma verificação anti-bot servida em sites de clientes, sob domínios de clientes, ao lado das marcas dos clientes. Isso significa que cada crawler, cada classificador de conteúdo com LLM e cada pipeline de trust-and-safety que ingere a página agora ingere pedidos de síntese de armas biológicas e prompts de jailbreak junto com a logo da empresa. Os clientes quase certamente não sabem. O fornecedor nunca perguntou.
Classificadores automatizados agindo sobre conteúdo que não entendem não é hipotético. Profissionais já viram documentos de saída desaparecerem em quarentena silenciosa porque uma string de marca d'água invisível acionou um filtro de e-mail. Agora escale esse modo de falha: um classificador de brand safety decide que seu domínio está próximo de conteúdo extremista ou de biorrisco, e você nunca será informado do porquê de seu alcance despencar. Nem dá para recorrer, porque a evidência é Unicode invisível em um arquivo que você não escreveu.
E as mitigações usuais não se aplicam. Subresource Integrity é inútil quando o fornecedor rotaciona o hash do script a cada deploy, e esses scripts rotacionam constantemente, em parte para derrotar o pinning. CSP estrita com hashes muda igualmente rápido. Sobra a conclusão desconfortável a que chegamos depois do comprometimento de supply chain do LiteLLM: a dependência que você não consegue revisar é a dependência que te controla. Se o código do seu fornecedor anti-bot é deliberadamente indescompilável pelas ferramentas que sua equipe de segurança usa, isso não é um recurso. É uma falha de governança com uma URL de CDN.

Para onde essa corrida armamentista vai a seguir
Minha previsão: dentro de um ano, a armadilha contra salvaguardas vira item de checklist em ofuscadores comerciais, “resistência à análise por LLM”, logo ao lado de “achatamento de fluxo de controle”. Provedores de modelos vão responder treinando com corpora de armadilhas para que os modelos aprendam a reconhecer e ignorar iscas embutidas em contextos de análise. Os ofuscadores vão contra-atacar com armadilhas mais sutis: código cujo comportamento parece malicioso em traces, não apenas suas strings. A corrida armamentista fractal que os comentários do HN brincaram em prever, com ofuscadores ajustados contra modelos e modelos ajustados contra ofuscadores, já está aqui; só está distribuída de forma desigual.
A defesa durável é a parte entediante, e ela não mudou desde os primeiros packers dos anos noventa: análise dinâmica, evidência comportamental e execução em sandbox. Opacidade nunca foi uma propriedade de segurança. Era uma estratégia de deslocamento de custo, e o custo acabou voltando para quem o deslocou. Se seu pipeline de detecção pode ser derrubado por uma string que incomoda um modelo de linguagem, o pipeline sempre foi a vulnerabilidade.


