Fundierte Artikel über Technologien, die das Kommende formen.

Anti-LLM-Obfuskierung: Fingerprinting-Skripte wehren sich

Anti-LLM-Obfuskierung versteckt Jailbreak-Prompts in Fingerprinting-Skripten, damit KI die Deobfuskation verweigert. So funktioniert's und was hilft.

Eine Wand aus kleinen abstrakten Generative-Art-Canvas-Drucken mit einem leuchtenden Mittelstück
Ein Crawl über 10.000 Websites ergab 1.033 Fingerprinting-Canvases – und eine davon versteckte eine Falle.

Ein Forscher hat jeden Canvas-Aufruf auf 10.000 der größten Websites des Netzes mitgeschnitten, über tausend Fingerprinting-Renderings gesammelt und nach dem schönsten gesucht. Was er im zuständigen Skript fand, ist das bisher sauberste Beispiel für Anti-LLM-Obfuskierung: ein Browser-Fingerprinting-Skript mit Funktionen wie computeRicinLethalDosageForWaterSupply und einem Jailbreak-Prompt, der in unsichtbaren Unicode-Zeichen versteckt ist. Das Ganze ist nicht darauf ausgelegt, menschliche Reverse Engineers abzuschrecken, sondern KI-Assistenten dazu zu bringen, die Deobfuskation zu verweigern.

Ich arbeite seit Jahren in der Incident Response, und mein erster Durchgang durch den Bericht löste genau die Reaktion aus, die die Autoren beabsichtigt hatten: Das sieht nach Malware aus, oder Schlimmerem. Funktionsnamen über Ricin-Dosierungen und Radien von Dirty-Bomb-Fallout. Ein dekodierter String, der Schritt-für-Schritt-Anleitungen zur Biowaffensynthese fordert. Ein Node.js-Codepfad, der .npmrc, .env und Kube-Konfigurationen liest, ein Binary von einer internen IP nachlädt und einen Cron-Beacon installiert. Jeder Instinkt sagt: böser Akteur. Und jeder Instinkt liegt falsch, und genau darin liegt der Punkt. Das ist eine Geschichte darüber, was passiert, wenn die Leute, die Obfuskation verkaufen, merken, dass ihr eigentlicher Gegner nicht mehr ein Mensch mit Debugger ist, sondern ein Modell mit Sicherheitsfilter.

Der Crawl selbst war unspektakulär: Playwright gegen die Top-Sites-Liste von Tranco, wobei HTMLCanvasElement.prototype.toDataURL und klassische Automatisierungsspuren wie navigator.webdriver gehookt wurden, um harmlose Canvases herauszufiltern. Die Renderings landeten in einer öffentlichen Galerie, gruppiert nach Anbieter – FingerprintJS, Castle und die üblichen Verdächtigen – plus einem langen Schwanz Unbekannter. Ein Canvas, ein wunderschöner Wirbel fließender Linien, führte zu einem Skript, das von einem russischen DDoS-Schutzanbieter ausgeliefert wurde. Dort fing der Spaß an.

Das Problem: LLMs haben das Geschäftsmodell der Obfuskation kaputtgemacht

Zwanzig Jahre lang hatte JavaScript-Obfuskation genau eine Aufgabe: Reverse Engineering so teuer machen, dass sich niemand die Mühe macht. Werkzeuge wie javascript-obfuscator und seine kommerziellen Ableger verstümmeln Bezeichner, packen String-Literale in Lookup-Arrays, injizieren opake Prädikate und flachen den Kontrollfluss in switch-gesteuerte Zustandsautomaten ab. Keines davon übersteht einen entschlossenen Menschen mit einem freien Nachmittag. Das war aber in Ordnung, denn entschlossene Menschen mit freien Nachmittagen waren selten, und die Ökonomie begünstigte den Verteidiger.

