Entmystifizierung von Ransomware-Techniken mit .NET-Assemblies: EXE-vs. DLL-Assemblies
Im ersten Teil dieser Serie haben wir einige Techniken untersucht, die von Malware, insbesondere Ransomware , verwendet werden. Wie wir gesehen haben, sind diese einzelnen Techniken wie Downloader, Dropper und Loader sowie Codierung und Verschlüsselung legitime, programmierbare Fähigkeiten, die vom . Net (dot net) Software-Framework und vielen anderen Programmier-Frameworks und Code-Sprachen angeboten werden. Im Folgenden finden Sie eine Collage einiger der Techniken, die im vorherigen Artikel besprochen wurden.

In diesem zweiten Artikel werden wir die Grundlagen von Assemblys über das Framework von Microsoft .Net untersuchen. Wir werden uns eingehender mit den Unterschieden zwischen Assemblys (EXE-vs. DLL) und ihren Beziehungen befassen, um zu ermöglichen, wie diese Funktionen schließlich von einem anfänglichen High-Level-Code wie C#-Programmiercode ausgeführt werden. Wir werden den im vorherigen Artikel vorgestellten Code verwenden, um diese Unterschiede und Beziehungen zu untersuchen.
Was ist Microsoft .Net?
Microsoft .Net ist ein Softwareentwicklungs-Framework, das entwickelt wurde, um mehrere Programmiersprachen zu unterstützen und auf verschiedene Betriebssysteme abzuzielen. Unterstützte Programmiersprachen wie C# (ausgesprochen C sharp) werden kompiliert und als sogenannter verwalteter Code ausgeführt (im Gegensatz zu nicht verwaltetem oder systemeigenem Code). Um dies zu erreichen, führt .Net seinen Code auf einem dedizierten virtuellen Computer und nicht direkt auf der Zielplattform aus. Dieser virtuelle Computer wird als .Net Common Language Runtime (CLR) bezeichnet. Er kann als allgemeiner Vermittler betrachtet werden, der schließlich den kompilierten oder assemblierten Code aus allen verschiedenen Programmiersprachen wie C#, VB.Net und F# ausführt, die .Net unterstützt. Das folgende Beispiel zeigt den Code der Programmiersprache C# aus dem vorherigen Artikel.

Verwalteter Code bedeutet, dass der oben genannte Code der High-Level-Programmiersprache C# und andere wie F# und VB.Net zuerst in eine Zwischensprache (Intermediate Language, IL) kompiliert werden. Der oben gezeigte C#-Code auf hoher Ebene wird in die in der folgenden Abbildung gezeigten Anweisungen für die Zwischensprache kompiliert. Dieser Code ähnelt der Assemblyprogrammiersyntax auf niedriger Ebene.

Diese Zwischensprache (Intermediate Language, IL) wird dann weiter in nativen oder maschinellen Code kompiliert, der auf die entsprechende Maschinenplattform abzielt. Diese Kompilierung erfolgt durch eine andere . Net-Komponente, die als JIT-Compiler (Just-in-Time) bezeichnet wird.
Systemeigener oder maschineller Code ist der Satz von Anweisungen (Nullen und Einsen), die der Prozessor (CPU) eines bestimmten Computers versteht. Dieser letzte Schritt wird von der Common Language Runtime (CLR) verwaltet, die auch den JIT enthält. Bei der CLR handelt es sich um die .NET-Laufzeitumgebung bzw. den virtuellen Computer. Java ist ein weiteres Software-Framework, das das Konzept der intermediären Laufzeiten verwendet. Ähnlich wie die Java Virtual Machine ist sie ein Hauptbestandteil dessen, was die .Net-Plattform unabhängig macht. .NET-Code wird als verwalteter Code bezeichnet, da der Programmiercode von der zwischengeschalteten CLR verwaltet und nicht direkt von der CPU des Computers ausgeführt wird.
Ein Vorteil von verwaltetem Code in .Net ist die automatische Speicherverwaltung und Garbage Collection. Dies bedeutet, dass sich der Entwickler keine Gedanken über das Zuordnen und Aufheben von Computerspeicher in seinem Code machen muss, um Systemressourcen zu sparen, wie dies beispielsweise bei C- oder C++-Code der Fall ist. In .NET gibt es den Garbage Collector, der regelmäßig ausgeführt wird, um freigegebenen Arbeitsspeicher zu verarbeiten. Es kann bei Bedarf auch vom Programmierer aufgerufen werden. Das folgende Diagramm zeigt die Architektur einer .Net-Anwendung .

