- Ein Entwickler wollte die Implementierung einer ASCII-Art-Komponente verstehen, die er im Web gesehen hatte, und gab minifiziertes JavaScript in ChatGPT ein; dabei wurde der obfuskierte Ablauf in eine lesbare Struktur aufgelöst
- Das Original war eine React-Komponente, die Spalten und Zeilen passend zur Fenstergröße berechnete und mit
setIntervalein String-Grid mit 60 fps aktualisierte - ChatGPT erklärte die Auswahl des Zeichensatzes, die koordinaten- und zeitbasierte Zeichenberechnung sowie den Ablauf zur Aktualisierung von
div.textContentund schrieb den Code anschließend als gleichwertigen TypeScript/React-Code neu - Zunächst schien die Implementierung anders zu sein, weil Blockzeichen wie
░▒▓█fehlten; eine erneute Prüfung des Originalskripts zeigte jedoch, dass es sich um ein Copy-and-paste-Encoding-Problem handelte - Nach der Aktualisierung mit dem richtigen Zeichensatz entsprach die Ausgabe der Originalkomponente und zeigte, dass ChatGPT praktisch genutzt werden kann, um minifizierten Code zu verstehen und neu zu implementieren
Eine ASCII-Art-Implementierung in minifiziertem JavaScript finden
- Um herauszufinden, wie der ASCII-Art-Block auf der Seite des reactive.network hackathon implementiert war, wurde der Quellcode untersucht
- Der relevante Code war minifiziert und daher für Menschen nicht direkt gut lesbar; normalerweise hätte man ihn manuell entschlüsseln oder eine
.map-Datei suchen müssen, um das Original wiederherzustellen - Diesmal wurde der gesamte minifizierte Code in ChatGPT eingefügt, mit der Bitte, die Funktionsweise des Codes zu erklären
Die aus dem minifizierten Code erkennbare Struktur
- Der Code war so aufgebaut, dass er mit JavaScript und React dynamische Textinhalte erzeugte
- Die wichtigsten Elemente waren:
- Mathematische Funktionen wie
Math.floor,Math.absundMath.minwurden mit kurzen Aliasen verwendet - Es gab einen Zeichensatz aus
"reactive.network REACTIVE.NETWORK"sowie Sonderzeichen und Blockzeichen - Je nach Ergebnis von
Date.now() % 3wurde der zu verwendende Zeichensatz ausgewählt - Auf Basis der Länge des ausgewählten Zeichensatzes wurde für jede Koordinate der Index des anzuzeigenden Zeichens berechnet
- Mathematische Funktionen wie
- Die Kernfunktion
eTnahm Positionskoordinaten und einen Zeitwert entgegen und gab ein einzelnes anzuzeigendes Zeichen zurück- Der Zeitwert wurde mit
t.time * 8e-5umgerechnet - Die Koordinaten wurden mithilfe des kleineren Werts aus Spalten und Zeilen sowie des Seitenverhältnisses korrigiert
- Der Zeichenindex wurde durch Kombination des Ergebnisses von
Math.cos(position.x * position.x - position.y * position.y)mit dem Zeitwert bestimmt
- Der Zeitwert wurde mit
Rendering-Ansatz der React-Komponente
- Die Komponente referenzierte mit
useRefdasdiv, in das der Text eingefügt werden sollte, und speicherte mituseStatedie height und width des Fensters - Der erste
useEffectsetzte die Anfangsgröße überwindow.innerHeightundwindow.innerWidthund aktualisierte die Größe, wenn einresize-Event auftrat - Der zweite
useEffectberechnete anhand der Bildschirmgröße die Größe des ASCII-GridscolsistMath.floor(width / 12) * 1.6rowsistMath.floor(height / 12)aspectistcols / rows
setIntervalwurde im Abstand von1_000 / 60ausgeführt und erzeugte den String passend zu 60 fps neu- In jedem Frame wurden alle Zeilen und Spalten durchlaufen, die berechneten Zeichen aneinandergehängt und der finale String in
element.textContentgeschrieben
Neufassung als gut lesbarer TypeScript-Code
- Der Entwickler bat anschließend darum, gleichwertigen Code in TypeScript zu implementieren, ihn aber für Menschen lesbar zu machen
- Der von ChatGPT erzeugte Code löste die Rollen des minifizierten Codes in folgende Namen auf:
selectedCharacterSetcharacterSetLengthcalculateCharacter- Typ
Size - React-Komponente
AsciiArtGenerator
- Schwer verständliche Variablennamen wurden durch Namen ersetzt, die die Implementierungsabsicht sichtbar machen
calculateCharacterberechnet anhand von Koordinaten, Spalten und Zeilen, Seitenverhältnis und Zeitwert das anzuzeigende ZeichentextRefverweist auf dasdiv, in dem der eigentliche Text stehtsizeundsetSizerepräsentieren den Zustand der Fenstergröße
- Der neu geschriebene Code war deutlich besser lesbar als der ursprüngliche minifizierte Code und reichte aus, um die Implementierungsweise der Komponente zu lernen
Missverständnis um fehlende Zeichen und Encoding-Problem
- Beim ersten Vergleich sah es so aus, als hätte das LLM einige Implementierungsdetails übersehen
- Insbesondere wurde angenommen, dass im Ergebnis von ChatGPT Zeichen wie
░▒▓█, die in der Originalkomponente zu sehen waren, fehlten - Später wies ein Nutzer im HN-Forum darauf hin, dass es sich um ein Copy-and-paste-Problem handeln könnte; eine zusätzliche Prüfung ergab, dass der Originalcode andere Zeichen enthielt als die, die in ChatGPT eingefügt worden waren
- Nach dem direkten Herunterladen des Skripts waren die richtigen Zeichen verfügbar, und als Ursache wurde ein Encoding-Unterschied bestätigt
- Nachdem der Zeichensatz korrigiert worden war, entsprach die Ausgabe der Originalkomponente
1 Kommentare
Meinungen auf Hacker News
Ich bin der Entwickler von HumanifyJS und habe für diesen Zweck ein eigenes LLM-basiertes Tool gebaut.
Es nutzt LLMs auf AST-Ebene, um sicherzustellen, dass der Code auch nach der Deobfuskation weiter funktioniert: https://github.com/jehna/humanify
Gemeint ist eine Funktion, die aus normalem Code alle aussagekräftigen Namen des Nutzers entfernt und dann anhand des Algorithmus und der verbleibenden Namen, zum Beispiel eingebauter Namen, neue Namen auswählt.
Beim Refactoring mache ich das manchmal manuell mit einem LLM; es ist nützlich, um standardnähere Begriffe wie
antiparallel_linestattparallel_line_opposite_directionzu finden oder stärker verallgemeinerbare Namen wiefind_instance_in_liststattfind_animal_instance_in_animalszu entdecken.Ehrlich gesagt warte ich immer noch auf Versionsverwaltung auf AST-Ebene.
Gebt ihr ihm die ganze Funktion und fragt wiederholt „Was sollte umbenannt werden?“, bis alles geändert ist, oder lasst ihr es alles auf einmal machen, parst dann die Ausgabe und prüft, ob der AST weiterhin übereinstimmt?
Ich meine etwa 50.000 Zeilen.
Edit: Ich lasse gerade erst einmal eine JS-Datei mit nur 1,2k Zeilen im OpenAI-Modus laufen, und nach 20 Minuten ist sie erst zu 70 % fertig. Selbst wenn es theoretisch mit einer Datei von 50.000 Zeilen funktionieren würde, sollte man es wohl besser nicht versuchen.
JS-Minifizierung ist ziemlich mechanisch und einfach, daher sollte auch das Rückgängigmachen vergleichsweise leicht sein.
Natürlich ist es im Allgemeinen immer noch mühsam genug, um es nicht von Hand zu machen, aber die Transformation selbst ist ziemlich begrenzt, sodass man sie mit Notizen zum Nachverfolgen der obfuskierten Bezeichner lesen kann.
Allgemeineres Dekomprimieren oder Deobfuskieren scheint weiterhin ein offenes Problem zu sein. Ich habe früher einige absichtlich obfuskierte Programme geschrieben, und meiner Erfahrung nach hat ChatGPT sie nicht einmal oberflächlich verstanden.
Zum Beispiel gab es einen Kommentar, der mit GPT-4 versuchte, den Code eines 160-Byte-Brainfuck-Interpreters in C aus einem Gist zu erklären [1], aber die „verdeutlichte Version“ hatte keinerlei Ähnlichkeit mit dem Originalcode.
[1] https://gist.github.com/lifthrasiir/596667#gistcomment-47512...
Beispiele sind Multiplikation/Faktorisierung, Ableiten/Integrieren oder Erinnerung an die Vergangenheit/Vorhersage der Zukunft.
Code-Deobfuskation ist eindeutig eines dieser schwierigen inversen Probleme und kann durch Bugs, ungenutzte oder irrelevante Routinen sowie fehlerhafte Implementierungen, die zufällig richtige Ergebnisse liefern, leicht noch schwieriger werden.
In diesem Sinne ist es erfreulich, wenn ChatGPT bei der Deobfuskation brauchbare Ergebnisse erzielt; a priori würde ich das aber nicht erwarten. Es ist gut, dass ihr an einem schwierigeren Problem die Grenzen von ChatGPT ausgelotet habt, aber ich sehe das, was ihr gefunden habt, nur als Obergrenze. Das Beispiel aus dem Original halte ich nicht für trivial.
let userSignedInTimestamp = new Date()inlet x = new Date()zu ändern, ist trivial; in die Gegenrichtung muss man aber wahrscheinlich den umgebenden Code lesen und verstehen, um zu sehen, in welchem Kontextxverwendet wird.Außerdem ist auch der restliche Code minifiziert, was es noch schwieriger macht, und selbst wenn man alles richtig macht, bleibt es letztlich eine verlustbehaftete Transformation; der Variablenname könnte Eigenschaften enthalten haben, die im Code nicht ausdrücklich stehen.
Das liegt daran, dass dabei Daten verloren gehen.
Minifizierung funktioniert auf die gleiche Weise. Viele Informationen, die zum Verstehen des Codes nötig sind, gehen verloren, und diese Informationen wiederzugewinnen kann sehr knifflig sein.
timeFactorin ein viel kürzeresizu ändern, kann mechanisch und einfach sein, aber der Prozess ist destruktiv und lässt sich vergleichsweise schwer rückgängig machen.Ohne ein ziemlich ausgefeiltes Verständnis davon, was der Code tatsächlich tut, ist das nicht möglich. Genau diesen Teil hat das LLM hier geleistet, und das ist zwar nicht überraschend, aber beeindruckend. Diese allzu reflexhafte Herabwürdigung gefällt mir nicht.
Da ich nicht am PC bin, konnte ich es nicht testen.
Sollte im Titel nicht ChatGPT oder der Modellname GPT-4 stehen, statt OpenAI, dem Firmennamen?
LLMs sind hervorragend bei Texttransformationen
Das ist ihre zentrale Stärke, die meiner Ansicht nach nicht ausreichend genutzt wird.
Andere Fähigkeiten wie Schlussfolgern oder das Bewahren von Wissen sind nur Nebenwirkungen dieser übermenschlichen Fähigkeit, Text zu reproduzieren, ohne die beabsichtigte Bedeutung zu „verstehen“.
Zum Beispiel können sie keine Listen sortieren, aber Sprachen übersetzen. Denn eine fast korrekt sortierte Liste ist falsch, während wir eine fast korrekte Übersetzung oft akzeptieren.
Wenn ein Team überlegt, ob es sich lohnt, große Teile von Python-Code nach Elixir zu übertragen, muss es nicht länger nur raten.
Vor Kurzem habe ich ein Python-Skript nach JavaScript übersetzen lassen; es war einwandfrei, und mit ein paar Guardrails ließe sich das vermutlich skalieren.
Wir nutzen es hauptsächlich, um strukturierte Daten aus anderen Formaten zu extrahieren.
Außerdem darf einem das Wissen oder die Fähigkeit fehlen, es mit awk, Python, Perl usw. schneller zu erledigen.
Einige Entwickler, die Komprimierung als Mittel zur Obfuskation genutzt haben, werden sicher verärgert sein.
Das erinnert mich an ein Code-„Verschlüsselungs“-Tool in alten ColdFusion-Versionen. Der Algorithmus war sehr schwach, daher dauerte es nicht lange, bis jemand ein Entschlüsselungs-Tool baute.
Trotzdem mochten manche Leute das nicht, weil sie glaubten, das Tool sei sicher, und es nutzten, um Code zu verkaufen, ohne den Source offenzulegen. Das war Ende der 90er/Anfang der 2000er, bevor Open Source zum überwältigenden Standard wurde.
Websites veranstalten Wettbewerbe, die teilweise auf obfuskiertem clientseitigem Code beruhen, und clevere Teilnehmer können ihn jetzt in ChatGPT werfen, um ihre Gewinnchancen zu erhöhen.
Das ist erst der Anfang.
Das ist ein Beispiel für dem Menschen überlegene intellektuelle Leistungsfähigkeit.
Das lässt sich nicht bestreiten. Diese Aufgabe ist intellektuell und hat nichts mit bloßem Auswendiglernen zu tun. Im Web gibt es auch nicht Unmengen an Paaren aus komprimiertem und dekomprimiertem Code, von denen ein LLM lernen könnte.
Das LLM versteht, was es dekomprimiert, und ist in diesem speziellen Thema im Allgemeinen Menschen überlegen.
Kann man sicher sein, dass es im Web nicht viele Paare aus komprimiertem und unkomprimiertem Code gibt? Sie lassen sich leicht aus bestehendem JS erzeugen und als Trainingsset verwenden, und es gibt bereits Unmengen an unkomprimiertem JS, das im Training verwendet wurde.
Der Grund, warum Menschen komprimierten Code schwer verstehen, sind undurchsichtige Variablennamen, ungewohnte Schleifen, minimierter Whitespace usw. Computer finden solche Dinge nicht schwierig, und genau deshalb komprimieren wir ja überhaupt. Die Attention-Architektur sollte bei solchen Aufgaben gut sein.
Ich denke auch, dass es enorm viel komprimierten/unkomprimierten Code gibt. Genau dafür sind Source-Map-Dateien da. Wenn man sieht, wie viel OpenAI in Web-Browsing und Softwareentwicklung investiert hat, wäre es nicht überraschend, wenn komprimierte/unkomprimierte Daten im Training enthalten waren.
Dass ein Computer eine 1000-stellige Zahl sofort multipliziert, ist ein Beispiel dafür, dass Menschen zuerst ein solches Tool gebaut und damit erfolgreich multipliziert haben. Was hat daran sonst noch intellektuell Erfolg? Der Computer hat sich schließlich nicht selbst gebaut.
Wenn man die Grenze menschlicher Intelligenz an der Schädeldecke zieht und die Tools, die diese Intelligenz erschafft und nutzt, nicht als Schritte im Problemlösungsprozess mitzählt, muss man auch akzeptieren, dass Menschen nicht intelligent genug sind, um ins All zu fliegen, Operationen durchzuführen oder die meisten Mahlzeiten zuzubereiten.
Die abstrakte Form von Code ohne Variablennamen zu erkennen und menschenlesbare Variablennamen auf ähnliche Muster abzubilden, die es im riesigen Internet gesehen hat, wirkt nicht absurd.
Interessant, und es scheint deutlich besser geworden zu sein.
Vor etwas mehr als einem Jahr bekam ich eine E-Mail mit der Bitte um Hilfe bei einem Custom-Plugin einer gehackten WordPress-Site und wollte GPT für die Deobfuskierung von Malware-Code ausprobieren. Damals kam ich viel schneller voran, indem ich den Code selbst Schritt für Schritt nachvollzog.
Nach der Lektüre dieses Beitrags habe ich es erneut versucht [0]. Es lieferte verständliche Ergebnisse, aber der Code war so obfuskiert, dass er über das Window-Objekt effektiv nicht lesbare Strings evaluiert; das allein reichte also nicht aus.
Ein Teil des Berichts, den ich damals schickte, lautete sinngemäß: Ich konnte mich durch stark obfuskierten JavaScript-Code arbeiten, erkennen, wann der Page Load detektiert wird, eine nur scheinbar konstante Berechnung aufrufen und die Logik entschlüsseln, die die Seite auf eine bösartige Website weiterleitet.
[0] https://chatgpt.com/share/f51fbd50-8df0-49e9-86ef-fc972bca6b...
Arbeitet jemand an einem Decompiler-LLM?
Es scheint, als könnte man damit sämtlichen Code wie Open Source machen.
In diesem Fall wären Trainingsdaten leicht zu erstellen. Man müsste kostenlosen GitHub-Code massenhaft mit verschiedenen Compilern bauen und das Modell darauf trainieren, die Kompilierung rückgängig zu machen. Das ist ein Fall, in dem synthetische Trainingsdaten passend und ziemlich einfach zu erzeugen wären.
Den Decompiler müsste man nur darauf trainieren, die Kompilierung rückgängig zu machen; für Aufgaben wie das Hinzufügen von Kommentaren könnte man bestehende, größere Code-LLMs verwenden.
Nicht nur Open-Sourcing: Man kann sich vorstellen, proprietäre Apps leicht zu dekompilieren und zu verändern, um Bugs zu beheben oder Funktionen hinzuzufügen. Besonders für Programme, die schon lange nicht mehr gepflegt werden, könnte das eine große Befreiung sein.
Aus rechtlichen Gründen dürfte ein solches Verhalten in großen Modellen wahrscheinlich eine gesperrte Funktion werden.
Solche Rechte kann nur der Autor des Codes gewähren.
Wenn KI Code wirklich gut versteht, wird sie einfach Binaries erstellen oder reparieren.
Ich glaube, sobald es echte KI-Programmierer gibt, werden sie sich nicht um menschenlesbaren Code kümmern, sondern alles in Maschinensprache programmieren. Wenn sie die Aufgabe bekommen, ein bestehendes System zu warten, werden sie den gesamten Software- und Hardware-Stack als Ganzes verstehen.
Ähnlich wie ein Diffusionsmodell zur Bilderzeugung sich nicht darum kümmert, echte Zeichentechniken zu lernen.
Es geht eher darum, Variablen und Funktionen umzubenennen und Zeilenumbrüche einzufügen.
Das ist der Art, wie ich LLMs häufig nutze, sehr ähnlich [0].
Wenn ich mich mit eigener Kraft durch Code lesen muss, nutze ich sie als ersten Schritt zur Entzifferung.
Es ist unglaublich befreiend, spaghettiartigen, völlig chaotischen Code hineinzukopieren, ihn lesbarer aufbereiten zu lassen und von dort aus weiterzumachen.
Wie der Autor ebenfalls festgestellt hat, übersehen LLMs gelegentlich Details, aber diese Stellen kann ich selbst abfangen.
Ein weiterer Anwendungsfall ist, wenn ich verstehe, was der Code tut, mir aber nicht recht in den Kopf will, warum er so aufgebaut ist. Das gilt besonders, wenn die Person, die den Code geschrieben hat, nicht mehr im Unternehmen ist. Dann füge ich die Methode in einen LLM-Chat ein, erkläre, was sie tut, und frage anschließend, warum bestimmte Dinge auf eine bestimmte Weise gelöst sein könnten.
Es ist nicht immer perfekt, aber ziemlich oft liefert es Erklärungen, die tatsächlich Sinn ergeben, einer Überprüfung standhalten und neue Einsichten liefern. Ein- oder zweimal hat es mich auch davon abgehalten, etwas so zu refaktorieren, dass es später Probleme gemacht hätte.
[0] Ich nutze ChatGPT und seit Kurzem mehrere andere Modelle, hauptsächlich Claude-Varianten, über openwebUI als Frontend, um die Unterschiede zu sehen. Man kann auch interessante Setups bauen, bei denen verschiedene Modelle die Antworten der jeweils anderen prüfen.
Wenn der entminifizierte Code nicht mit dem minifizierten Code übereinstimmt, dann sinkt der Nutzen stark, wie in der Passage am Ende: „Die LLM-Antwort scheint einige Implementierungsdetails übersehen zu haben“.
Vor allem, weil man in vielen Fällen den Code nicht einfach wie im Artikel ausführen und die Unterschiede überprüfen kann.
Nachtrag: Wenn ich den Thread unten lese, scheint es wohl ein Encoding-Problem gewesen zu sein. Trotzdem macht mir die Korrektheit weiterhin etwas Sorgen.
In einigen Fällen hat ChatGPT zusätzlich zum Entminifizieren sogar eigenes Refactoring vorgenommen.
Der Originalcode ruft
handleResizenicht sofort auf, sondern enthält dessen Inhalt inline. Vermutlich hat der Minifier tatsächlich Inlining gemacht. Der einzige echte Unterschied hier ist, dass die Bedingungif (typeof window < "u")fehlt.Konkret geht es um Blockzeichen; HN scheint das Einfügen von Unicode-Zeichen nicht zu erlauben. In der Originalversion sind sie sichtbar, in der von ChatGPT erzeugten Version nicht.
Es ist allerdings gut möglich, dass ich nicht den gesamten nötigen Kontext mitgegeben habe.
Die gesamte Ausgabe wegen ein paar fehlender Zeichen abzuwerten, wirkt eher wie übergenaues Haarspalten. Abgesehen davon ist die Ausgabe aus meiner Sicht identisch.
Wenn man es nur gebeten hätte, Variablen- und Methodennamen zu ändern, wäre es dem Original vermutlich näher gekommen.