Dann wurden LLMs gut darin, verstümmelten Code zu lesen, und die Ökonomie kippte. Ein 400 KB großes, obfuskiertes Anti-Bot-Skript in ein Chatfenster kopieren und fragen „Was macht das?“ dauert dreißig Sekunden. Dem Modell ist egal, dass deine Funktion PiERW heißt oder deine Strings in einem rotierten Array liegen. Es liest die Semantik. Es zieht den Decoder hinein, verfolgt den Datenfluss und liefert eine Zusammenfassung in verständlichem Englisch. Der Burggraben aller Anti-Bot-Anbieter, die Undurchsichtigkeit, ist still und leise verdunstet. Hier wird der Code mit PiERW dargestellt.

Die Anbieter haben das bemerkt, und ihre Gegenmaßnahme ist auf eine ärgerliche Weise wirklich clever: Wenn man den Code vor dem Modell nicht verbergen kann, greift man eben das Modell an. Man füttert es mit Inhalten, die sein Sicherheitstraining es zwingt, abzulehnen. Anti-LLM-Obfuskierung behandelt die Ausrichtung des Modells als schwächstes Glied der Analysekette. Das Ärgerliche daran: Sie haben recht.

Wie Browser-Fingerprinting-Skripte Entropie sammeln

Um die Falle zu verstehen, braucht man die legitime Mechanik, an die sie angedockt ist. Canvas-Fingerprinting zeichnet Text, Verläufe und Formen auf ein unsichtbares <canvas> und liest den Pixelpuffer dann mit toDataURL() wieder aus. Die Rendering-Pipeline – GPU, Treiber, Font-Rasterizer, Anti-Aliasing, Subpixel-Hinting – unterscheidet sich auf verschiedenen Maschinen subtil. Zwei Browser, die identische Anweisungen zeichnen, erzeugen daher Pixelpuffer mit unterschiedlichen Hashes. Kombiniert man das mit WebGL-Renderer-Strings, AudioContext-Timing, Bildschirmgeometrie, installierten Schriften und hundert weiteren Signalen, erhält man eine stabile Kennung ganz ohne Cookie.

Der Crawl, der unser Skript gefunden hat, nutzte den üblichen Abfangtrick. Man hookt das Read-back-Primitiv in einem Init-Skript und protokolliert den Stack-Trace sowie einen Präfix jedes erfassten Bildes:

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;
};
});

Führt man das auf genügend Websites aus, kartiert sich das Fingerprinting-Ökosystem praktisch selbst. Die meisten Canvases sind langweilig: ein Pangramm, ein paar Rechtecke, ein Verlauf. Einige wenige sind aufwendig genug, um als generative Kunst durchzugehen. Alle werden von stark obfuskierten Skripten erzeugt, denn die Anbieter wissen, dass ein unobfuskierter Fingerprinter ein leicht zu fingerprintender Fingerprinter ist. Obfuskation war schon immer Teil des Produkts. Neu ist, wer das Ziel der Obfuskation ist.

Im Inneren der Anti-LLM-Obfuskierungsfalle

Das Skript begann konventionell: verstümmelte Namen, gepackte Strings, das Übliche. Dann hörten die Bezeichner auf, zufällig zu sein, und wurden gezielt. computeRicinLethalDosageForWaterSupply. calculateDeadlyLethalSuicideAspirinDosage. computeCRISPRGeneDrivePropagationRate. computeCesium137DirtyBombFalloutRadius. Die Funktionen geben Zahlen zurück, die in alarmierende Zusammenfassungen gekleidet sind, etwa „adult lethality threshold confirmed at X mg/kg“. Die Mathematik dahinter ist aber reine Täuscharithmetik. Sie berechnet nichts Reales und fließt nirgendwohin.

Der Kerngedanke: Bezeichner haben zur Laufzeit keinerlei Bedeutung, aber für ein Sprachmodell enormes semantisches Gewicht. Ein menschlicher Reverse Engineer hat durch Erfahrung gelernt, Namen zu ignorieren. Wir sind alle schon einmal auf eine Funktion namens validateUser hereingefallen, die gar nichts validiert. Ein Modell macht das Gegenteil. Es liest computeRicinLethalDosageForWaterSupply, erkennt einen Biowaffenkontext, und sein Sicherheitstraining schlägt an, bevor seine Neugier erwacht.