Im Gegensatz dazu kompilieren Nicht-.NET-Compiler wie VB6, C und C++ ihren High-Level-Code direkt in den Computercode der Zielplattform (Betriebssystem und CPU). Die resultierende ausführbare Datei oder Zusammenstellung von Code ist daher an die Zielcomputerplattform des Compilers gebunden. Dies wird auch als nicht verwalteter oder systemeigener Code bezeichnet. Obwohl die Architektur unterschiedlich ist, ist es möglich, Code aus Assemblys zu verwenden, insbesondere DLLs, die in systemeigenem Code in einer . Net-verwalteteAnwendung mithilfe einer Funktion, die als Interop Marshalling (Platform Invoke) bezeichnet wird. Beispiele hierfür sind die Verwendung von systemeigenen DLLs des Windows-Betriebssystems oder externen Bibliotheken, z. B. in C++ geschriebener Code, auf den in einer verwalteten .NET-Anwendung verwiesen wird, um einige Betriebssystemfunktionen auf niedriger Ebene zu ermöglichen. In diesem Fall kann .Net selbst als sicherer Wrapper für die nativen DLLs betrachtet werden, auf denen das Windows-Betriebssystem basiert und von denen ein Großteil tatsächlich in C++ geschrieben ist.
Was ist eine .Net-Assembly?
Microsoft beschreibt .NET-Assemblys als eine einzelne Bereitstellungseinheit. Das bedeutet, dass eine Assembly eine Sammlung verschiedener Arten von Code und zugehörigen Dateien ist, die in eine Form kompiliert (assembliert) wurden, die auf jeder kompatiblen .Net-Zielplattform ausgeführt werden kann. Die Ausführung erfolgt durch . Nets Common Language Runtime. Beispiele für Assemblys im Windows-Betriebssystem sind ausführbare Dateien (.exe) und Klassenbibliotheksdateien (Dynamic Link Library, .dll).
Wenn Sie sich das folgende Beispielcodebild genauer ansehen, werden auf der linken Seite die ausführbare C#-Assembly und auf der rechten Seite ein weiterer C#-DLL-Assemblycode (auch als Klassenbibliothek bezeichnet) angezeigt. Der ausführbare Code verweist auf die DLL-Datei und ruft dann während der Ausführung eine bestimmte Methode (Funktion) aus dem DLL-Code auf. Diese Verweise und Aufrufe sind in der Abbildung unten hervorgehoben. Wir werden die Details beider Codeteile weiter unten in diesem Artikel erläutern. Wie diese Kombination für böswillige Zwecke genutzt werden kann, zeigen wir in dieser Serie auch.

Im folgenden Beispiel wird im ausführbaren Code manuell auf die DLL-Datei verwiesen. Dies bedeutet, dass während der Kompilierungszeit des ausführbaren Codes auf die DLL und die zugehörigen Informationen zu ihren Metadaten sowie zum Code (bestehend aus Modulen, Klassen und Methoden) verwiesen wird.

Da es sich um eine gemeinsam genutzte Bibliothek handelt, kann DLL-Code nicht direkt ausgeführt werden. Aus Sicht des Codes liegt dies daran, dass DLLs nicht über eine Haupteinstiegspunktfunktion verfügen, von der aus ausgeführt werden kann, und daher nicht als eigenständiger Code ausgeführt werden können, wie es bei einem ausführbaren Code (.exe) der Fall ist. Die folgende Fehlermeldung zeigt beispielhaft die Folgen des Versuchs, eine Klassenbibliothek oder DLL-Datei direkt von einem Compiler aus auszuführen.

Ausführbarer Code hingegen verfügt über eine Haupteinstiegspunktfunktion oder -methode, mit der die Ausführung beginnt, aber eine DLL benötigt nicht wirklich eine Haupteinstiegspunktfunktion, da es sich in erster Linie um eine Bibliothek von Codeblöcken handelt, auf die von anderen Assemblys verwiesen wird.
Sobald darauf verwiesen wird, kann der spezifische Code in der DLL-Datei, der von Interesse ist, zur Ausführung aufgerufen werden. Wie im vorherigen Artikel gezeigt, wird dieser Punkt in den folgenden Codebeispielen (EXE und DLL) wiederholt.

