Hat Ihre Obfuskierung wirklich funktioniert? Prüfen Sie es in Glass.NET
Ein erfolgreicher Build ist kein Beweis, dass Ihr Code geschützt ist. Öffnen Sie das obfuskierte Assembly in Glass.NET — dem kostenlosen, rein statischen .NET-Decompiler — und lesen Sie es so, wie es ein Angreifer tun wird: bestätigen Sie, dass Umbenennung, String-Verschlüsselung, Kontrollfluss und Virtualisierung gehalten haben, und dass Sie Ihre öffentliche API nicht überschützt haben.
Sie haben Ihren Obfuskator laufen lassen. Der Build war erfolgreich, die Tests sind grün, das Paket ist signiert. Also ist Ihr Code jetzt geschützt — oder?
Vielleicht. Ein erfolgreicher Build sagt Ihnen, dass der Obfuskator nicht abgestürzt ist. Er sagt Ihnen nichts darüber, ob das Ergebnis tatsächlich schwer zu lesen ist. Der einzige Weg, das herauszufinden, ist, das Assembly so anzusehen, wie es ein Angreifer tun wird: es in einen Decompiler ziehen und schauen, was zurückkommt. Wenn das C# noch lesbar ist, haben Sie ein falsches Sicherheitsgefühl ausgeliefert.
Das richtige Werkzeug für diese Prüfung ist ein Decompiler, dem Sie vertrauen und der die Binärdatei nie ausführt. Glass.NET ist kostenlos, und es ist streng statisch — es liest Metadaten und IL und rekonstruiert C#, und führt das inspizierte Assembly nie aus. Das macht es zum natürlichen Begleiter eines Obfuskators wie Nebula.NET: Sie schützen mit dem einen, Sie überprüfen mit dem anderen, und Sie müssen nie einen Build ausführen, bei dem Sie sich nicht sicher sind.
Warum „der Build war erfolgreich” keine Überprüfung ist
Obfuskierung ist eine Reihe von Transformationen, die auf die IL angewandt werden. Jede davon kann falsch konfiguriert, zu eng gefasst oder stillschweigend übersprungen sein. Eine Umbenennungsregel, die zu viel ausschließt, ein String-Verschlüsselungsdurchlauf, der nie lief, eine ausgeschaltete Kontrollfluss-Option, eine Methode, die Sie virtualisieren wollten, aber zu markieren vergaßen — nichts davon erzeugt einen Build-Fehler. Es erzeugt eine Binärdatei, die in Ihrem Ausgabeordner geschützt aussieht und sich in einem Decompiler wie Quellcode liest.
Behandeln Sie die Überprüfung daher als Pflichtschritt, nicht als Nebensache. Das Vorgehen ist einfach: dekompilieren Sie das Assembly vor und nach dem Schutz, und vergleichen Sie.
Richten Sie einen Vorher-Nachher-Vergleich ein
Bewahren Sie eine Kopie des nicht obfuskierten Builds auf. Öffnen Sie sie zuerst in Glass.NET und navigieren Sie zu einer Klasse, die Ihnen wichtig ist — eine Lizenzprüfung, eine Preisberechnung, alles, was Sie nicht kopiert sehen wollen. Sie werden mit ziemlicher Sicherheit überrascht sein, wie lesbar sie ist; bei einem ungeschützten Assembly ist das rekonstruierte C# oft nah genug, um es neu zu kompilieren.
Öffnen Sie nun den geschützten Build. Glass.NET bietet einen Assembly-Vergleich nebeneinander mit einem zeilenweisen Diff, sodass Sie die beiden Versionen nebeneinanderlegen und den Unterschied direkt lesen können, statt zwischen Fenstern hin- und herzuspringen. Gehen Sie den Code, von dem Sie erwartet haben, dass der Obfuskator ihn berührt, Methode für Methode durch und stellen Sie sich für jede die konkreten Fragen unten.
1. Hat die Umbenennung gehalten?
Der billigste Schutz ist die Umbenennung von Bezeichnern — ValidateLicenseKey, _customerBalance und ComputeDiscount in bedeutungslose Symbole zu verwandeln. Im ungeschützten Build sehen Sie Ihre echten Namen. Im geschützten Build sollten sich interne Typen und Member wie a, b, c oder ähnliches Rauschen lesen:
// vorher
internal sealed class LicenseValidator
{
public bool ValidateLicenseKey(string key) { ... }
}
// nachher
internal sealed class a
{
public bool b(string A_0) { ... }
}
Bestätigen Sie zwei Dinge. Erstens, dass die Umbenennung überhaupt stattfand — wenn Ihre Namen überlebt haben, lief der Durchlauf nicht oder Ihre Ausschlussregeln sind viel zu weit. Zweitens, und ebenso wichtig, dass sie nicht zu weit ging: Ihre öffentliche API, Reflexionsziele und Serialisierungsverträge müssen intakt bleiben, sonst bricht Ihre Bibliothek für ihre Aufrufer. Verwenden Sie Gehe zu allem (Strg+T) und die Assembly-Informationsansicht, um zu bestätigen, dass Ihre öffentliche Oberfläche ihre echten Namen behält, während alles Interne verschwunden ist. Über-Obfuskierung ist ein genauso realer Fehler wie Unter-Obfuskierung; Glass lässt Sie beides sehen.
2. Sind die Strings weg?
Dekompilierter Code leckt stark über einfache String-Literale — URLs, SQL, Fehlermeldungen, Formatstrings, Feature-Flag-Namen. String-Verschlüsselung speichert diese verschlüsselt und entschlüsselt sie nur bei der Verwendung. Suchen Sie zur Überprüfung in der dekompilierten Ausgabe nach einem Literal, von dem Sie wissen, dass es im Quellcode war — eine URL, eine Fehlermeldung, eine magische Konstante. In einem korrekt verschlüsselten Build finden Sie es nicht als Text; stattdessen finden Sie einen Aufruf, der den String zur Laufzeit zurückgibt.
Seien Sie ehrlich, was das beweist. Das Literal wird immer noch im Speicher entschlüsselt, wenn der Code läuft, also kann ein entschlossener Angreifer mit einem Debugger es trotzdem bergen. Was Sie bestätigt haben, ist, dass der triviale Angriff — die Binärdatei oder den dekompilierten Quellcode nach einem Schlüsselwort zu greppen — nun nichts mehr zurückgibt. Das allein beseitigt eine große Menge an Exposition mit geringem Aufwand.
3. Ist der Kontrollfluss ein Wirrwarr?
Umbenennung verbirgt, wie die Dinge heißen; Kontrollfluss-Obfuskierung verbirgt, was der Code tut. Eine geschützte Methode sollte nicht mehr zu sauberem, linearem C# dekompilieren. Statt eines lesbaren if/else sollten Sie abgeflachte Logik sehen — ein switch über eine Zustandsvariable, undurchsichtige Verzweigungen, gotos — die Form, die ein Decompiler erzeugt, wenn er die ursprüngliche Struktur nicht mehr wiederherstellen kann:
// nach Kontrollfluss-Obfuskierung, dekompiliert
int num = 0;
while (true)
{
switch (num)
{
case 0: num = (cond ? 2 : 1); continue;
case 1: result = a(); num = 3; continue;
case 2: result = b(); num = 3; continue;
case 3: return result;
}
}
Wenn sich der Körper noch wie der von Ihnen geschriebene Quellcode liest, erreicht der Kontrollfluss-Schutz diese Methode nicht. Die anzustrebende Latte ist nicht nur „verwirrt einen Menschen”, sondern „übersteht automatische De-Obfuskatoren”, daher ist es sinnvoll, auch einen De-Obfuskator über den Build laufen zu lassen — aber die erste, kostenlose Prüfung besteht einfach darin, ihn in Glass zu lesen.
4. Wurden Ihre sensiblen Methoden wirklich virtualisiert?
Code-Virtualisierung ist der stärkste gängige Schutz, und in Nebula.NET ist sie ein Pro-Feature. Sie wandelt die IL einer Methode in Bytecode für eine kleine, ins Assembly eingebettete virtuelle Maschine um, sodass keine gewöhnliche IL übrig bleibt, die ein Decompiler rekonstruieren könnte. Dies ist die Prüfung, bei der das Ergebnis unmissverständlich ist. Schalten Sie Glass.NET in seine IL-Ansicht bei einer Methode, die Sie virtualisiert haben. Sie sollten Ihren Algorithmus nicht sehen. Sie sollten einen Aufruf des VM-Interpreters mit einem undurchsichtigen Datenargument sehen — den Bytecode, der ohne die passende Laufzeit nichts bedeutet:
// IL-Ansicht einer virtualisierten Methode
IL_0000: ldarg.0
IL_0001: ldsfld uint8[] '<VM>'::'data_7f3a'
IL_0006: call object '<VM>'::Run(object, uint8[])
IL_000b: ret
Die C#-Ansicht derselben Methode schrumpft ebenfalls zu einem einzigen undurchsichtigen Aufruf. Wenn Sie hingegen Ihre Logik in einer der beiden Ansichten noch lesen können, wurde diese Methode nie virtualisiert — prüfen Sie, ob Sie die richtigen Methoden markiert haben, denn Virtualisierung wird selektiv angewandt (sie ist zur Laufzeit langsamer, also reservieren Sie sie für Ihren sensibelsten Code, nicht für das ganze Assembly).
Lesen Sie den Expositionsbericht, dann entscheiden Sie
Glass.NET erzeugt außerdem einen Expositionsbericht, der zusammenfasst, wie viel von einem Assembly lesbar ist — eine schnelle, hochrangige Messgröße, die Sie über beide Builds laufen lassen. Das ist ein nützliches Triage-Signal, ersetzt aber nicht das Lesen der Methoden, die Ihnen am wichtigsten sind. Ein Bericht kann Ihnen sagen, dass sich das Verhältnis verbessert hat; nur Ihre eigenen Augen auf dem dekompilierten ValidateLicenseKey können Ihnen sagen, dass das, was Sie wirklich schützen wollten, nicht mehr offen daliegt.
Nichts davon macht Ihren Code unmöglich rückentwickelbar — nichts tut das, und jedes Werkzeug, das „unknackbar” verspricht, verkauft Ihnen etwas. Was dieser Arbeitsablauf Ihnen gibt, ist die Wahrheit: Sie können genau sehen, wie hoch Sie die Latte gelegt haben, die Durchläufe erwischen, die still nichts getan haben, und bestätigen, dass Sie Ihre öffentliche API nicht versehentlich in die Unsichtbarkeit umbenannt haben. Schützen Sie mit Nebula.NET, überprüfen Sie mit Glass.NET — kostenlos, rein statisch und die ehrliche zweite Meinung zu Ihrem eigenen Build. Neu auf der Decompiler-Seite? Beginnen Sie mit wie man eine .NET-DLL zu C# dekompiliert.
Nebula.NET testen
Härten Sie Ihren .NET-Code in wenigen Minuten — starten Sie mit der kostenlosen Edition.