Quebrando o CAPTCHA do 4Chan
(nullpt.rs)- Projeto que criou um modelo em TensorFlow.js para resolver automaticamente o CAPTCHA do 4Chan no navegador, alcançando a meta mínima de 80% e a meta desejada de mais de 90% de precisão
- A coleta de CAPTCHAs era difícil de fazer com scraping em massa simples por causa de
ttl,cd, do Cloudflare Turnstile e do tempo de espera progressivamente maior - Soluções comerciais baseadas em humanos e rotulagem manual foram limitadas por erros e throttling, então foram geradas cerca de 50.000 imagens de dados sintéticos com aproximadamente 2.500 fundos reais e 50–150 imagens por caractere para garantir escala de treino
- O modelo usou arquitetura CNN+LSTM e codificação CTC, e após treino com Keras/TensorFlow foi convertido para TensorFlow.js passando por Python 3.10, Keras 2 e formato
.h5 - No navegador real, o primeiro carregamento levava cerca de 1 segundo e depois a execução parecia instantânea, mostrando taxa de sucesso acima de 90% em centenas de CAPTCHAs reais
Objetivo e código público
- O objetivo era, ao mesmo tempo, aprender machine learning e TensorFlow e criar um modelo que resolvesse com confiabilidade o CAPTCHA do 4Chan no navegador
- A referência era no mínimo 80% de precisão e, se possível, mais de 90%, o que acabou sendo alcançado
- O código relacionado foi publicado no GitHub em 4chan-captcha-playground
Como funciona o CAPTCHA do 4Chan
- O 4Chan exige entrada de CAPTCHA antes de publicar um post ou resposta
- O CAPTCHA normal é uma imagem com 5–6 caracteres alfanuméricos, e o usuário precisa digitar todos corretamente
- O CAPTCHA de slider funciona alinhando uma imagem de fundo que parece formada por fragmentos aleatórios de caracteres com uma imagem de frente com um buraco transparente, revelando assim o texto do CAPTCHA
Limitações encontradas na coleta de CAPTCHA
- Ao observar uma nova requisição de CAPTCHA, o navegador envia uma chamada para
https://sys.4chan.org/captcha?framed=1&board={board} - Ao remover
framed=1, retorna JSON bruto em vez depostMessage()dentro de HTML- O JSON inclui
challenge,ttl,cd,img,img_width,img_height,bg,bg_widthe outros campos ttlparece indicar quando o CAPTCHA expira, cerca de 2 minutos depoiscdfoi interpretado como o valor de cooldown até a próxima requisição de CAPTCHA
- O JSON inclui
- Ao enviar requisições em sequência,
cdcresce gradualmente- Nas primeiras vezes, era possível pedir um CAPTCHA a cada 5 segundos
- Depois isso aumentava para 8 segundos e continuava crescendo aproximadamente em dobro
- No fim, atingia um teto de 280 segundos
- Depois de chegar ao temporizador de 280 segundos, o CAPTCHA ficava mais difícil
- Passavam a aparecer imagens com várias linhas horizontais e elementos ovais de interferência
- A qualidade dos dados caía, mas eles ainda podiam ser usados
- Antes de requisitar o CAPTCHA, era necessário passar pelo Cloudflare Turnstile
- Usar muitos proxies e scripts simples não era realista
- O script de coleta copiava os cookies do Cloudflare do navegador e os reutilizava, substituindo-os manualmente quando expiravam
- Com esse método, foram coletadas algumas centenas de CAPTCHAs, mas isso ainda não era suficiente para o treino e também não havia rótulos de resposta
Limites da rotulagem baseada em humanos
- O alinhamento do CAPTCHA de slider mostrou 100% de sucesso com um script heurístico em
trainer/captcha_aligner.py - Foi escrito
trainer/labeler.pypara enviar CAPTCHAs a um serviço comercial de resolução, onde pessoas reais digitavam as respostas - Nas primeiras dezenas de CAPTCHAs enviadas, a maioria era resolvida com pelo menos um caractere errado
- Foi usada a função “100% Recognition” do serviço para receber resultados apenas quando várias respostas de trabalhadores coincidissem
- Os parâmetros eram
n = 2,x = 2,y = 3 - Primeiro enviava para 2 pessoas e, se não houvesse concordância, enviava para até mais 3 até que duas respostas coincidissem
- Os parâmetros eram
- Com essa configuração, cerca de 80% dos CAPTCHAs foram resolvidos e, entre eles, cerca de 90% estavam corretos, mas aproximadamente 10% ainda tinham erro
- Havia casos em que vários trabalhadores cometiam o mesmo erro
- Também foi usado um userscript para resolver CAPTCHAs manualmente, pelo próprio autor ou com ajuda de conhecidos, salvando imagem e resposta
- Isso rendeu mais algumas centenas de imagens para o conjunto de treino
- A abordagem foi interrompida por causa do throttling de requisições repetidas e do aumento de dificuldade do CAPTCHA
Geração de dados sintéticos
- O 4Chan e o código desse CAPTCHA não são open source, então não era possível executar localmente o mesmo código
- Em vez disso, foram gerados CAPTCHAs sintéticos que aproximavam a estrutura dos reais
- O CAPTCHA foi tratado em duas partes: fundo e caracteres
- O fundo foi obtido encontrando grandes contours em imagens reais e removendo a área dos caracteres
- Depois da remoção dos caracteres, restava apenas o fundo com ruído
- Os caracteres individuais foram obtidos por rotulagem manual
- VoTT foi usado para marcar os caracteres
- Um script simples extraía os caracteres e fazia o pós-processamento
- Foram obtidas de 50 a 150 imagens isoladas por caractere
- O CAPTCHA do 4Chan contém apenas
0, 2, 4, A, D, G, H, J, K, M, N, P, R, S, T, W, X, Y- Isso provavelmente foi escolhido para evitar ambiguidades
- Os caracteres extraídos e os fundos foram combinados para gerar imagens sintéticas, seguindo os padrões de posicionamento observados
- Como os caracteres de entrada já estavam rotulados, as respostas corretas dos CAPTCHAs sintéticos também podiam ser geradas automaticamente
Arquitetura do modelo e pré-processamento
- Os dados de treino misturavam CAPTCHAs de slider previamente alinhados, CAPTCHAs normais e CAPTCHAs sintéticos
- O script de treino ajustava todas as imagens para 300x80 pixels e as convertia para preto e branco puro
- O modelo usou uma arquitetura LSTM CNN montada com base em vários textos sobre resolução de CAPTCHA
- Foram usadas 3 camadas de convolution/max-pooling
- Foram usadas 2 camadas LSTM
- Uma 4ª camada de convolution também foi testada, mas não melhorou o desempenho
- Como o comprimento de saída era variável, com 5 ou 6 caracteres, foi usada codificação CTC
- A implementação usou Keras e TensorFlow
Problema com a ordem dos argumentos de tf.image.resize()
- Alguns CAPTCHAs antigos de slider alinhados não se encaixavam na resolução 300x80 nem na proporção esperada
- O script de treino passou a usar
tf.image.resize()para lidar com entradas variadas - Inicialmente, foi assumido que o argumento de tamanho era uma tupla
(width, height), mas na práticatf.image.resize()exige a ordem(height, width) - Esse erro fazia a imagem ficar 80x300, esticada verticalmente e ilegível
- Mesmo após treinar por mais de 32 epochs, o desempenho em imagens vistas era quase nulo
- Em CAPTCHAs novos, as previsões ficavam próximas do aleatório
- O problema foi identificado ao visualizar as imagens de entrada já processadas, e depois da correção o desempenho do treino melhorou bastante
Escala do treino e resultados
- O dataset final foi composto por cerca de 500 imagens resolvidas manualmente e cerca de 50.000 imagens sintéticas
- As imagens sintéticas foram geradas com amostragem aleatória a partir de cerca de 2.500 imagens de fundo e 50–150 imagens por caractere
- O dataset foi embaralhado aleatoriamente e dividido em proporção 90/10 entre treino e avaliação
- Em uma NVIDIA RTX A4000 Laptop GPU, o tempo de treino por epoch era de cerca de 45 segundos
- Ao fim do primeiro epoch, a loss estava em torno de 19 e as previsões quase sempre erravam
- Ao fim do 4º epoch, a loss caiu para 0,55 e 5 de 5 previsões em testes aleatórios estavam corretas
- Entre 8 e 16 epochs parecia haver o melhor equilíbrio entre tempo e desempenho final
- Por volta do 8º epoch, a loss se estabilizava
- Acima de 16 epochs, a melhora adicional diminuía bastante
- A inferência foi testada em Python com
trainer/infer.py, com resultados promissores mesmo em imagens não vistas
Conversão para TensorFlow.js e execução no navegador
- O userscript foi escrito com TensorFlow.js e TypeScript
- O algoritmo de alinhamento de CAPTCHA do código Python e o pré-processamento de imagem foram reimplementados
- O código relacionado está no diretório
user-scripts/do repositório - O formato do modelo Python TensorFlow/Keras não é compatível com o formato esperado pelo TensorFlow.js
- Foi necessário usar o script oficial de conversão, mas surgiram dois problemas
- O conversor oficial de TensorFlow para TFJS não funcionava no Python 3.12 e a mensagem de erro não era clara
- Ao usar Python 3.10 com PyEnv, a conversão passou a funcionar
- O script de conversão conseguia transformar modelos Keras 3 para o formato do TensorFlow.js, mas o TensorFlow.js não conseguia realmente ler o modelo convertido
- Esse problema foi confirmado em um post no fórum
- A solução foi usar Keras 2
- Foi instalado o pacote legado
tf_keras - A variável de ambiente
TF_USE_LEGACY_KERAS=1foi definida no treino - O modelo foi exportado no formato legado
.h5e o formato de entrada foi especificado no script de conversão - A mudança no código exigiu apenas uma linha simples
- Foi instalado o pacote legado
Desempenho em CAPTCHAs reais do 4Chan
- O modelo também funcionou bem em CAPTCHAs reais do 4Chan
- O primeiro carregamento do modelo levava cerca de 1 segundo
- Depois disso, a execução parecia instantânea
- Com base na experiência resolvendo centenas de CAPTCHAs reais no navegador, a taxa de sucesso foi superior a 90%
- Erros no reconhecimento dos caracteres em si eram raros; quando falhava, normalmente um caractere inteiro era omitido
- Ainda há espaço para melhorar com mais treino em dados reais ou ajustando o layout do gerador de dados sintéticos
- A precisão desse modelo foi muito superior à de serviços comerciais de resolução de CAPTCHA baseados em humanos
CAPTCHA de 4 caracteres e encerramento
- Depois da conclusão do projeto, enquanto o texto era escrito e editado, o 4Chan começou às vezes a oferecer CAPTCHAs de 4 caracteres
- O modelo havia sido treinado apenas com CAPTCHAs de 5 e 6 caracteres, mas mostrou desempenho semelhante também nos de 4 caracteres
- Ao longo do projeto, houve muito aprendizado em machine learning e visão computacional, e o objetivo inicial de criar um modelo de resolução de CAPTCHA no navegador foi alcançado
1 comentários
Opiniões no Hacker News
A parte em que a integração entre Keras e TensorFlow.js é uma bagunça parece bem típica do TensorFlow
Sempre que eu usava TensorFlow, a impressão era menos de um produto integrado e polido e mais de um monte de ferramentas mais ou menos relacionadas colocadas sob o mesmo guarda-chuva
Na verdade, dá até para dizer que todas as bibliotecas e ferramentas open source do Google passam essa sensação
A resposta para “por que decidiram fundir o Keras ao TensorFlow em 2019?” foi: “Não fui eu que decidi. Foi uma decisão tomada pelos líderes do TF em 2018; na época eu era um contribuidor individual L5, e aquilo era uma decisão L8”
Eu precisava de um CAPTCHA para impedir spam no formulário de comentários do meu site[0], então reaproveitei uma abordagem divertida que tinha visto antes
Não é nem de longe perfeito nem difícil, mas gostei muito do processo de criação
[0] https://www.hybridlogic.co.uk/contact
https://vivirenremoto.github.io/doomcaptcha/
Há um motivo pelo qual as pessoas se afastaram dos CAPTCHAs baseados em texto distorcido
Estamos quase no ponto em que computadores os resolvem melhor que humanos
https://www.usenix.org/system/files/conference/woot14/woot14... é um artigo sobre esse tema, e acho bem interessante
Ainda assim, uma quantidade surpreendente de CAPTCHAs baseados em texto pode ser resolvida com um shell script de poucas linhas que usa ImageMagick para converter para escala de cinza, aplica dilatação e erosão, e depois passa para o Tesseract
Mas também existem sites como https://2captcha.net, então, no fim, CAPTCHA é mais um mecanismo que exige um pequeno esforço mínimo
A solução deste artigo também exigiu bastante tempo, técnica e esforço, e o resultado não generaliza bem; para outro tipo de CAPTCHA, seria preciso começar do zero
A maioria dos spammers não consegue reproduzir isso, e quem consegue provavelmente pode ganhar dinheiro de forma legítima ou mirar alvos mais lucrativos
Esse tipo de CAPTCHA ainda funciona bem para elevar o custo de um spam bem-sucedido acima da receita esperada
Será que daria para criar um fórum em que todos os membros precisassem fazer uma entrevista por vídeo de 15 minutos com um moderador? Sei que “não escala”, mas como uma espécie de pegadinha engraçada parece possível
Não é uma solução; é como uma pequena fortaleza que vai ficando obsoleta aos poucos
Segundo o link, o reCAPTCHA v3 leva de 10 a 15 segundos e custa US$ 1,30 por 1000 CAPTCHAs
Para muitas tarefas em que se quer contornar CAPTCHA, como raspar um grande site em escala, esse custo se torna realmente considerável e difícil de bancar
mCaptcha.org é um deles, e há outras implementações
CAPTCHAs tradicionais, quando são minimamente eficazes, tendem a virar um pesadelo de acessibilidade
Se você se interessa por esse tema, também há uma análise do Silk Road CAPTCHA que eu organizei em 2014: https://github.com/mieko/sr-captcha
A resposta do 4chan parece adequada
Como, de todo modo, é fácil resolver com redes neurais, eles estão simplificando a tarefa para humanos
Hoje, mesmo projetando um CAPTCHA muito difícil, é pouco provável que ele se torne difícil para máquinas resolverem; é mais provável que só irrite mais as pessoas
https://4chan.org/pass
Ele já é oferecido como opção para postar sem CAPTCHA
Se o CAPTCHA é completamente inútil, a conclusão que se segue é eliminar o CAPTCHA e a postagem gratuita, e exigir que todos que queiram postar comprem um 4chan Pass
Recentemente introduziram um atraso de 15 minutos para postar, e isso dá muita raiva
Tive que colocar o 4chan na lista de permissões do Cookie AutoDelete
Talvez seja melhor fingir que há um CAPTCHA, mas na prática analisar o timing e o comportamento do usuário
Sinceramente, também acho que isso já deve estar acontecendo
Indo totalmente para o lado meta, também daria para treinar uma IA para decidir se o agente do outro lado é uma pessoa ou não
Ou seja, seria inventar um teste de Turing reverso, em que a pessoa é considerada humana se a IA não conseguir distingui-la de uma resposta humana normal
A diferença é que não se trata de distinguir de respostas humanas voltadas a marketing
Só de pensar nisso já estou meio enjoado e preciso me deitar
Antes mesmo de exibir o CAPTCHA, eles primeiro identificam fingerprint de TLS, IP, HTTP/2, requisição, ambiente JavaScript, capacidade de renderização de fontes e imagens e o próprio navegador
Com essas informações, calculam uma pontuação de confiança e decidem se vão mostrar um CAPTCHA para começo de conversa
Só depois disso é que faz sentido analisar a entrada do CAPTCHA, mas, a essa altura, 90% dos bots já foram pegos
A quantidade de informação que um navegador consegue informar ao servidor sem nenhuma percepção do usuário é absurda, então é bem possível que a impressão digital digital de cada um de nós seja mais única do que uma impressão digital física
Acho que o caso original, ou pelo menos um dos pioneiros, de quebrar o CAPTCHA do 4chan ainda é o Yannick Kilcher fazendo fine-tuning do GPT-J com o dataset “Raiders of the Lost Kek”
Talvez seja um dos exemplos mais legais de uso de modelos de linguagem grandes em vídeo: https://youtu.be/efPrtcLdcdM?si=errY0PrEhnX9ylDw
É digno de registro
Quase desisti de tentar brincar um pouco com machine learning alguns anos atrás por coisas como “o conversor oficial de modelos TensorFlow para TFJS não funciona no Python 3.12 e nem é documentado direito” e “TensorFlow.js não dá suporte ao Keras 3”
Tutoriais recentes muitas vezes já estavam obsoletos, havia muitas armadilhas aleatórias, e era chocante como guias de “primeiros passos” já presumiam que você era especialista
É melhor aprender os fundamentos com um livro-texto antigo de estatística bayesiana e depois passar para um framework principal como PyTorch
No começo, é bom implementar você mesmo CNNs, RNNs, arquiteturas Transformer e todas as partes do pipeline de treinamento
Incluindo até o data loader, mas pode deixar de fora os kernels de matrizes em CUDA
É melhor ficar longe de wrappers que embrulham wrappers de terceiros, como LangChain
Muitas vezes a documentação não está apenas desatualizada; ela está simplesmente errada sobre fundamentos
Hugging Face é excelente se você conhece o básico e consegue consertar quando o wrapper padrão quebra
Isso é parecido com passar algumas horas aprendendo como abrir a tampa de uma fossa séptica
Se você seguir o link de serviços de resolução de CAPTCHA, dá para ler os perfis das pessoas que fazem esse trabalho
Eles são divulgados como algo mais ético do que trabalhar em fábricas perigosas