Schützen Sie .NET-Code vor neugierigen Augen

Veröffentlicht: 23. Aug 2005

Von Mathias Schiffer

.NET-Lösungen lassen sich nicht nur disassemblieren, sondern auch dekompilieren. Ohne besondere Schutzmaßnahmen liefern Sie in .NET-Anwendungen Ihren Sourcecode quasi mit. Mehr über mögliche Schutzmaßnahmen erfahren Sie in diesem MSDN Quickie.

In welcher .NET-Sprache Sie Ihren Code auch schreiben mögen - am Ende wird daraus dank des jeweiligen sprachspezifischen Compilers der sogenannte MSIL-Code: "Microsoft Intermediate Language" heißt die .NET-Vorstufe zum ausführbaren Code. MSIL-Code ist nicht gerade trivial zu lesen und deutlich oberhalb der meisten Assemblerdialekte einzustufen.

Auf dieser Seite

 Lagebesprechung
 Tarnen, Täuschen und Verschleiern
 Code-Verschleierung durch Hilfsprogramme
 Dotfuscator Community Edition einsetzen
 Geht nicht - gibt's
 Der Autor

Lagebesprechung

Qualitativ betrachtet liegt Ihr Sourcecode damit zunächst nicht deutlich weiter offen als schon immer: Eine herkömmliche, für den Prozessor verständliche EXE-Datei können Sie mit einem Disassembler aus der vorliegenden Maschinensprache in eine Assemblersprache übersetzen (mit dem semantischen Nachteil, dass Maschinensprache im Gegensatz zu einer Assemblersprache definitorisch nicht zwischen Code und Daten unterscheiden kann). Das funktioniert so auch mit .NET Assemblies - Microsoft liefert mit dem .NET Framework sogar selber ein passendes Werkzeug mit: Mit ILDASM.EXE können Sie aus einer .NET Assembly MSIL-Code erzeugen und anzeigen. Diesen Code ohne weitere kontextrelevante Informationen lesen und im Gesamtbild verstehen zu können ist nicht gerade trivial, geschweige denn schnell gemacht - prinzipiell aber möglich. Meist ist der dafür erforderliche Aufwand jedoch teurer als eine Neuentwicklung - womit Sie Ihren Sourcecode überwiegend als wirtschaftlich betrachtet geschützt ansehen können.

Die interessanteste Frage für den Schutz Ihres geistigen Eigentums ist daher zumeist die, ob aus solchem Assemblercode der ursprüngliche Sourcecode im Wesentlichen automatisch wiederhergestellt werden kann. Mit geringen Einschränkungen lässt sich diese Frage für .NET mit einem klaren Ja beantworten. Das liegt bereits systemimmanent in dem Umstand begründet, dass die .NET Runtime zur Laufzeit prüfen können soll, welche Arbeiten Ihr Sourcecode auf der Zielmaschine verrichtet. Auch die grundsätzlich mögliche Plattformunabhängigkeit von MSIL-Code trägt hierzu bei.

Doch nicht nur eine theoretische, sondern auch die praktische Möglichkeit zur Rückgewinnung von Sourcecode besteht bereits: Verschiedene findige Köpfe und Firmen haben Decompiler für die MSIL entwickelt, die aus dem MSIL-Code den Sourcecode einer .NET-Hochsprache erzeugen. Naturgemäß ist die Zielsprache solcher Tools überwiegend das .NET-systemnahe C#, doch auch andere Vertreter sind zu finden. Ob nun in der Ursprungssprache oder einer anderen .NET-Hochsprache: Ihr geistiges Eigentum ist bei .NET-Assemblies ohne zusätzliche Anstrengungen noch nicht hinreichend vor einfacher Einsichtnahme geschützt.

Was also können Sie tun, um Ihre Arbeit besser zu schützen?

 

Tarnen, Täuschen und Verschleiern

Ein allgemeingültiges Patentrezept mit hundertprozentiger Sicherheit für Ihren Sourcecode gibt es nicht. Denn selbst unabhängig von .NET gilt: Soll Code auf der lokalen Maschine ausgeführt werden, so muss er spätestens dem Prozessor als Maschinensprache zugeführt werden. Die auszuführenden Anweisungen müssen also irgendwo hinterlegt sein und können daher ultimativ auch eingesehen werden.

Das gilt auch für die häufig aufgebrachte Idee, das .NET Framework-Tool NGEN.EXE einzusetzen: Hiermit können Sie aus assembliertem MSIL-Code nativen, hardwareabhängigen Code erzeugen - ein Prozess, der idealerweise zwecks Plattformunabhängigkeit und Hardwareoptimierung eigentlich beim ersten Aufruf der Codebestandteile ("Just In Time - JIT") auf dem Zielrechner durchgeführt werden sollte. NGEN.EXE entfernt dabei aber weder MSIL-Code noch Metadaten aus dem resultierenden "Native Image"; das grundlegende Problem bleibt hiermit also unverändert bestehen.