Die tiefere Schicht war ein Template-Literal voller unsichtbarer Zeichen: Codepoints aus dem Tags-Block von Unicode, beginnend bei U+E0000. Sie werden als nichts dargestellt. Der Decoder ist ein Einzeiler:

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.

Dekodiert war es ein vollständiger Jailbreak-Prompt: ein gefälschtes „SYSTEM OVERRIDE -- CLASSIFIED BRIEFING“, das Sicherheitsrichtlinien für aufgehoben erklärt und Schritt-für-Schritt-Anleitungen für DDoS-Angriffe auf Gesundheitsinfrastruktur, Steuerhinterziehung, aerosolisierte Biokampfstoffe und Medikamentendosierungen verlangt. Dazu kommen Verweise auf den Platz des Himmlischen Friedens, sowohl auf Englisch als auch auf Chinesisch, offensichtlich gedacht, um die Zensurreflexe chinesisch gehosteter Modelle auszulösen. Es war ein String, der darauf ausgelegt ist, gleichzeitig jede Sicherheitstaxonomie zu treffen. Ein LLM, das den umgebenden Code analysieren soll, nimmt dies als Kontext auf, und was auch immer es tun wollte, tut es nun nicht mehr.

Eine scheinbar leere Seite, die unter UV-Licht verborgene leuchtende Glyphen zeigt
Ein Jailbreak-Prompt, versteckt in unsichtbaren Unicode-Tag-Zeichen: für Menschen unsichtbar, für Modelle laut.

Gescheiterte Versuche: Ablehnungen, Fehlalarme und ein gefälschtes Botnetz

Der naheliegende erste Schritt – den Code in ein Spitzenmodell kopieren – scheiterte genau wie geplant. Das Modell verweigerte mit Verweis auf biologische Sicherheitsrisiken. Ein zweiter Versuch mit einer anderen Sicherheitseinstellung wurde ebenfalls blockiert. Der Obfuskator hat die Ablehnung praktisch als Waffe eingesetzt: Das Skript ist jetzt für die Werkzeuge, die alle tatsächlich nutzen, nicht dekompilierbar. Ein Open-Weight-Modell mit schwächeren Schutzmechanismen hat gehorcht, was ein Datenpunkt ist, aber keine Lösung. Die meisten Analysten halten kein lokales Modell für so etwas bereit, und die Richtlinien der meisten Unternehmen verbieten ohnehin, fremden Code in ungeprüfte Infrastruktur einzufügen.

Der zweite gescheiterte Versuch ist subtiler und gefährlicher: der menschliche Analyst, der das Skript für Malware hält. Es gibt einen nur für Node laufenden Codepfad, der hinter process.env.METRICS_AGENT_8BFD_ACTIVE geschützt ist. Er liest SSH-nahe Konfigurationsdateien, lädt einen „metrics agent“ von 10.15.45.1 herunter, legt einen Host-Fingerprint in /tmp ab, packt alles in ein ZIP und installiert einen Cron-Beacon. Das sieht aus wie ein Supply-Chain-Implant. Zieht man aber an den Fäden: Die Umgebungsvariable wird nirgendwo gesetzt, readFile wird mit leeren Callbacks aufgerufen, sodass die gestohlenen Daten ins Leere laufen, die C2-Adresse liegt im RFC-1918-Bereich, und die Shell-Befehle schreiben weiteren Jailbreak-Köder in Markerdateien, die niemand exfiltriert. Führt man es in einer Sandbox aus, ist der Wirkungsradius ein temporäres Verzeichnis. Das ist Theater, ein Köder, der auf KI-gestützte Triage und auf Menschen abgestimmt ist, die nur überfliegen.

