Dokumentation
/
Rezepte
/

Funktionsnamen vor der LLM-Analyse verbergen

Funktionsnamen vor der LLM-Analyse verbergen

Pro

Das Problem

Sie haben vmObfuscation: true aktiviert, eine Datei verarbeitet, die eine Funktion wie validateLicense enthält, und festgestellt, dass die obfuskierte Ausgabe weiterhin den wörtlichen Text validateLicense enthält: Der Funktionsrumpf ist verschwunden und durch Bytecode ersetzt, aber der Name selbst steht gut sichtbar da.

JavaScript

Übertragen Sie das auf eine echte Codebasis, und Sie erhalten eine Liste von Funktionsnamen wie validateLicense, decryptPayload, processPayment oder checkSubscription. Ein LLM muss den Bytecode nicht knacken, um zu verstehen, was das Programm tut: Schon die Namen genügen ihm für eine selbstbewusste und zutreffende Zusammenfassung des Modulverhaltens. Der Bytecode ist undurchsichtig, das Inhaltsverzeichnis nicht.

Warum die VM-Obfuskierung diese Namen beibehält

Mit vmTargetFunctionsMode: 'root' (der Standardeinstellung) wandelt der Obfuscator den Rumpf jeder Funktion auf oberster Ebene in VM-Bytecode um, lässt den Namen aber bewusst unverändert. Eine Funktionsdeklaration auf oberster Ebene ist semantisch eine Bindung im umgebenden Gültigkeitsbereich: bei einem Skript im globalen Objekt, bei einem Modul im Modul-Gültigkeitsbereich (eine nicht exportierte Funktion auf oberster Ebene ist modullokal, nicht global, liegt in der Datei aber trotzdem auf oberster Ebene). Der Obfuscator kann sie nicht gefahrlos umbenennen, weil er nicht wissen kann, wer sonst noch darauf verweist: ein anderes Bundle, ein Inline-<script>, ein HTML-Attribut onclick="validateLicense(...)", ein dynamischer Zugriff über window['validateLicense'] usw.

Der Kompromiss der Standardeinstellung lautet also: die Implementierung schützen, die öffentliche Schnittstelle erhalten. Dadurch bleiben Integrationen intakt, aber ein LLM erhält zugleich ein kostenloses Verzeichnis aller Einstiegspunkte. In diesem Fall meldet das Obfuskierungsergebnis eine Warnung VMGlobalFunctionNamesNotRenamed mit den Namen, die lesbar geblieben sind.

Warum das für LLM-gestütztes Reverse Engineering wichtig ist

Ein menschlicher Angreifer, der vor einigen hundert Zeilen Bytecode-Dispatch sitzt, gibt meist auf. Ein LLM, das dieselbe Datei erhält, greift den Bytecode gar nicht erst an: Es liest die Namen, gleicht sie mit den wenigen sichtbaren String-Literalen ab und liefert etwa Folgendes:

„Dieses Modul schaltet eine kostenpflichtige Funktion frei. validateLicense prüft ein signiertes Token, checkExpiry weist abgelaufene Lizenzen zurück, und activateFeature entsperrt die Oberfläche, sobald die Prüfung bestanden ist. Die Entschlüsselungshilfe steckt in decryptPayload.“

Diese Zusammenfassung reicht einem Angreifer, um eine gezielte Umgehung zu planen, ohne die VM je anzurühren. Die Namen sind das Leck.

Die Lösung: Code in eine IIFE einschließen

Der einfachste und robusteste Weg, dieses Leck zu schließen, besteht darin, Ihre sensiblen Funktionen eine Ebene tiefer in den Gültigkeitsbereichsbaum zu verschieben. Funktionen, die innerhalb einer anderen Funktion deklariert sind, liegen nicht auf oberster Ebene. Der Obfuscator kann sie daher frei umbenennen und ihre Deklarationen wie jede andere Anweisung in Bytecode aufnehmen.

Eine IIFE (Immediately-Invoked Function Expression, sofort ausgeführter Funktionsausdruck) ist dafür der leichtgewichtigste Weg: Sie fügt eine einzige umschließende Funktion hinzu, die einmal ausgeführt wird und nichts unter einem Namen preisgibt.

Vorher: Namen sichtbar

JavaScript

Nach der VM-Obfuskierung bleiben sowohl validateLicense als auch checkExpiry in der Ausgabe namentlich erhalten.

Nachher: Namen hinter einer IIFE verborgen

JavaScript

Jetzt befinden sich beide Funktionsdeklarationen im Rumpf der IIFE. Die IIFE selbst ist das einzige Konstrukt auf oberster Ebene, und eine anonyme IIFE hat keinen Namen, der etwas verraten könnte. Nach der VM-Obfuskierung sind die Funktionsnamen umbenannt und die Rümpfe in Bytecode umgewandelt.

Was sich geändert hat. Die Funktionen sind nicht mehr als globale Werte erreichbar (genau darüber sind ihre Namen durchgesickert). Sie lassen sich weiterhin aufrufen, allerdings nur innerhalb derselben IIFE. Musste tatsächlich etwas validateLicense von außen aufrufen, erreicht es die Funktion nicht mehr; siehe das Trampolin-Muster weiter unten. War das nicht der Fall, haben Sie nichts verloren.

Exportierte Namen, reservierte Bezeichner, Strings und beobachtbares Verhalten können sichtbar bleiben. Testen Sie Integrationen nach dem Einschließen, insbesondere globale Werte, Modulexporte und Code, der Funktionsnamen auswertet.

