Uroburos wurde bereits in unserem G DATA Red Paper als besonders raffinierte, sehr komplexe Malware beschrieben. Im entsprechenden Artikel untersuchten wir das Verhalten dieser Malware. Diese Malware zählt zu einem bestimmten Typ, der als Rootkit bezeichnet wird. Der wesentliche Zweck eines Rootkits ist es, das Verhalten des Systems zu ändern und vor allen Dingen die eigenen Aktivitäten zu verbergen. In der Regel nistet sich ein Rootkit im Kernel ein. Für die Analyse dieser Art von Schadsoftware benötigen die Experten spezielle Tools wie WinDbg, um den Microsoft Windows-Kernel zu debuggen. WinDbg ist ein Debugger von Microsoft. Mit diesem Tool kann man Anwendungen im Benutzermodus und im Kernelmodus (z. B. die Treiber) debuggen.
Heute möchten wir Ihnen zeigen, auf welche Weise Experten Malware analysieren.
Wir möchten Ihnen einen Einblick in den Code geben, der eine der raffiniertesten digitalen Bedrohungen darstellt. In diesem aktuellen Fallbeispiel arbeiten wir mit einem Speicherauszug (Absturzabbild) eines mit Uroburos infizierten Systems. Um die Analyse zu erleichtern, haben wir die Erweiterung PyKd hinzugefügt, die zusätzliche Python-Unterstützung bietet. WinDbg verfügt über eine eigene Skriptsprache, ist aber nicht ganz einfach zu verstehen. Die Python-Erweiterung kann hier kostenlos heruntergeladen werden: http://pykd.codeplex.com/
Für diesen Artikel wurde der Computer mit dem Uroburos-Dropper infiziert, der den folgenden md5-Hashwert aufweist: 626576e5f0f85d77c460a322a92bb267.
Das Uroburos-Rootkit fügt mehrere Hooks hinzu, um seine Aktivitäten zu verbergen. In unserem speziellen Fall wird das Hooking als Technik verwendet, um das Verhalten bestimmter Systemfunktionen zu verändern: Das Rootkit täuscht die Ausgabe der Microsoft Windows-API vor. Beispielsweise versteckt es Registry-Einträge, Dateien und vieles mehr.
Für diesen Zweck nutzen die Rootkit-Entwickler Interrupts. Im Folgenden geben wir die Interrupt Descriptor Table (IDT) wieder. In der IDT-Tabelle werden Zeiger auf ISR (Interrupt-Service-Routinen) gespeichert, die aufgerufen werden, wenn ein Interrupt ausgelöst wird.
Einer der Zeiger (0x859e84f0) ist unbekannt und kann nicht aufgelöst werden. Alle anderen Zeiger haben einen Funktionsnamen, der der Adresse folgt. Die letzten Stellen der ersten Spalte entsprechen der ID des Interrupt (in unserem Fall 0xC3). Wir können den Code an dieser Adresse disassemblieren:
Das letzte Argument des WinDbg-Befehls ist die zu disassemblierende Länge (L0x16). Die Funktion beginnt mit einer Reihe von Nulloperationen (NOP). Die Malware verwendet den Interrupt 0xC3. Im nächsten Schritt ist zu ermitteln, wie und wann dieser Interrupt ausgelöst wird. Hier ist der Code am Beginn der Funktion IoCreateDevice ():
Wir sehen, dass der zweite Befehl int 0xC3 (Interrupt 0xC3) lautet. Dank der PyKd-Erweiterung können wir sehr einfach ein Python-Skript erstellen, das jede Funktion mit diesem Interrupt erkennt:
Das Skript beginnt damit, alle exportierten Funktionen in ntoskrnl.exe aufzulisten. Dann prüft es bei jeder Funktion, ob der zweite Befehl int 0xC3 (cdc3) lautet. Ist dies der Fall, wird der Name der exportierten Funktion angezeigt. Hier ist die Ausgabe des Skripts zur aktuellen Analyse:
Mit der Funktion !chkimg hätten wir den Hook ganz einfach identifizieren können. Es war jedoch eine gute Übung, mit PyKd zu experimentieren.
Ein weiterer interessanter Schritt besteht darin, einen Auszug des Treibercodes zu erstellen. Dazu müssen wir zunächst den Beginn der PE finden. Wir können die Adresse anhand der Adresse des Codes finden, der beim Auslösen eines Interrupt ausgeführt wird:
Diese Ausgabe zeigt uns zwei bemerkenswerte Dinge:
Das Rootkit verändert den Anfang der PE, um sich selbst zu verbergen. Einige Tools analysieren den Speicher und suchen nach dem MZ-String, um den Anfang einer PE zu identifizieren. Wenn wir im vorliegenden Fall diese Tools für die Suche nach einer PE-Datei verwendet hätten, wären wir mit dieser Automatisierung niemals in der Lage gewesen, die Malware zu identifizieren. Hier ist eine manuelle Auswertung erforderlich. Um einen Speicherauszug unseres Treibers zu erstellen, müssen wir die PE rekonstruieren. Wie bereits oben erwähnt kennen wir die Größe der Binärdatei jedoch nicht. Deshalb müssen wir einen großen Auszug erstellen, um sicher zu gehen, dass kein Teil der Binärdatei vergessen wird.
Wir können nun mit WinDbg die geladenen (und nicht geladenen) Module anzeigen:
In unserem Fall weist das Rootkit das Modul fdisk.sys auf. Laut dem oben angezeigten Code scheint das Modul nicht geladen zu sein. Doch wie wir zuvor analysiert haben, ist der Code tatsächlich in dem infizierten System vorhanden. Die Entwickler haben also eine Möglichkeit gefunden, die Module zu entladen, während der Schadcode noch läuft!
Wir können die Treiber auflisten:
Der von unserem Modul verwendete Treiber ist \driver\Null. Alle anderen Module sind legitime, von Windows verwendete Module. Wir können die Geräte anzeigen, die dem Treiber zugeordnet sind, auf den wir uns konzentrieren:
Folgende Geräteobjekte sind unserem Treiber zugeordnet:
Darüber hinaus können wir die Beschreibung dieser Geräte sehen:
Zwei Objekte sind besonders interessant: FWPMCALLOUT und RawDisk1. In den folgenden Kapiteln erfahren Sie warum.
Das erste Gerät heißt FWPMCALLOUT. Aufgrund des Gerätenamens können wir vermuten, dass das Rootkit einen Callout für Windows Filtering Platform (WFP) registriert. Bei der WFP handelt es sich um verschiedene API- und Systemdienste, die eine Plattform für die Erstellung von Netzwerkfilteranwendungen bereitstellen. In unserem Fall nutzt das Rootkit diese Technologie, um eine Deep-Packet-Inspection (DPI) und Modifikationen am Netzwerkdatenfluss vorzunehmen. Zweck dieses Geräts ist es, relevante Daten abzufangen und Befehle zu empfangen, sobald eine Verbindung zum Command & Control-Server oder zu anderen infizierten Rechnern der lokalen Umgebung hergestellt wird, welche als Relaisstationen verwendet werden.
Da es keinen Befehl gibt, mit dem man die WFP-Callouts einfach auflisten kann, müssen wir die benötigten Informationen in mehreren Schritten extrahieren:
Zunächst enthält die Variable netio!gWfpGlobal den Ausgangspunkt für die WEP-Datenstrukturen:
Eine globale Tabelle speichert die Anzahl der Callouts und das Array der entsprechenden Callout-Strukturen.
Mit der folgenden Methode können geeignete Offsets gefunden werden:
Die erste Zahl ist das Offset, das die Gesamtzahl der erstellten Callouts enthält – natürlich in Hexadezimalzahlen:
Die zweite Zahl ist das Offset mit dem Array, in dem die Callout-Struktur gespeichert ist:
Das Pooltag dieser Adresse bestätigt unsere bisherigen Ergebnisse und beweist, dass wir auf der richtigen Spur sind:
Wir können nun die Größe der einzelnen Strukturen extrahieren, welche in dem Array gespeichert sind. Da dies nicht von Microsoft dokumentiert ist, ermitteln wir die Größe, indem wir die Funktion InitDefaultCallout() disassemblieren:
Zu guter Letzt verwenden wir einen einzeiligen Befehl, um die Elemente dieses Arrays aufzulisten:
Die Liste der Elemente erinnert uns an die Informationen, die wir in der IDT gesehen haben: Zwei Adressen werden nicht aufgelöst. Diese beiden WFP-Callouts sind: 0x859b5040 und 0x859b5520. WinDbg ist nicht in der Lage, diese beiden Adressen aufzulösen, weil die Adressen nicht bekannt sind. Es sind keine Adressen von Microsoft. Da wir jetzt die Adressen haben, können wir mit dem Befehl !pool bestätigen, dass die Adressen sich in derselben Region befinden wie der Code, der beim Auslösen eines Interrupt ausgeführt wird:
Bei der oben erfolgten Betrachtung der Geräteobjekte sind wir auf zwei Geräte mit sehr ähnlichen Namen gestoßen: RawDisk1 und RawDisk2. Schauen wir uns das erste dieser Geräte einmal genauer an:
Wie wir sehen, handelt es sich bei dem Gerät RawDisk1 um ein NTFS-Dateisystem – ein virtuelles Dateisystem, in dem das Rootkit seine Konfiguration und die exfiltrierten Daten speichert...
Wir können die verwendeten Dateien (geöffnete Handles) im Dateisystem identifizieren, z. B. \queue und \klog:
Dank diesem Befehl sind wir in der Lage, die Dateien aufzulisten, die vor dem Betriebssystem verborgen werden.
Microsoft hat für die 64-Bit-Versionen von Windows Vista und für neuere Betriebssysteme eine Treibersignierungsrichtlinie entwickelt. Zum Laden eines Treibers muss die .sys-Datei von einem authentifizierten Herausgeber signiert sein. Entwickler können den Treibersignierungszwang in der Entwicklungsphase eines Treibers deaktivieren. Das heißt, Entwickler müssen in der Entwicklungsphase nicht jede kompilierte Treiberversion signieren. Dieser Modus wird als „Testmodus“ bezeichnet. In unserem Fall ist das Rootkit nicht signiert und hätte daher normalerweise keine Chance, von der Microsoft-Richtlinie akzeptiert zu werden. Es umgeht jedoch die Kontrollen, indem es das digitale Signaturverfahren deaktiviert. Der Status dieser Funktion wird in der globalen Variable nt!g_cienabled gespeichert. Vergleichen Sie den Wert dieser Variablen auf einem sauberen, nicht infizierten System mit denselben Informationen auf einem infizierten System:
Der obige Code zeigt, dass der Wert auf 1 gesetzt ist. Das bedeutet, dass der Zwang zur digitalen Signatur aktiviert ist. Hier der Wert auf dem infizierten System:
Wir können deutlich erkennen, dass die Malware den Treibersignierungszwang deaktiviert hat. Eigentlich könnten wir dasselbe Ergebnis mit dem Befehl bcdedit.exe -set TESTSIGNING OFF erreichen, also mit dem Befehl zum Wechsel in den Testmodus, um den nicht signierten Treiber laden zu können. Der Unterschied ist jedoch folgender: Der Befehl bcdedit.exe löst aus, dass unten auf dem Desktop ein Meldungsfenster angezeigt wird. Diese Methode wäre also nicht gerade unauffällig. Der Vorgang könnte sofort entdeckt werden.
Weitere Informationen zur Umgehung des Treibersignierungszwangs durch die Malware finden Sie in unserem Artikel im SecurityBlog: Uroburos – Detaillierte Analyse der Umgehung des Kernel-Schutzes
Was Sie soeben gesehen haben, war nur ein sehr kleiner Ausschnitt aus der umfangreichen Analyse komplexer Malware und eine sehr kurze Einführung in WinDbg. Im Allgemeinen ist es ziemlich schwer, ein derart komplexes Tool zu verstehen. Doch bei der Analyse eines Kernel-Rootkits bleibt den Experten keine andere Wahl.
Die Ergebnisse erscheinen absolut logisch und nachvollziehbar, wenn sie wie hier im Artikel übersichtlich mit Codeausschnitten dokumentiert werden. Aber bitte glauben Sie uns: Die Arbeit mit Malware-Code erfordert viel Training, Erfahrung und Zeit.