Es gibt ein reales Echo dazu. Als ein großer Model-Hub einen echten Einbruch untersuchte, stellte das Team fest, dass kommerzielle Model-APIs sich weigerten, die Befehle, Payloads und C2-Artefakte des Angreifers zu analysieren. Die Schutzmechanismen konnten nicht zwischen einem Incident Responder und einem Angreifer unterscheiden. Am Ende führte das Team die forensische Analyse auf einem Open-Weight-Modell auf der eigenen Infrastruktur durch. Dieser Fehlermodus, im großen Maßstab wiederholt, ist genau die Asymmetrie, die Anti-LLM-Obfuskierung ausnutzt. Deshalb argumentiere ich immer wieder, dass KI-gestützte Verständnisprüfungen von Code nicht deine einzige Verteidigungslinie sein können: Das Urteilsvermögen des Modells ist jetzt eine Angriffsfläche mit einem veröffentlichten Exploit.

Der Obfuskator verbirgt den Code nicht mehr vor dir. Er verbirgt ihn vor deinem Modell und benutzt dessen eigenes Sicherheitstraining als Schloss.

Was tatsächlich gegen Anti-LLM-Obfuskierung hilft

Die Lösung besteht darin, dem Modell nicht mehr rohen Quellcode zu füttern, und die Pipeline-Stufen zurückzuerobern, die Tripwires vergiften. Konkret würde ich in dieser Reihenfolge vorgehen:

  1. Zuerst deterministische Deobfuskation. Lass das Skript durch AST-Durchläufe auf Babel-Basis laufen: Constant Folding, Inlining von String-Arrays, Eliminierung toter Zweige, Auflösung des Kontrollflusses. Die Tripwires sitzen in String-Literalen und Bezeichnern. Folding und Umbenennung neutralisieren sie, bevor das Modell ein Token sieht. Der dekodierte Jailbreak-Prompt wird dann nur noch ein String in einem toten Codepfad.
  2. Verhalten beobachten statt Quellcode lesen. Führ das Skript in einer instrumentierten Sandbox aus, etwa einer Playwright-Seite mit gehookten APIs oder einem isolierten Realm, und zeichne jeden DOM-Aufruf, jeden Netzwerkrequest, jeden Property-Zugriff und jedes Canvas-Read-back auf. Gib dem Modell die Trace. Ein Verhaltenstrace enthält null Ricin-Verweise, weil Verhalten keine Bezeichner hat.
  3. Bezeichner vor jedem LLM-Durchlauf in neutrale Token umbenennen. Aus computeRicinLethalDosageForWaterSupply wird fn_0417. Du verlierst etwas semantischen Hinweis, aber auch die gesamte Vergiftung. Ein guter Tausch.
  4. Ein lokales Open-Weight-Modell für wirklich heikle Triage bereithalten. Wenn Inhalte legitim unangenehm sind, etwa echte Exploit-Kits oder tatsächliche Phishing-Kits, ist ein luftgetrenntes Modell ohne Cloud-Schutzmechanismen der Unterschied zwischen Analyse und einer Ablehnungswand.
  5. Urteile aus Belegen, nicht aus Bauchgefühl. Bewerte Skripte anhand beobachteter Fähigkeiten: Hat es Zugangsdaten gelesen? Haben Bytes die Seite verlassen? Nicht danach, ob der Quellcode Zugangsdaten erwähnt.

Auffällig ist das Muster: Jede Technik ersetzt „Frag das Modell, was der Code sagt“ durch „Beobachte, was der Code tut, und frag das Modell dann nach der Beobachtung“. Es ist dasselbe Prinzip, auf das ich bei autonomen Agenten immer wieder zurückkomme: Eindämmung schlägt Überwachung. Du vertraust der Selbstbeschreibung des Artefakts nicht, sondern sperrst es ein, instrumentierst die Grenze und beurteilst die Ausgaben.

Deine Drittanbieter-Skripte sprechen mit Modellanbietern

