Décrypter le CAPTCHA de 4chan
(nullpt.rs)- Projet consistant à créer un modèle TensorFlow.js pour résoudre automatiquement les CAPTCHA de 4chan dans le navigateur, avec l’objectif minimal de 80 % de précision et l’objectif préféré de plus de 90 % finalement atteints
- La collecte des CAPTCHA était difficile à automatiser par simple scraping de masse à cause de
ttl,cd, de Cloudflare Turnstile et d’un temps d’attente croissant entre les requêtes - Les services commerciaux de résolution par des humains et l’annotation manuelle se heurtaient à des erreurs et au throttling ; l’auteur a donc généré environ 50 000 données synthétiques à partir d’environ 2 500 arrière-plans réels et de 50 à 150 images par caractère pour obtenir une échelle d’entraînement suffisante
- Le modèle utilise une architecture CNN+LSTM et un encodage CTC ; après entraînement avec Keras/TensorFlow, il a été converti vers TensorFlow.js via Python 3.10, Keras 2 et le format
.h5 - Dans le navigateur réel, le premier chargement prenait environ 1 seconde, puis l’exécution devenait quasi instantanée, avec un taux de réussite supérieur à 90 % sur plusieurs centaines de CAPTCHA réels
Objectif et code publié
- L’objectif était de créer un modèle capable de résoudre de façon fiable les CAPTCHA de 4chan dans le navigateur, tout en apprenant le machine learning et TensorFlow
- Le critère visé était une précision minimale de 80 %, avec si possible plus de 90 %, objectif finalement atteint
- Le code associé est publié sur GitHub dans 4chan-captcha-playground
Fonctionnement du CAPTCHA de 4chan
- 4chan exige la saisie d’un CAPTCHA avant de publier un message ou une réponse
- Le CAPTCHA classique est une image contenant 5 à 6 caractères alphanumériques, que l’utilisateur doit tous saisir correctement
- Le CAPTCHA à curseur fonctionne en alignant une image d’arrière-plan qui ressemble à des fragments de caractères aléatoires avec une image de premier plan comportant un trou transparent, afin de faire apparaître le texte du CAPTCHA
Limitations rencontrées lors de la collecte des CAPTCHA
- En observant les requêtes de nouveaux CAPTCHA, le navigateur envoie une requête vers
https://sys.4chan.org/captcha?framed=1&board={board} - En supprimant
framed=1, on obtient du JSON brut à la place dupostMessage()intégré dans le HTML- Le JSON contient notamment
challenge,ttl,cd,img,img_width,img_height,bg,bg_width ttlsemble être la durée avant expiration du CAPTCHA, environ 2 minutes plus tardcdest interprété comme la valeur de cooldown à attendre avant de pouvoir demander le CAPTCHA suivant
- Le JSON contient notamment
- Lorsque les requêtes s’enchaînent,
cdaugmente progressivement- Au début, il est possible d’envoyer une requête toutes les 5 secondes
- Puis cela passe à 8 secondes et continue ensuite à augmenter en doublant à peu près
- Enfin, la valeur atteint un plafond à 280 secondes
- Une fois ce minuteur de 280 secondes atteint, les CAPTCHA deviennent plus difficiles
- Des images apparaissent avec plusieurs lignes horizontales et des éléments elliptiques parasites
- La qualité des données baisse, mais elles restent exploitables
- Avant de demander un CAPTCHA, il faut aussi passer Cloudflare Turnstile
- Une approche reposant sur de nombreux proxys et de simples scripts n’était pas réaliste
- Le script de collecte copiait les cookies Cloudflare depuis le navigateur et les remplaçait manuellement lorsqu’ils expiraient
- Cette méthode a permis de collecter plusieurs centaines de CAPTCHA, mais cela restait insuffisant pour l’entraînement et il n’y avait pas de labels de réponse
Les limites de l’annotation humaine
- L’alignement des CAPTCHA à curseur obtenait un taux de réussite de 100 % grâce à un script heuristique dans
trainer/captcha_aligner.py - L’auteur a écrit
trainer/labeler.pypour envoyer les CAPTCHA à un service commercial de résolution, où de vraies personnes saisissaient les réponses - Sur les premières dizaines de CAPTCHA envoyés, la plupart étaient résolus avec au moins un caractère erroné
- La fonctionnalité « 100% Recognition » du service a été utilisée pour ne recevoir un résultat que lorsque plusieurs travailleurs donnaient la même réponse
- Les paramètres étaient
n = 2,x = 2,y = 3 - Le CAPTCHA était d’abord envoyé à 2 personnes ; si leurs réponses ne correspondaient pas, il pouvait être envoyé jusqu’à 3 personnes supplémentaires jusqu’à ce que deux réponses coïncident
- Les paramètres étaient
- Avec ce réglage, environ 80 % des CAPTCHA étaient résolus, et parmi eux environ 90 % étaient corrects, mais environ 10 % contenaient encore des erreurs
- Il arrivait que plusieurs travailleurs commettent la même erreur
- Un script utilisateur a aussi été utilisé pour résoudre les CAPTCHA manuellement, seul ou avec l’aide de proches, puis enregistrer l’image et la bonne réponse
- Cela a permis d’ajouter plusieurs centaines d’images supplémentaires au jeu d’entraînement
- Cette approche a été abandonnée à cause du throttling des requêtes répétées et de la hausse de difficulté des CAPTCHA
Génération de données synthétiques
- 4chan et le code de son CAPTCHA n’étant pas open source, il n’était pas possible d’exécuter exactement le même code en local
- À la place, l’auteur a généré des CAPTCHA synthétiques qui reproduisaient approximativement la structure des vrais
- Le CAPTCHA a été traité comme deux éléments distincts : l’arrière-plan et les caractères
- L’arrière-plan a été obtenu en trouvant de grands contour dans les images réelles pour supprimer la zone des caractères
- Une fois les caractères retirés, il ne restait qu’un fond bruité
- Les caractères individuels ont été obtenus par annotation manuelle
- Les caractères ont été tagués avec VoTT
- Un script simple a ensuite servi à les extraire et à les post-traiter
- Entre 50 et 150 images isolées ont été obtenues pour chaque caractère
- Les CAPTCHA de 4chan ne contiennent que
0, 2, 4, A, D, G, H, J, K, M, N, P, R, S, T, W, X, Y- Il s’agit probablement d’un choix destiné à éviter les ambiguïtés
- Les caractères extraits et les arrière-plans ont ensuite été combinés pour produire des images synthétiques correspondant aux motifs de placement observés
- Comme les caractères d’entrée étaient déjà annotés, les réponses correctes des CAPTCHA synthétiques pouvaient elles aussi être générées automatiquement
Architecture du modèle et prétraitement
- Les données d’entraînement mélangeaient des CAPTCHA à curseur préalablement alignés, des CAPTCHA classiques et des CAPTCHA synthétiques
- Le script d’entraînement redimensionnait toutes les images en 300x80 pixels et les convertissait en noir et blanc pur
- Le modèle suivait une architecture de type LSTM CNN, construite en s’inspirant de plusieurs articles sur la résolution de CAPTCHA
- 3 couches convolution/max-pooling étaient utilisées
- 2 couches LSTM étaient utilisées
- Une 4e couche convolution avait aussi été testée, sans amélioration des performances
- Comme la sortie pouvait faire 5 ou 6 caractères, un encodage CTC a été utilisé
- L’implémentation reposait sur Keras et TensorFlow
Problème d’ordre des arguments de tf.image.resize()
- Certains anciens CAPTCHA à curseur alignés ne correspondaient ni à la résolution 300x80 ni au bon ratio
- Le script d’entraînement utilisait
tf.image.resize()pour gérer ces entrées variées - Au départ, l’auteur avait supposé que les dimensions devaient être passées sous forme de tuple
(width, height), alors quetf.image.resize()attend en réalité(height, width) - Cette erreur étirait les images verticalement en 80x300, les rendant illisibles
- Même après plus de 32 epochs d’entraînement, les performances restaient très faibles sur les images observées
- Sur les nouveaux CAPTCHA, les prédictions étaient proches du hasard
- Le problème a été identifié en visualisant les images traitées en entrée, puis corrigé, ce qui a nettement amélioré les performances d’entraînement
Taille de l’entraînement et résultats
- Le jeu de données final comprenait environ 500 images résolues manuellement et environ 50 000 images synthétiques
- Les images synthétiques étaient générées par échantillonnage aléatoire à partir d’environ 2 500 images de fond et de 50 à 150 images pour chaque caractère
- Le jeu de données était mélangé aléatoirement puis séparé en ensembles d’entraînement et d’évaluation selon un ratio 90/10
- Sur un NVIDIA RTX A4000 Laptop GPU, le temps d’entraînement par epoch était d’environ 45 secondes
- À la fin du premier epoch, la loss tournait autour de 19 et les prédictions étaient presque toutes fausses
- À la fin du 4e epoch, la loss était tombée à 0,55, et 5 prédictions sur 5 lors d’un test aléatoire étaient correctes
- La plage de 8 à 16 epochs représentait un bon compromis entre temps passé et performances finales
- Vers le 8e epoch, la loss se stabilisait
- Au-delà de 16 epochs, les gains devenaient très limités
- L’inférence a été testée en Python avec
trainer/infer.py, avec des résultats prometteurs même sur des images jamais vues
Conversion vers TensorFlow.js et exécution dans le navigateur
- Le script utilisateur a été écrit avec TensorFlow.js et TypeScript
- L’algorithme d’alignement des CAPTCHA et le code de prétraitement d’image du script Python ont été réimplémentés
- Le code correspondant se trouve dans le répertoire
user-scripts/du dépôt - Le format de modèle Python TensorFlow/Keras n’est pas compatible avec celui attendu par TensorFlow.js
- Il a donc fallu utiliser le script de conversion officiel, mais deux problèmes se sont posés
- Le convertisseur officiel TensorFlow-vers-TFJS ne fonctionnait pas avec Python 3.12, sans message d’erreur vraiment explicite
- En utilisant Python 3.10 avec PyEnv, la conversion a réussi
- Le script de conversion pouvait convertir les modèles Keras 3 au format TensorFlow.js, mais TensorFlow.js était ensuite incapable de réellement les lire
- Ce problème a été confirmé via ce forum post
- La solution a été d’utiliser Keras 2
- Le paquet legacy
tf_kerasa été installé - La variable d’environnement
TF_USE_LEGACY_KERAS=1a été définie pour l’entraînement - Le modèle a été exporté dans l’ancien format
.h5, puis ce format d’entrée a été indiqué au script de conversion - Le changement dans le code s’est limité à une simple modification d’une ligne
- Le paquet legacy
Performances sur les vrais CAPTCHA de 4chan
- Le modèle fonctionnait bien aussi sur les vrais CAPTCHA de 4chan
- Le premier chargement du modèle prenait environ 1 seconde
- Les exécutions suivantes étaient perçues comme quasi instantanées
- D’après l’expérience accumulée sur plusieurs centaines de CAPTCHA réels dans le navigateur, le taux de réussite dépassait 90 %
- Les erreurs de reconnaissance de caractères étaient rares ; lorsque le modèle se trompait, il omettait généralement un caractère entier
- Il y aurait encore une marge d’amélioration en ajoutant davantage de données réelles à l’entraînement ou en ajustant la disposition des CAPTCHA dans le générateur synthétique
- La précision du modèle était bien supérieure à celle des services commerciaux de résolution humaine de CAPTCHA
CAPTCHA à 4 caractères et conclusion
- Après la fin du projet, pendant la rédaction et l’édition de l’article, 4chan a commencé à proposer parfois des CAPTCHA à 4 caractères
- Le modèle n’avait été entraîné que sur des CAPTCHA à 5 et 6 caractères, mais il montrait un niveau de performance similaire aussi sur les CAPTCHA à 4 caractères
- Le projet a permis d’apprendre beaucoup sur le machine learning et la vision par ordinateur, tout en atteignant l’objectif initial : créer un modèle de résolution de CAPTCHA dans le navigateur
1 commentaires
Réactions sur Hacker News
L’intégration entre Keras et TensorFlow.js est bancale, ce qui est très typique de TensorFlow
Quand on utilise TensorFlow, on a toujours eu l’impression non pas d’un produit intégré et fluide, mais d’un ensemble d’outils vaguement liés rassemblés sous une même bannière
On pourrait même dire que c’est l’impression que donnent tous les bibliothèques open source et outils de Google
À la question « Pourquoi avoir décidé en 2019 de fusionner Keras avec TensorFlow ? », la réponse était : « Ce n’est pas moi qui ai pris cette décision. C’était une décision prise en 2018 par les responsables de TF, et à l’époque j’étais contributeur individuel L5, tandis que c’était une décision de niveau L8 »
J’avais besoin d’un CAPTCHA pour empêcher le spam sur le formulaire de commentaires de mon site[0], et j’ai réutilisé une méthode amusante que j’avais vue auparavant
Ce n’est absolument ni parfait ni difficile, mais j’ai vraiment adoré le processus de création
[0] https://www.hybridlogic.co.uk/contact
https://vivirenremoto.github.io/doomcaptcha/
Il y a une raison pour laquelle les gens se sont éloignés des CAPTCHA à texte déformé
On est presque arrivé au point où les ordinateurs les résolvent mieux que les humains
https://www.usenix.org/system/files/conference/woot14/woot14... est un article sur le sujet, que je trouve assez intéressant
Malgré tout, un nombre surprenant de CAPTCHA textuels peuvent être résolus avec quelques lignes de shell script qui convertissent l’image en niveaux de gris avec ImageMagick, appliquent des opérations de dilatation et d’érosion, puis l’envoient à Tesseract
Mais il existe aussi des sites comme https://2captcha.net, donc au final un CAPTCHA ressemble surtout à un mécanisme qui exige un minimum de petit effort
La solution présentée dans cet article a demandé beaucoup de temps, de compétences et d’efforts, et le résultat se généralise mal : pour un autre type de CAPTCHA, il faudrait tout recommencer depuis le début
La plupart des spammeurs ne pourront pas le reproduire, et ceux qui le peuvent ont de bonnes chances de gagner de l’argent légalement ou de viser des cibles plus rentables
Ce type de CAPTCHA fonctionne donc encore très bien pour faire monter le coût d’un spam réussi au-dessus du revenu attendu
Pourrait-on créer un forum où tous les membres doivent passer un entretien vidéo de 15 minutes avec l’administrateur ? Je sais que « ce n’est pas scalable », mais comme dispositif de plaisanterie absurde, ça semble possible
Ce n’est pas une solution, juste une petite forteresse qui se dégrade peu à peu
D’après le lien, reCAPTCHA v3 prend 10 à 15 secondes et coûte 1,3 dollar pour 1 000 CAPTCHA
Pour beaucoup de tâches qui cherchent à contourner des CAPTCHA, comme le scraping massif de gros sites, ce coût devient réellement important et difficile à absorber
mCaptcha.org en est un exemple, et il existe d’autres implémentations
Les CAPTCHA traditionnels, dès qu’ils deviennent un tant soit peu efficaces, tournent facilement au cauchemar du point de vue de l’accessibilité
Si ce sujet vous intéresse, j’ai aussi une analyse du CAPTCHA de Silk Road que j’avais rédigée en 2014 : https://github.com/mieko/sr-captcha
La réponse de 4chan semble appropriée
Puisque les réseaux neuronaux les résolvent de toute façon facilement, autant simplifier la tâche donnée aux humains
Désormais, même si l’on conçoit des CAPTCHA très difficiles, il est peu probable qu’ils deviennent plus durs pour les machines ; en revanche, ils risquent surtout d’agacer davantage les humains
https://4chan.org/pass
C’est déjà proposé comme option pour poster sans CAPTCHA
Si les CAPTCHA sont totalement devenus inefficaces, la conclusion logique serait de supprimer les CAPTCHA et la publication gratuite, et d’exiger de tous ceux qui veulent poster qu’ils achètent un 4chan Pass
Ils ont récemment introduit un délai de publication de 15 minutes, et c’est vraiment exaspérant
J’ai dû mettre 4chan sur la liste blanche dans Cookie AutoDelete
Je me demande s’il ne vaudrait pas mieux faire semblant d’avoir un CAPTCHA tout en analysant en réalité le timing et le comportement de l’utilisateur
Honnêtement, j’ai l’impression que c’est probablement déjà le cas
En poussant l’idée jusqu’au bout, on pourrait même entraîner une IA à déterminer si l’acteur en face est un humain ou non
Autrement dit, ce serait l’invention d’un test de Turing inversé : si l’IA n’arrive pas à distinguer la réponse d’une personne de celle d’un humain « normal », on considère que c’est un humain
La différence, c’est qu’il ne s’agirait pas de distinguer des réponses humaines destinées au marketing
Rien que d’y penser, ça me donne un peu la nausée, je vais m’allonger
Avant même d’afficher un CAPTCHA, ils identifient d’abord l’empreinte TLS, l’IP, HTTP/2, les requêtes, l’environnement JavaScript, la capacité de rendu des polices et des images, ainsi que le navigateur lui-même
Avec ces informations, ils calculent un score de confiance et décident s’ils doivent montrer un CAPTCHA dès le départ
Ce n’est qu’ensuite qu’analyser la saisie du CAPTCHA a du sens, mais à ce stade, 90 % des bots sont déjà arrêtés
La quantité d’informations qu’un navigateur peut transmettre au serveur sans aucune interaction est hallucinante, au point que notre empreinte numérique est probablement plus unique que notre vraie empreinte digitale
À mon avis, le cas fondateur du contournement du CAPTCHA de 4chan reste celui où Yannick Kilcher a affiné GPT-J sur le dataset « Raiders of the Lost Kek »
C’est peut-être l’un des usages de grand modèle de langage les plus impressionnants montrés en vidéo : https://youtu.be/efPrtcLdcdM?si=errY0PrEhnX9ylDw
C’est un record
À cause de choses comme « le convertisseur officiel de modèles TensorFlow vers TFJS ne fonctionne pas sous Python 3.12 et n’est pas correctement documenté » ou « TensorFlow.js ne prend pas en charge Keras 3 », j’ai failli abandonner il y a quelques années en essayant simplement de toucher un peu au machine learning
Trop de tutoriels récents étaient déjà obsolètes, il y avait trop de pièges aléatoires, et il était frappant de voir à quel point les guides « getting started » supposaient déjà qu’on était un expert
Mieux vaut apprendre les bases avec un vieux manuel de statistiques bayésiennes, puis passer à un framework majeur comme PyTorch
Au début, il vaut mieux écrire soi-même toutes les parties des architectures CNN, RNN et Transformer ainsi que du pipeline d’entraînement
Y compris le data loader, mais sans aller jusqu’aux noyaux matriciels CUDA
Mieux vaut rester loin des wrappers qui re-encapsulent les wrappers de quelqu’un d’autre, comme LangChain
La documentation est souvent plus qu’ancienne : elle peut être carrément fausse sur les bases
Hugging Face est excellent si on maîtrise les fondamentaux et qu’on sait réparer un wrapper standard quand il casse
C’est un peu comme passer des heures à apprendre comment ouvrir le couvercle d’une fosse septique
Si on suit les liens vers les services de résolution de CAPTCHA, on peut lire le profil des gens qui font ce travail
C’est présenté comme étant plus éthique que de travailler dans une usine dangereuse