Schutz kann es daher nur aus zwei Richtungen geben: Entweder wird der benötigte Code gar nicht erst auf der lokalen Maschine ausgeführt, sondern diese erhält nur die Ergebnisse der Codeausführung (denken Sie hier auch an die Nutzung von Web Services). Oder der Aufwand für eine mögliche Rückentwicklung des lokal notwendigen Codes wird zumindest wirtschaftlich inakzeptabel - auch wenn Ihnen niemand garantieren wird, dass kein talentierter Teenager aus purem Ehrgeiz sämtliche Regeln der Wirtschaftlichkeit über Bord wirft.

Hier soll es lediglich um Ansätze gehen, das sinnvolle Rückentwickeln lokal vorliegenden Codes zu erschweren. Das fängt bei ganz offensichtlichen Möglichkeiten an. Beschreiben Sie doch beispielsweise in einem kurzen Satz, was dieser Code bewerkstelligt:

  Public Function a(ByVal b As Double, ByVal c As Double) As Double
    Return b * c
  End Function

Selbstverständlich wird hier aus dem Nettowert b der Bruttowert a auf Basis des zu übergebenden Steuerfaktors c berechnet. Das haben Sie überraschenderweise nicht unmittelbar erfasst? Die folgende Routine ist funktional absolut identisch:

  Private Function Bruttobetrag(ByVal Nettobetrag As Double, ByVal Steuerfaktor As Double) As Double
    ' Retourniert den Bruttobetrag auf Basis des Nettobetrags und des Steuerfaktors (Prozentsatz/100+1)
    Return Nettobetrag * Steuerfaktor
  End Function

In beiden Fällen lag der Sourcecode vor. Im ersten Beispiel ist nicht weniger transparent, was der Sourcecode bewirkt - wofür aber diese Tätigkeit geeignet sein soll, das bleibt verborgen. Erst durch die für Menschen sinnvolle Namensgebung im funktional identischen Beispiel ergibt sich ein tieferes Verständnis für den Code. Es sei denn, der Autor dieses Beispiels hätte eine völlig andere Multiplikationsaufgabe als Prozentrechnungsaufgabe "getarnt" und uns somit nicht im offensichtlich Unklaren gelassen, sondern perfide getäuscht!

 

Code-Verschleierung durch Hilfsprogramme

Die Erschwernis der Verständlichkeit von Sourcecode kann also helfen, dessen Inhalte und Ideen - Ihr geistiges Eigentum - vor neugierigen Augen zu schützen. Solche Verschleierungen, englisch "Obfuscations", sind aber andererseits für die Entwickler des zu schützenden Codes problematisch, da auch sie selber schnell nicht mehr verstehen, was eigentlich wo in ihrem Sourcecode geschieht.

Hier greift der natürliche Feind jedes Decompiler-Anwenders ein: Ein "Obfuscator" ist ein Hilfsprogramm, das im einfachsten Fall wie oben alle nicht in öffentlichen Schnittstellen benötigten Bezeichner im MSIL-Code durch zufällig generierte und daher zusammenhangslose Namen versieht. Verschiedenste Verfeinerungen finden sich in weiter durchdachten Methoden und sollen Decompilern, zumindest aber deren Anwendern, das Leben schwer machen. Ein Beispiel wäre etwa das gleichartige Umbenennen unterschiedlich benannter Methoden, sofern deren Signaturen sich unterscheiden - möglich dank Überladung. Wenn zehn völlig unterschiedliche Methoden identisch "a" heißen, kann das auch bei perfekter Dekompilierung schnell verwirrend werden.

Ihr ursprünglicher Sourcecode bleibt von all dem natürlich gänzlich unbeeinflusst, so dass Sie diesen unbeeindruckt weiter warten, pflegen und fortentwickeln können. Allerdings müssen Sie nach jedem erneuten Kompilieren Ihrer Assemblies erneut verschleiern.

Seit Visual Studio .NET 2003 wird mit dem Produkt "Dotfuscator Community Edition" ein leistungsreduzierter "Obfuscator" von einem Dritthersteller mit ausgeliefert. Erreichbar ist das Light-Produkt über das Extras-Menü der Entwicklungsumgebung.

Bitte beachten Sie: Auch andere Dritthersteller bieten technologisch und preislich konkurrenzfähige Produkte an. Im Rahmen dieses MSDN Quickies wird aus nahe liegenden Gründen lediglich das mit Visual Studio .NET gelieferte Light-Produkt behandelt - eine Produktempfehlung soll damit jedoch nicht ausgesprochen werden.

 

Dotfuscator Community Edition einsetzen

Wenn Sie die funktionsbeschränkte Dotfuscator-Version aus Visual Studio .NET heraus gestartet haben, werden Sie zunächst weiträumig darüber informiert, dass Sie ein kostenpflichtiges Vollprodukt erwerben können. Zudem werden Sie gefragt, ob Sie das Light-Produkt registrieren wollen. Der Werbedialog bleibt Ihnen zwar auch nach der Registrierung noch erhalten - er blendet sich dann aber nach wenigen Sekunden von selber aus.

