Skip to content
← Alle Beiträge
· Delta1 Labs Glass.NETDekompilierungAnleitung.NET

Eine Single-File- oder eigenständige .NET-App mit Glass.NET dekompilieren

Sie haben eine .NET-App veröffentlicht, ziehen die .exe in einen Decompiler und es kommt nichts Lesbares heraus. Das Problem ist nicht der Decompiler — es ist, dass dotnet publish mehrere Formate hat und die meisten Ihnen kein verwaltetes Assembly dort liefern, wo Sie es erwarten. Hier steht, wo Ihr IL in einem eigenständigen, Single-File-, ReadyToRun- oder Native-AOT-Build wirklich liegt und wie Sie es in Glass.NET öffnen.

Sie haben eine .NET-App veröffentlicht. Jetzt wollen Sie lesen, was tatsächlich ausgeliefert wurde — vielleicht ist es Ihre eigene Version und der Quellcode ist längst verschwunden, vielleicht ein Werkzeug, dessen Verhalten Sie untersuchen. Also ziehen Sie die .exe in Glass.NET und … es kommt nichts Brauchbares zurück. Keine Namespaces, keine Methoden, kein C# — nur ein Stub oder eine glatte Ablehnung. Es ist verlockend zu schließen, der Decompiler sei kaputt. Ist er nicht. Das Problem ist, dass dotnet publish mehrere Ausgabeformate hat und die meisten kein dekompilierbares verwaltetes Assembly an dem Pfad ablegen, nach dem Sie instinktiv greifen.

Sobald Sie wissen, welches Format Sie in Händen halten und wo sein verwaltetes IL wirklich liegt, ist die Dekompilierung selbst der einfache Teil. Dieser Beitrag geht die Formate durch — eigenständig, Single-File, ReadyToRun und Native AOT — sagt genau, wo in jedem das lesbare IL steckt (oder, ehrlich gesagt, wann es keines gibt), und lässt dann den normalen Glass-Ablauf darauf laufen. Glass.NET ist kostenlos und streng statisch: Es liest die Metadaten und das IL des verwalteten Assemblys und rekonstruiert C#, und führt die inspizierte App nie aus. Das zählt hier doppelt, denn eine veröffentlichte App ist genau das, was Sie sonst hätten ausführen müssen, um herauszufinden, was sie tut.

Was alle verwirrt: Der Apphost ist nicht Ihr Code

Das moderne dotnet publish verpackt Ihr Programm in einen nativen Starter namens Apphost — die MyApp.exe, die Sie per Doppelklick starten. Der Apphost ist ein kleiner nativer Bootstrapper, und seine einzige Aufgabe ist, das .NET-Runtime hochzufahren und es auf Ihr verwaltetes Assembly zu richten. Er ist nativer Maschinencode; er enthält nichts von Ihrem C#. Ein Decompiler liest verwaltete Assemblys — IL plus Metadaten — ihn auf den Apphost .exe zu richten, richtet ihn also auf schlicht die falsche Datei.

Ihr Code liegt in einer verwalteten .dll. Ob diese DLL neben dem Apphost liegt oder in ihm versteckt ist, ist die einzige echte Variable, und sie wird vom Veröffentlichungsformat entschieden. Die ganze Aufgabe läuft also auf eine Frage hinaus: Wo ist die verwaltete DLL? Hier ist die Antwort, Format für Format.

Eigenständig: Öffnen Sie die verwaltete DLL, nicht die .exe

Eine eigenständige Veröffentlichung liefert Ihre App zusammen mit ihrer eigenen privaten Kopie des .NET-Runtimes aus, damit sie auf einer Maschine ohne installiertes .NET läuft. Öffnen Sie den Veröffentlichungsordner und Sie sehen eine Menge Dateien: den nativen Apphost MyApp.exe, Dutzende Runtime-Assemblys (System.*.dll, Microsoft.*.dll), einige native Bibliotheken (*.dll, *.so, *.dylib) — und, direkt dort auf der Platte, die gewünschte: Ihr eigenes verwaltetes Assembly, MyApp.dll.