Ein Kompromiss, den Sie kennen sollten: Enthält der Rumpf der IIFE ein direktes eval oder einen Aufruf von new Function(...) / Function(...) mit dynamischem Rumpf, überspringt die VM-Obfuskierung diese gesamte Funktion samt allem, was darin verschachtelt ist (eine Warnung VMDynamicCodeSkipped), weil der zur Laufzeit erzeugte Quelltext auf Bezeichner verweisen könnte, die der Obfuscator umbenannt hat. Der Code, den Sie gerade in die IIFE verschoben haben, fällt dann auf die gewöhnliche Obfuskierung zurück und verliert seinen Bytecode-Schutz. Verwenden Sie indirektes eval - (0, eval)(...) - oder lesen Sie die Optionen unter Verhalten von direktem eval nach.

Was, wenn eine Funktion wirklich global sein muss?

Manchmal ist eine Funktion tatsächlich ein öffentlicher Einstiegspunkt: ein Inline-Event-Handler, ein JSONP-Callback oder ein Hook für ein Drittanbieter-SDK. Sie haben zwei Möglichkeiten:

  • Ein schlankes Trampolin bereitstellen und die Logik in der IIFE belassen. Deklarieren Sie einen kleinen globalen Wrapper, dessen einzige Aufgabe es ist, die Implementierung innerhalb der IIFE aufzurufen. Der Name des Trampolins ist weiterhin sichtbar, trägt aber keine inhaltliche Information (nennen Sie es etwa __entry1), und die gesamte aussagekräftige Logik bleibt verborgen.

    JavaScript

  • Die Aufrufstelle umschreiben. Existiert der globale Wert nur, weil ein Inline-Handler onclick="validateLicense(...)" ihn benötigt, ersetzen Sie diesen durch addEventListener innerhalb der IIFE. Das HTML nennt die Funktion dann nicht mehr, die Funktion muss nicht mehr global sein, und das Leck verschwindet vollständig.

Variableninitialisierer auf oberster Ebene: vmWrapTopLevelInitializers

Funktionsdeklarationen sind nicht das Einzige, was auf der obersten Ebene einer Datei steht. Variableninitialisierer auf oberster Ebene, also String-Konstanten, Konfigurationsobjekte und Nachschlagetabellen, sind in der Ausgabe ebenso lesbar, wenn sie reines JavaScript bleiben. Eine Zeile wie const API_BASE = '/api/v2/license' verrät einem LLM genauso viel wie function validateLicense.

Die Option vmWrapTopLevelInitializers (boolesch, Standardwert false; die aktuellen VM-Voreinstellungen aktivieren sie) schließt geeignete Initialisierer auf oberster Ebene in eine IIFE ein, sodass der Wert selbst zur Laufzeit von VM-Bytecode berechnet wird, statt als Literal im Quelltext zu stehen. Im Root-Modus werden Initialisierer, die reines JavaScript bleiben, mit der Warnung VMTopLevelInitializerNotVirtualized gemeldet.

Ohne die Option

JavaScript

Mit vmWrapTopLevelInitializers: true

JavaScript

Der Name der Bindung (MY_STRING) liegt aus demselben Grund wie Funktionsnamen weiterhin auf oberster Ebene, denn Code außerhalb der Datei könnte darauf verweisen. Der Wert, den sie enthält, wird jetzt aber von der VM erzeugt und erscheint nicht mehr als lesbarer Text.

Die Option wirkt nur, wenn vmTargetFunctionsMode auf 'root' (der Standardeinstellung) steht und vmAsyncExecutor ausgeschaltet ist. Im Kommentarmodus hat sie keine Wirkung, und es wird keine Warnung gemeldet. Im Root-Modus unter vmAsyncExecutor (das nur asynchrone Funktionen virtualisiert) hat sie ebenfalls keine Wirkung, und der Build meldet VMTopLevelInitializerNotVirtualized für die Initialisierer, die als reines JavaScript verbleiben.

Enthält eine Datei keine Funktion, die die VM virtualisieren kann, meldet das Ergebnis VMNoFunctionsToVirtualize. Schließen Sie den zu schützenden Code in eine Funktion ein, oder wählen Sie sensible Funktionen im Kommentarmodus explizit aus.

Wenn das nicht ausreicht

  • Aus anderen Modulen importierte Namen. Wenn Sie mehrere Dateien bündeln und ein Modul validateLicense exportiert, damit ein anderes es importiert, lässt der Bundler diesen Namen in der gebündelten Ausgabe genauso sichtbar wie Funktionen auf oberster Ebene. Schließen Sie das Bundle selbst in eine IIFE ein (die meisten Bundler können das), oder verschieben Sie den Export in eine IIFE und stellen Sie ihn über ein nichtssagendes Trampolin wieder bereit.
  • Die Laufzeit bleibt beobachtbar. Obfuskierung erhöht den Aufwand, Code zu verstehen und zu verändern; sie kann nicht garantieren, dass Reverse Engineering unmöglich ist. Halten Sie Geheimnisse und maßgebliche Sicherheitsentscheidungen auf dem Server.

Greifen Sie nicht zuerst zu renameGlobals. Die Option existiert und benennt Bezeichner auf oberster Ebene um, kann aber nicht wissen, welche dieser Bezeichner von außerhalb der Datei referenziert werden (andere Bundles, Inline-HTML, dynamische Zugriffe). Wenn Sie sie einschalten, bricht das Integrationen oft auf subtile Weise. Das Einschließen in eine IIFE ist sicherer: Es benennt nichts Globales um, sondern verhindert lediglich globale Werte, die Sie gar nicht brauchten.