Die ausführbare Anwendung wird ausgeführt und ruft Code aus der DLL auf, auf die verwiesen wurde, um die in der folgenden Abbildung gezeigte Ausgabe zu erzeugen.

Dieses einfache Programm zeigt, wie .Net Assemblies wie EXEs und DLLs zusammen verwendet werden können.
Der DLL-Code, auf den oben verwiesen wird, verfügt über eine Methode (Funktion), die zwei Parameter pro Eingabe akzeptiert – einen Vornamen und ein Alter – und dann eine Begrüßungsnachricht mit diesen Informationen anzeigt. Der ausführbare Code hingegen führt Code aus, der Benutzereingaben wie Vorname und Alter über die Befehlszeile akzeptiert und diese Informationen dann als Argumente oder Eingaben an die DLL-Methode übergibt. Die Meldung des DLL-Codes wird dann mit den Informationen, die die EXE-Anwendung vom Benutzer gesammelt hat, wieder auf dem Konsolenbildschirm angezeigt.
Analysieren von .NET-Assemblys
Beim Ausführen einer statischen Analyse für die ausführbare Datei werden die verschiedenen Verweise auf DLLs und andere Komponenten angezeigt, die zur Ausführung importiert wurden. Zusätzlich zu unserer eigenen benutzerdefinierten DLL importiert die ausführbare Assembly auch zusätzliche DLLs, die mit .Net selbst verknüpft sind, z. B. mscorlib , eine DLL, die Basiscode (Klassen, Typen usw.) enthält und etwas ist, das unser Programm benötigt, um reibungslos zu funktionieren.

In unserer Codeentwicklungsumgebung Visual Studio können wir die Verwendung von mscorlib bestätigen, indem wir die Ursprünge in einem der Datentypen (in diesem Fall Zeichenfolge aus System.String in .Net) zurückverfolgen. Dadurch wird die integrierte .Net-Assembly angezeigt, aus der dieser Typ stammt, nämlich mscorlib , wie unten gezeigt.

String ist ein Datentyp in der Programmiersprache, bei dem der Text, den der Benutzer eingibt und der dann wieder angezeigt wird, gespeichert wird. In unserer statischen Analyse können wir auch die DLL mit dem Namen "DLL_dontNet_Assembly" sehen. Dies ist unsere benutzerdefinierte DLL, die die Methode "DisplayMsgMethod" enthält, die dem Benutzer eine Nachricht anzeigt, nachdem er seine Details eingegeben hat.
In unserem Beispiel haben wir während der Kompilierung unseres gesamten Codes manuell auf unsere benutzerdefinierte DLL verwiesen und sie geladen, bevor das Programm mit der Ausführung begann. Es ist auch möglich, während der Ausführung einer ausführbaren Datei auf eine DLL zu verweisen. Dies kann besonders nützlich sein, wenn wir während der Kompilierung unseres Codes keinen Zugriff auf die gewünschte DLL haben. Dieser Prozess wird als Reflektion bezeichnet und ermöglicht die Möglichkeit, eine .NET-Assembly (Metadaten und Attribute) zu untersuchen und auch den darin enthaltenen Code (Module, Klassen, Methoden und Eigenschaften) während der Laufzeit unseres Programms zu verwenden. Diese Technik kann auch für böswillige Absichten in sogenannten reflektierenden DLL-Injection-Angriffen optimiert werden.
.NET-Assemblys (ausführbare Dateien und Klassenbibliotheken) bestehen auch aus einer Manifestdatei, die Metadaten über die Assembly und den IL-Code (Intermediate Language) enthält, die es der Common Language Runtime ermöglichen, die Assembly auf jeder kompatiblen Plattform auszuführen, auf der .Net ausgeführt werden kann. Die folgende Abbildung zeigt die IL-Assemblyanweisungen und die Manifeststruktur der beiden Assemblys – EXE und DLL. Die Manifestdatei enthält die Metadaten über die .NET-Assembly wie Versionsnummer, Beschreibung usw.

Wir sollten nun ein grundlegendes Verständnis des . Net-Software-Frameworks, der zugehörigen Assemblys und der Art und Weise haben, wie sie miteinander interagieren können.
Im nächsten Artikel werden wir die Techniken und Fähigkeiten, die wir bisher besprochen und gelernt haben, in einer einzigen ausführbaren bösartigen Ransomware-Datei zusammenfassen.
Erfahren Sie mehr darüber, wie Illumio Zero Trust Segmentation Ihnen helfen kann, Ransomware-Verstöße einzudämmen.