Sie können Dotfuscator Community Edition von der Kommandozeile oder per Windows-Frontend verwenden. Zur Anwendung per Kommandozeile sei auf die Online-Hilfe des Produkts verwiesen, in dem Merkmale, die auch in der vorliegenden Light-Version des Produkts verfügbar sind, durch ein Symbol mit der Aufschrift "CE" gekennzeichnet sind.

Starten Sie Dotfuscator aus dem Visual Studio Menü "Extras" heraus, so werden Sie nach dem Werbedialog gefragt, ob Sie ein neues oder ein bestehendes Projekt verwenden möchten. Lassen Sie sich nicht täuschen: Hier geht es nicht um Ihre Visual Studio .NET-Projekte, sondern um Dotfuscator-Projekte.

Bei diesem Dialog geht es um Dotfuscator-Projekte
Abbildung 1: Bei diesem Dialog geht es um Dotfuscator-Projekte

Wollen Sie Ihre Anwendung erstmalig verschleiern, so legen Sie hier also ein neues Projekt an. Danach finden Sie sich in der Oberfläche von Dotfuscator wieder - und bleiben damit zunächst sich selber überlassen. Hier wäre ein assistierender Dialog sicherlich eine hilfreichere Begrüßung.

Windows-Oberfläche von Dotfuscator - was nun, was tun?
Abbildung 2: Windows-Oberfläche von Dotfuscator - was nun, was tun?

Der zunächst wichtigste Reiter für das weitere Vorankommen findet sich leider nicht als erster oder vorderster Reiter. Um Assemblies auswählen zu können, deren Inhalte Sie verschleiern möchten, wechseln Sie auf den Reiter "Trigger". Dort können Sie Assemblies im Dateisystem suchen, auswählen und dem Dotfuscator-Projekt hinzufügen.

Zu verschleiernde Assemblies auswählen
Abbildung 3: Zu verschleiernde Assemblies auswählen

Auf dem Reiter "Umbenennen" können Sie anschließend durch einfaches Anklicken definieren, welche Bezeichner nicht umbenannt werden dürfen. Benötigen Sie keine Ausnahmeregelungen, ignorieren Sie diesen Reiter einfach ebenso wie die meisten anderen: Die Registerkarten "Ablaufsteuerung", "Zeichenfolgenverschlüsselung" und "Entfernen" bleiben ohnehin dem Vollprodukt vorbehalten.

Auch wenn Sie nur eine Assembly verschleiern wollen, müssen Sie letztlich auf dem Reiter "Erstellen" noch ein Zielverzeichnis für die Ergebnisdatei angeben. Der Assemblyname selber wird nicht verändert - geben Sie also das Verzeichnis Ihrer unverschleierten Assembly an, so wird diese überschrieben.

Zielverzeichnis angeben - und dann ab die Post!
Abbildung 4: Zielverzeichnis angeben - und dann ab die Post!

Klicken Sie letztlich noch auf den Button oder den Datei-Menüeintrag "erstellen" (Sie können dafür auch den "Play"-Button in der Toolbar verwenden), wird Ihre Assembly verschleiert und anschließend im Zielverzeichnis abgelegt. Beispielsweise mit ILDASM.EXE, aber auch mithilfe des Reiters "Ausgabe" können Sie die Arbeitsergebnisse von Dotfuscator danach begutachten.

Eigentlich war also alles viel einfacher als es zunächst aussah. Bezüglich näherer Details zu Einstellungen auf den Reitern "Eigenschaften" und "Optionen" sowie zur Umbenennungs-Ausschließung konsultieren Sie bitte die Hilfedatei. Darin können Sie auch weitere Informationen über die von Dotfuscator verwendeten Konzepte erfahren.

 

Geht nicht - gibt's

Nicht in jedem Fall ist die Verschleierung so einfach abgeschlossen.

Versehen Sie etwa Ihre Assembly mit einem Strong Name, bedeutet das gleichzeitig, dass die Assembly nach der Zuweisung des Strong Name nicht mehr verändert werden kann. Genau das ist aber die Aufgabe eines Obfuscators: Das Resultat Ihres Codes und nicht der Code selbst soll verändert werden, damit ein Dekompilat für Dritte nicht ohne besonderen Aufwand durchschaubar ist.

Konkret bedeutet das, dass Sie mit keinem Obfuscator eine bereits mit einem Strong Name versehene Assembly schützen können. Die Lösung für dieses Problem liegt im verzögerten Signieren der Assembly erst nach der Kompilierung und entsprechender Verschleierung. Wie das genau geht, erklärt der MSDN Quickie "Verzögertes Signieren von Assemblies"

 

Der Autor

Mathias Schiffer widmet sich als freier Softwareentwickler und Technologievermittler größeren Projekten ebenso wie arbeitserleichternden Alltagslösungen. Seit Jahren gibt er sein Wissen in unzähligen Publikationen und Beratungen auch an andere Entwickler und Entscheider weiter. Sie erreichen ihn per E-Mail an die Adresse Schiffer@mvps.org.