OpenAI montre des performances étonnantes pour restaurer du code à partir d’une version minifiée
(glama.ai)- Un développeur a collé dans ChatGPT un JavaScript minifié pour comprendre l’implémentation d’un composant d’art ASCII vu sur le web, et l’outil a déroulé le flux obfusqué en une structure lisible
- À l’origine, il s’agissait d’un composant React qui calculait les colonnes et les lignes selon la taille de la fenêtre, puis mettait à jour une grille de chaînes à 60 fps avec
setInterval - ChatGPT a expliqué la sélection du jeu de caractères, le calcul des caractères selon les coordonnées et le temps, ainsi que le flux de mise à jour de
div.textContent, avant de le réécrire en code TypeScript/React équivalent - Au départ, l’absence de caractères de bloc comme
░▒▓█a fait penser à une implémentation différente, mais une nouvelle vérification du script d’origine a montré qu’il s’agissait en fait d’un problème d’encodage lors du copier-coller - Une fois le bon jeu de caractères rétabli, le rendu est devenu identique à celui du composant d’origine, montrant qu’on peut utiliser ChatGPT de façon pratique pour comprendre et réimplémenter du code minifié
Retrouver une implémentation d’art ASCII dans du JavaScript minifié
- L’auteur a examiné le code source pour voir comment était implémenté le bloc d’art ASCII de la page reactive.network hackathon
- Le code concerné était minifié, donc difficile à lire directement pour un humain ; en temps normal, il aurait fallu le décoder à la main ou retrouver un fichier
.mappour restaurer la source originale - Cette fois, il a collé l’intégralité du code minifié dans ChatGPT et lui a demandé d’expliquer son fonctionnement
La structure révélée par le code minifié
- Le code utilisait JavaScript et React pour générer un contenu textuel dynamique
- Les principaux éléments étaient les suivants
- des alias très courts pour des fonctions mathématiques comme
Math.floor,Math.absetMath.min - un jeu de caractères composé de
"reactive.network REACTIVE.NETWORK"ainsi que de caractères spéciaux et de caractères de bloc - la sélection du jeu de caractères à utiliser selon le résultat de
Date.now() % 3 - le calcul de l’index du caractère à afficher à chaque coordonnée en fonction de la longueur du jeu de caractères choisi
- des alias très courts pour des fonctions mathématiques comme
- La fonction centrale,
eT, prenait des coordonnées de position et une valeur temporelle pour renvoyer un seul caractère à afficher- la valeur temporelle était convertie via
t.time * 8e-5 - les coordonnées étaient corrigées à l’aide de la plus petite valeur entre colonnes et lignes, ainsi que du ratio de l’écran
- l’index du caractère était déterminé en combinant le résultat de
Math.cos(position.x * position.x - position.y * position.y)avec la valeur temporelle
- la valeur temporelle était convertie via
Le mode de rendu du composant React
- Le composant utilisait
useRefpour référencer ledivdestiné à recevoir le texte, etuseStatepour stocker la height et la width de la fenêtre - Le premier
useEffectdéfinissait la taille initiale avecwindow.innerHeightetwindow.innerWidth, puis mettait à jour ces dimensions à chaque événementresize - Le second
useEffectcalculait la taille de la grille ASCII à partir des dimensions de l’écrancolsvalaitMath.floor(width / 12) * 1.6rowsvalaitMath.floor(height / 12)aspectvalaitcols / rows
setIntervals’exécutait toutes les1_000 / 60pour régénérer la chaîne à 60 fps- À chaque frame, le code parcourait toutes les lignes et colonnes, concaténait les caractères calculés, puis plaçait la chaîne finale dans
element.textContent
Réécriture en TypeScript plus facile à lire
- Le développeur a ensuite demandé une implémentation équivalente en TypeScript, mais rendue lisible pour un humain
- Le code produit par ChatGPT traduisait le rôle du code minifié à l’aide des noms suivants
selectedCharacterSetcharacterSetLengthcalculateCharacter- le type
Size - le composant React
AsciiArtGenerator
- Les noms de variables obscurs ont été remplacés par des noms qui exposent l’intention de l’implémentation
calculateCharactercalcule le caractère à afficher à partir des coordonnées, des colonnes et lignes, du ratio d’écran et du tempstextRefpointe vers ledivqui contient réellement le textesizeetsetSizereprésentent l’état de la taille de la fenêtre
- Le code réécrit était bien plus lisible que la version minifiée d’origine, au point d’être suffisant pour apprendre comment le composant était implémenté
Malentendu sur des caractères manquants et problème d’encodage
- Lors de la comparaison initiale, on a d’abord eu l’impression que le LLM avait manqué certains détails d’implémentation
- En particulier, on pensait que des caractères comme
░▒▓█, visibles dans le composant d’origine, manquaient dans le résultat de ChatGPT - Par la suite, un utilisateur du forum HN a suggéré qu’il pouvait s’agir d’un problème de copier-coller, et une vérification supplémentaire a montré que le code source original contenait des caractères différents de ceux collés dans ChatGPT
- En téléchargeant directement le script, il a été possible de récupérer les bons caractères, ce qui a confirmé un écart d’encodage comme cause du problème
- Après correction du jeu de caractères, le rendu est devenu identique à celui du composant d’origine
1 commentaires
Avis de Hacker News
Je suis la personne qui a créé HumanifyJS, et j’ai développé un outil basé sur les LLM spécifiquement pour cet usage
Il utilise les LLM au niveau de l’AST afin de garantir que le code continue de fonctionner après la désobfuscation : https://github.com/jehna/humanify
Je veux dire une fonction qui supprimerait tous les noms porteurs de sens dans du code ordinaire, puis choisirait de nouveaux noms à partir de l’algorithme et des noms restants, par exemple les noms intégrés
Quand je refactorise, je le fais parfois manuellement avec un LLM, et c’est utile pour trouver des termes plus standard comme
antiparallel_lineau lieu deparallel_line_opposite_direction, ou des noms plus généralisables commefind_instance_in_listau lieu defind_animal_instance_in_animalsHonnêtement, j’attends toujours un système de gestion de versions au niveau de l’AST
Est-ce que vous lui donnez toute la fonction et lui demandez « qu’est-ce qu’il faut renommer ? » en boucle jusqu’à ce que tout soit modifié, ou bien vous lui demandez tout d’un coup, puis vous parsez la sortie et vérifiez que l’AST correspond toujours ?
Je parle d’environ 50 000 lignes
Modification : je suis en train de le lancer en mode openai sur un fichier JS de seulement 1,2 k ligne, et après 20 minutes il n’en est qu’à 70 %. Même si, en théorie, ça fonctionnait sur un fichier de 50 000 lignes, j’ai l’impression qu’il vaudrait mieux ne pas essayer
La minification JS est assez mécanique et simple, donc l’opération inverse devrait elle aussi être relativement facile
Bien sûr, en général, c’est suffisamment pénible à faire à la main, mais la transformation elle-même est assez limitée, donc il est possible de lire le code avec seulement quelques notes pour suivre les identifiants obfusqués
La déminification ou la désobfuscation plus générale semble encore être un problème ouvert. J’ai écrit par le passé quelques programmes volontairement obfusqués, et d’après mon expérience ChatGPT n’arrivait pas à les comprendre, même en surface
Par exemple, il y avait un commentaire qui essayait d’expliquer avec GPT-4 le code d’un gist contenant un interpréteur Brainfuck en C de 160 octets [1], et la « version clarifiée » ne ressemblait pas du tout au code d’origine
[1] https://gist.github.com/lifthrasiir/596667#gistcomment-47512...
On peut citer par exemple multiplication/factorisation en nombres premiers, dérivation/intégration, souvenirs du passé/prédiction de l’avenir
La désobfuscation de code est clairement l’un de ces problèmes inverses difficiles, et elle peut facilement devenir encore plus compliquée à cause de bugs, de routines inutilisées ou sans rapport, ou d’implémentations incorrectes qui donnent par hasard le bon résultat
En ce sens, si ChatGPT obtient des résultats corrects en désobfuscation, c’est une bonne chose, mais il n’y a aucune raison de s’attendre a priori à ce qu’il en soit capable. C’est bien d’avoir vérifié les limites de ChatGPT sur des problèmes plus difficiles, mais je pense que ce que vous avez trouvé n’est qu’une borne supérieure. Je ne considère pas l’exemple de l’article original comme trivial
let userSignedInTimestamp = new Date()parlet x = new Date()est trivial, mais pour revenir en arrière, il faudra probablement lire et comprendre le code autour afin de voir dans quel contextexest utiliséEn plus, le reste du code est aussi minifié, ce qui complique encore les choses, et même si tout est correctement fait, cela reste au final une transformation avec perte : le nom de variable pouvait contenir des propriétés qui n’étaient pas explicitement présentes dans le code
C’est parce que des données ont disparu pendant le processus
La minification fonctionne de la même manière. Beaucoup d’informations nécessaires à la compréhension du code disparaissent, et les retrouver peut être très délicat
timeFactorpar un nom beaucoup plus court commeipeut être mécanique et simple, mais le processus est destructeur et l’inverser n’est pas vraiment facileC’est impossible sans une compréhension assez fine de ce que fait réellement le code. C’est précisément ce qu’a fait le LLM ici, et même si ce n’est pas surprenant, c’est impressionnant. Le réflexe de minimiser ça aussi vite n’est pas très agréable
Je n’ai pas pu tester, je ne suis pas sur PC
Le titre ne devrait-il pas utiliser ChatGPT ou le nom du modèle, GPT-4, plutôt que le nom de l’entreprise, OpenAI ?
Les LLM excellent dans la transformation de texte
C’est leur force principale, mais j’ai l’impression qu’elle n’est pas assez exploitée
Les autres capacités, comme raisonner ou conserver des connaissances, ne sont que des effets secondaires de cette aptitude surhumaine à reproduire du texte sans “comprendre” le sens voulu
Par exemple, ils ne savent pas trier une liste, mais ils savent traduire une langue. Parce qu’une liste triée presque correctement est fausse, alors qu’une traduction presque correcte est souvent acceptable pour nous
Si une équipe se demande s’il vaut la peine de transformer un gros morceau de code Python en Elixir, elle n’a plus besoin de se contenter de suppositions
Il y a quelque temps, j’ai essayé de traduire un script Python en JavaScript : c’était irréprochable, et avec quelques garde-fous, j’ai l’impression qu’on pourrait passer à l’échelle
Nous nous en servons surtout pour extraire des données structurées à partir d’autres formats
Et en plus, il faut aussi ne pas avoir les connaissances ou les compétences qui permettraient de le faire plus vite avec awk, Python, Perl, etc.
Certains développeurs qui utilisaient la compression comme moyen d’obfuscation vont sûrement être en colère
Ça me rappelle qu’une ancienne version de ColdFusion avait un outil de “chiffrement” du code. L’algorithme était très faible, et il n’a pas fallu longtemps avant que quelqu’un crée un outil de déchiffrement
Mais certaines personnes n’aimaient pas ça, parce qu’elles croyaient l’outil sûr et l’utilisaient pour vendre du code sans en publier les sources. C’était à la fin des années 90/début des années 2000, avant que l’open source ne devienne la norme écrasante
Des sites proposeront des concours reposant en partie sur du code côté client obfusqué, et les participants malins pourront désormais le donner à ChatGPT pour augmenter leurs chances de gagner
Ce n’est que le début
C’est un exemple de performance intellectuelle supérieure à celle des humains
On ne peut pas le nier. Cette tâche est intellectuelle, et n’a rien à voir avec de la simple mémorisation. Le Web ne contient pas non plus d’énormes quantités de paires code minifié/code déminifié sur lesquelles les LLM pourraient s’entraîner
Le LLM comprend ce qu’il déminifie et, sur ce sujet précis, il est globalement supérieur aux humains
Peut-on vraiment être sûr qu’il n’y a pas beaucoup de paires code minifié/code non minifié sur le Web ? On peut facilement en générer à partir de JS existant pour en faire un jeu d’entraînement, et il existe déjà énormément de JS non minifié utilisé à l’entraînement
Si les humains ont du mal à comprendre du code minifié, c’est à cause des noms de variables opaques, des boucles peu naturelles, des espaces réduits au minimum, etc. Les ordinateurs ne trouvent pas ces choses difficiles, et c’est d’ailleurs pour cela qu’on minifie. Une architecture d’attention devrait être bonne à ce genre de tâche
Je pense aussi qu’il existe énormément de code minifié/non minifié. C’est précisément l’objectif des fichiers source map. Vu les investissements d’OpenAI dans la navigation Web et le développement logiciel, il ne serait pas surprenant qu’ils aient inclus des données minifiées/non minifiées dans l’entraînement
Le fait qu’un ordinateur multiplie instantanément des nombres à 1000 chiffres est un exemple où des humains ont d’abord créé un outil de ce type, et ont ainsi réussi la multiplication. Qu’y a-t-il d’autre, là-dedans, qui soit une réussite intellectuelle ? L’ordinateur ne s’est pas créé tout seul
Si l’on trace la frontière de l’intelligence humaine au niveau du crâne et que l’on ne compte pas les outils que cette intelligence crée et utilise comme des étapes du processus de résolution de problèmes, alors il faut aussi accepter que les humains ne sont pas assez intelligents pour aller dans l’espace, pratiquer la chirurgie ou préparer la plupart de leurs repas
Reconnaître la forme abstraite d’un code dont les noms de variables ont disparu, puis faire correspondre ces motifs à des noms de variables lisibles par des humains à partir de choses similaires vues sur l’immense Internet, cela ne paraît pas absurde
C’est intéressant, et cela semble s’être beaucoup amélioré
Il y a un peu plus d’un an, j’avais reçu un e-mail me demandant de l’aide pour un plugin personnalisé sur un site WordPress piraté, et j’avais essayé d’utiliser GPT pour désobfusquer du code malveillant. À l’époque, j’avançais beaucoup mieux en suivant moi-même le code étape par étape
Après avoir lu cet article, j’ai réessayé [0]. Il m’a donné quelque chose de compréhensible, mais le code était obfusqué au point d’évaluer essentiellement via
evalune chaîne illisible à travers l’objet Window, donc cela ne suffisait pas à lui seulUne partie du rapport que j’avais envoyé à l’époque disait ceci : j’avais pu fouiller dans du code JavaScript fortement obfusqué, détecter le chargement de la page, appeler une façade de calcul théoriquement constant, puis décoder la logique qui redirigeait la page vers un site malveillant
[0] https://chatgpt.com/share/f51fbd50-8df0-49e9-86ef-fc972bca6b...
Quelqu’un travaille-t-il sur un LLM de décompilation ?
On dirait qu’on pourrait rendre tout code comme de l’open source.
Dans ce cas, les données d’entraînement seraient faciles à produire. Il suffirait de compiler en masse du code GitHub gratuit avec plusieurs compilateurs, puis d’entraîner le modèle à inverser la compilation. C’est un cas où des données d’entraînement synthétiques sont adaptées et assez faciles à générer.
Le décompilateur n’aurait qu’à être entraîné à inverser la compilation ; pour des tâches comme l’ajout de commentaires, on pourrait utiliser les grands LLM de code existants.
Au-delà de l’open sourcing, on peut imaginer décompiler facilement des applications propriétaires et les modifier pour corriger des bugs ou ajouter des fonctionnalités. Cela pourrait être particulièrement libérateur pour de très vieux programmes abandonnés.
Pour des raisons juridiques, il est très probable que ce type de comportement devienne une fonctionnalité bloquée dans les grands modèles.
Seul l’auteur du code peut accorder ces droits.
Si l’IA comprend vraiment très bien le code, elle produira ou corrigera directement des binaires.
Je suis convaincu que, lorsqu’un vrai programmeur IA apparaîtra, il ne se souciera pas du code lisible par les humains et codera tout en langage machine. Si on lui confie la maintenance d’un système existant, il comprendra l’ensemble de la pile logicielle et matérielle.
C’est un peu comme les modèles de diffusion pour la génération d’images, qui ne se préoccupent pas d’apprendre de véritables techniques de dessin.
C’est plutôt renommer des variables et des fonctions, et ajouter des retours à la ligne.
C’est très proche de la façon dont j’utilise souvent les LLM [0].
Quand je dois lire du code à la main, je m’en sers comme première étape de décodage.
Pouvoir donner un code spaghetti complètement illisible et demander à le rendre plus lisible pour partir de là, c’est incroyablement libérateur.
Comme l’auteur l’a aussi constaté, les LLM ratent parfois des détails, mais je peux les repérer moi-même.
Un autre usage, c’est lorsque je comprends ce que fait le code, mais que je n’arrive pas bien à saisir pourquoi il est écrit de cette manière. C’est particulièrement le cas quand la personne qui l’a écrit n’est plus dans l’entreprise. Dans ces cas-là, je colle la méthode dans un chat LLM, j’explique ce qu’elle fait, puis je demande pourquoi certaines choses pourraient être faites d’une façon particulière.
Ce n’est pas toujours parfait, mais assez souvent, il produit une explication réellement cohérente, qui tient à la relecture et apporte un nouvel éclairage. Une ou deux fois, cela m’a même empêché de refactorer d’une manière qui m’aurait posé problème plus tard.
[0] J’utilise ChatGPT et, plus récemment, openwebUI comme frontend pour plusieurs autres modèles, surtout des variantes de Claude, afin de comparer les différences. On peut aussi mettre en place des configurations amusantes où différents modèles relisent les réponses les uns des autres.
Si le code déminifié ne correspond pas au code minifié, comme le dit la partie finale selon laquelle « la réponse du LLM semble avoir raté quelques détails d’implémentation », son utilité diminue fortement.
D’autant plus que, dans beaucoup de cas, on ne peut pas simplement exécuter le code pour vérifier les différences comme dans l’article.
Modification : en lisant le fil ci-dessous, il semble que c’était un problème d’encodage. Cela dit, l’exactitude m’inquiète encore un peu.
Dans certains cas, ChatGPT a même effectué son propre refactoring en plus de la déminification.
Le code original n’appelle pas immédiatement
handleResize: il contient son corps en ligne. Le minifieur a probablement réellement procédé à une inlining. La seule vraie différence ici est l’absence de la conditionif (typeof window < "u").Plus précisément, il s’agit de caractères de bloc, mais HN ne semble pas permettre de coller des caractères Unicode. Ils sont visibles dans la version originale, mais pas dans la version générée par ChatGPT.
Cela dit, il est tout à fait possible aussi que je n’aie pas fourni tout le contexte nécessaire.
Dévaloriser toute la sortie parce qu’il manque quelques caractères relève presque du pinaillage excessif. À part cela, la sortie me semble identique.
S’il lui avait seulement demandé de renommer les variables et les méthodes, le résultat aurait probablement été plus proche de l’original.