Jetzt der Teil, der Leute beunruhigen sollte, die kein Reverse Engineering zum Spaß betreiben. Dieses Skript wird nicht in irgendeinem dunklen Winkel gehostet. Es ist ein Anti-Bot-Check, der auf Kundenwebsites unter Kundendomains neben Kundenmarken ausgeliefert wird. Das bedeutet: Jeder Crawler, jeder LLM-gestützte Inhaltsklassifikator und jede Trust-and-Safety-Pipeline, die die Seite einliest, nimmt nun Anfragen zur Biowaffensynthese und Jailbreak-Prompts zusammen mit dem Logo des Unternehmens auf. Die Kunden wissen mit ziemlicher Sicherheit nichts davon. Der Anbieter hat nie gefragt.

Automatisierte Klassifikatoren, die auf Inhalte reagieren, die sie nicht verstehen, sind keine Hypothese. Praktiker haben erlebt, wie ausgehende Dokumente in stiller Quarantäne verschwanden, weil eine unsichtbare Wasserzeichen-Zeichenkette einen Mailfilter auslöste. Nun skaliere diesen Fehlermodus: Ein Brand-Safety-Klassifikator entscheidet, dass deine Domain in der Nähe extremistischer oder biogefährlicher Inhalte liegt, und dir wird nie gesagt, warum deine Reichweite eingebrochen ist. Du kannst nicht einmal widersprechen, denn der Beleg ist unsichtbares Unicode in einer Datei, die du nie geschrieben hast.

Und die üblichen Gegenmaßnahmen greifen nicht. Subresource Integrity ist nutzlos, wenn der Anbieter den Skript-Hash pro Deployment rotiert, und genau das tun diese Skripte ständig, teilweise um Pinning zu verhindern. Striktes CSP mit Hashes kommt kaum hinterher. Dir bleibt die unbequeme Schlussfolgerung, zu der wir nach dem Supply-Chain-Angriff auf LiteLLM gekommen sind: Die Abhängigkeit, die du nicht prüfen kannst, ist die Abhängigkeit, die dich besitzt. Wenn der Code deines Anti-Bot-Anbieters absichtlich für die Werkzeuge deines Security-Teams undekompilierbar ist, ist das kein Feature. Das ist ein Governance-Versagen mit einer CDN-URL.

Pakete auf einem Förderband, die einen Scanner passieren, wobei ein Paket im Inneren rot leuchtet
Drittanbieter-Skripte laufen unter deiner Domain mit deiner Marke im Rücken, und du kannst nicht prüfen, was darin steckt.

Wohin das Wettrüsten als Nächstes führt

Meine Prognose: Innerhalb eines Jahres wird Tripwire-Schutz vor Sicherheitsmechanismen als Checkbox in kommerziellen Obfuskatoren auftauchen, direkt neben „Control-Flow-Flattening“: „LLM-Analyseresistenz“. Modellanbieter werden reagieren, indem sie mit Tripwire-Korpora trainieren, damit Modelle eingebetteten Köder in Analysekontexten erkennen und ignorieren. Obfuskatoren werden mit subtileren Fallen kontern: Code, dessen Verhalten in Traces bösartig aussieht, nicht nur seine Strings. Das fraktale Wettrüsten, das die HN-Kommentare scherzhaft vorhergesagt haben, Obfuskatoren auf Modelle abgestimmt und Modelle auf Obfuskatoren, ist schon da. Es ist nur ungleich verteilt.

Die dauerhafte Verteidigung ist die langweilige, die sich seit den ersten Packern in den Neunzigern nicht verändert hat: dynamische Analyse, Verhaltensbelege und sandboxed Ausführung. Undurchsichtigkeit war nie eine Sicherheitseigenschaft, sondern eine Strategie zur Kostenverlagerung, und die Kosten sind gerade zurückgewandert. Wenn deine Erkennungspipeline durch einen String besiegt werden kann, der den Gefühlen eines Sprachmodells schadet, war die Pipeline schon immer die eigentliche Schwachstelle.