Diese MyApp.dll ist genau das, was der C#-Compiler ausgegeben hat — gewöhnliches IL und vollständige Metadaten. Öffnen Sie diese in Glass (Datei ▸ Assembly öffnen oder auf das Fenster ziehen), nicht die .exe. Der Apphost daneben ist ein Ablenkungsmanöver; ignorieren Sie ihn. Eigenständig ist nicht Single-File, sofern Sie das nicht ebenfalls angefordert haben, standardmäßig wird also nichts gebündelt und nichts muss extrahiert werden — die verwaltete DLL liegt bereits auf der Platte, bereit zum Lesen. Wenn Sie nicht sicher sind, welche der vielen DLLs Ihre ist, sortieren Sie nach Namen und suchen Sie den Namen Ihres Assemblys, oder öffnen Sie eine Kandidatin und prüfen Sie in der Assembly-Informationsansicht Ihre Produktattribute statt einer System.*-Identität.

Single-File: Extrahieren Sie zuerst das Bundle, öffnen Sie dann die DLL

Eine Single-File-Veröffentlichung (PublishSingleFile) geht einen Schritt weiter. Sie nimmt diese verwalteten Assemblys und bettet sie als Ressourcen in den Apphost .exe ein, damit Sie eine Datei statt eines Ordners verteilen. Das verwaltete IL steckt immer noch darin, Byte für Byte unverändert — aber es ist im nativen Host verpackt, es gibt also keine MyApp.dll auf der Platte, die Glass öffnen könnte. Glass liest verwaltete Assemblys; es bricht kein natives Bundle für Sie auf. Sie extrahieren also zuerst das Bundle mit dem Standardmechanismus von .NET und öffnen dann die DLL, die herausfällt.

Der Extractor ist ins Runtime eingebaut. Richten Sie DOTNET_BUNDLE_EXTRACT_BASE_DIR auf ein Verzeichnis und starten Sie die App einmal; der Host entpackt dort seine eingebetteten Assemblys, bevor er sich startet:

# Single-File-App: die eingebetteten Assemblys auf die Platte entpacken
DOTNET_BUNDLE_EXTRACT_BASE_DIR=./extracted ./MyApp
# ./extracted/<appname>/<hash>/MyApp.dll  — gewöhnliches verwaltetes IL, bereit für Glass

Ein ehrlicher Vorbehalt: Dieser Weg führt die App aus, damit sie sich selbst entpackt. Für Ihre eigene Version kein Problem. Für eine Binärdatei, der Sie nicht vertrauen, starten Sie sie nicht — greifen Sie stattdessen zu einem eigenständigen Bundle-Extractor-Werkzeug, das das dokumentierte Bundle-Layout liest und die eingebetteten DLLs auf die Platte schreibt, ohne die ausführbare Datei zu starten. So oder so landen Sie mit MyApp.dll auf der Platte, und von da an bleibt Glass vollständig statisch: Es öffnet immer nur die bereits extrahierte DLL. Ziehen Sie diese MyApp.dll auf Glass und Sie sind zurück bei einer gewöhnlichen Dekompilierung.

ReadyToRun: Darunter ist immer noch IL, es dekompiliert problemlos

ReadyToRun (PublishReadyToRun) fürchtet man leicht ohne Grund. R2R kompiliert vorab ein natives Image Ihrer Methoden, um die Startkosten zu senken — behält aber das ursprüngliche IL neben diesem nativen Image als Rückfallebene. Das Ergebnis ist weiterhin ein völlig gewöhnliches verwaltetes Assembly mit vollständigen Metadaten und IL. Glass liest ein R2R-Assembly genau wie jede andere DLL; der vorkompilierte native Code wird einfach ignoriert, und Sie dekompilieren das IL wie immer. R2R ändert, wie schnell Ihre App startet, nicht, ob sie gelesen werden kann.

Native AOT: Es gibt nichts zu dekompilieren — für niemanden

Native AOT (PublishAot) ist die ehrliche Ausnahme, und es lohnt sich, deutlich zu sein. AOT kompiliert Ihr IL vorab vollständig in nativen Maschinencode und verwirft das IL. Das veröffentlichte Artefakt ist eine echte native Binärdatei: kein verwaltetes Assembly, keine Metadaten, kein IL. Es gibt nichts, was Glass — oder ILSpy, oder dotPeek, oder irgendein .NET-Decompiler — rekonstruieren könnte, denn das, was ein Decompiler liest, ist wegkompiliert. Das ist keine Grenze von Glass; es ist die Natur des Formats, und kein Decompiler kann nativen Code zurück in das C# verwandeln, das ihn erzeugt hat. Müssen Sie eine NativeAOT-Binärdatei analysieren, sind Sie im Gebiet des nativen Reverse Engineering — ein nativer Disassembler — nicht der .NET-Dekompilierung. (Ein praktischer Ausweg: Viele reale Apps sind nur teilweise AOT, und jedes verwaltete Assembly, das außerhalb des AOT-Images blieb, ist wieder eine normale, dekompilierbare DLL. Die Schutz-Kompromisse dahinter behandelt Native-AOT- und Single-File-Apps obfuskieren.)

Dann folgt der normale Glass-Ablauf

Sobald Sie die verwaltete DLL auf der Platte haben — direkt aus einem eigenständigen Ordner oder aus einem Single-File-Bundle extrahiert — ist alles Weitere der gewöhnliche Glass-Ablauf, derselbe wie in Wie man eine .NET-DLL in C# dekompiliert:

  • Öffnen und orientieren. Glass lädt die Metadaten und legt das Assembly als Baum aus Namespaces ▸ Typen ▸ Member aus. Werfen Sie zuerst einen Blick in die Assembly-Informationsansicht — Zielframework und referenzierte Assemblys — um zu bestätigen, dass Sie Ihr Assembly erwischt haben und nicht ein Runtime-Assembly, das als blinder Passagier im Veröffentlichungsordner mitgereist ist.
  • In C# dekompilieren. Klicken Sie auf einen beliebigen Typ oder Member und Glass rekonstruiert lesbares C# auf Anforderung. Sie brauchen keine passende PDB; die Metadaten tragen die echten Typ- und Methodennamen, und Glass synthetisiert lokale Namen, wenn die Symbole fehlen.
  • Steigen Sie zum IL hinab, wenn das C# mehrdeutig ist. Schalten Sie denselben Member auf die IL-Ansicht, wenn eine rekonstruierte async-Methode oder ein Iterator sich seltsam liest — das IL ist das, was tatsächlich läuft, also die Grundwahrheit. (IL lesen geht es durch.)
  • In ein Projekt exportieren. Wenn Sie den Code auf der Platte wollen, um zu greppen, zu vergleichen oder ihn in einer IDE zu öffnen, schreibt Datei ▸ In Projekt exportieren (oder die CLI glass export) eine .csproj plus die rekonstruierten .cs-Dateien. Der vollständige Durchgang steht in ein Assembly in ein baubares Projekt exportieren.

Das Fazit

Das Veröffentlichungsformat ist eine Verpackungsentscheidung. Es ändert, wo Ihr verwaltetes IL liegt, nicht, ob es gelesen werden kann. Eigenständig lässt die DLL auf der Platte — öffnen Sie sie, nicht den Apphost. Single-File versteckt sie im Apphost — extrahieren Sie das Bundle, öffnen Sie dann die DLL. ReadyToRun behält das IL — öffnen Sie das Assembly direkt. Native AOT kompiliert das IL weg — und dann gibt es wirklich nichts zu dekompilieren, für Glass wie für alle anderen. Wissen Sie, welches Sie in Händen halten, und die Dekompilierung ist zwei Klicks entfernt. Und weil Glass.NET kostenlos und streng statisch ist, lesen Sie das Ganze, ohne eine einzige Zeile davon auszuführen. Laden Sie Glass.NET herunter, richten Sie es auf die verwaltete DLL und lesen Sie, was ausgeliefert wurde.

Nebula.NET testen

Härten Sie Ihren .NET-Code in wenigen Minuten — starten Sie mit der kostenlosen Edition.