Volatility-Workshop (2) : Der Weg von Prozessen zu Spuren und Artefakten von Malware
Im Nachgang zu seinem Überblicksbeitrag [1] zu Prozessen und Open-Source-Tools für Incident-Response und Forensik behandelt unser Autor in einer Reihe von Workshop-Beiträgen den konkreten Einsatz entsprechender Softwarewerkzeuge. Als erstes steht die Hauptspeicherforensik mit Volatility auf dem Programm.
Dieser Workshop stellt verschiedene grundlegende Volatility-Plugins vor, die es ermöglichen, ein gutes Verständnis zu Ereignissen auf einem kompromittierten System zu gewinnen und dadurch einen ersten Einblick in mögliche Aktivitäten von Schadsoftware zu erhalten. Im Folgenden werden typische Eigenschaften von Windows-Prozessen aufgeführt, die sich mit Volatility analysieren und extrahieren lassen, um irreguläres Verhalten zu identifizieren und einem forensischen Analysten eine Handhabe für weitere Schritte zu liefern.
Auflisten von Process-Handles
Sobald man einen schädlichen Prozess identifiziert hat, möchte man während einer Untersuchung vielleicht wissen, auf welche Objekte (z. B. Prozesse, Dateien, Registrierungsschlüssel usw.) er zugreift, um eine Vorstellung von mit der Malware verbundenen Komponenten und einen Einblick in ihre Funktionsweise zu erhalten. So könnte etwa ein Keylogger auf eine Protokolldatei zugreifen, um erfasste Tastenanschläge zu protokollieren, oder ein Schadprogramm möglicherweise aktiven Zugriff auf eine Konfigurationsdatei besitzen.
Um auf ein Objekt zuzugreifen, muss ein Prozess zunächst ein sogenanntes Handle für dieses Objekt öffnen, indem er eine API wie CreateFile oder CreateMutex aufruft. Ein Handle ist eine indirekte Referenz auf ein Objekt: Die Objekte befinden sich im Kernelspeicher (Kernel-Space), während der Prozess im Benutzerbereich (User-Space) abläuft. Durch diese Trennung bedingt, kann ein Prozess nicht direkt auf Objekte zugreifen und benötigt daher ein Handle, das ein Objekt repräsentiert. Sobald ein Prozess ein solches Handle öffnet, verwendet er es, um nachfolgende Operationen auszuführen – beispielsweise zum Schreiben in oder Lesen aus einer Datei.
Zu jedem Prozess gibt es eine private Handle-Tabelle, die sich im Kernelspeicher befindet – sie enthält alle Kernelobjekte wie Dateien, Prozesse und Netzwerk-Sockets, die diesem spezifischen Prozess zugeordnet sind. Wenn der Kernel von einem Prozess die Anforderung erhält, ein Objekt zu erstellen (über eine API wie CreateFile), wird das Objekt im Kernelspeicher angelegt. Ein Zeiger (Pointer) auf das Objekt wird in den ersten verfügbaren Slot in der Prozess-Handle-Tabelle platziert und der entsprechende Indexwert an den Prozess zurückgegeben. Dieser Indexwert ist das Handle, welches das Objekt repräsentiert.
Auf einem laufenden System kann man die Kernel-Objekte, auf die ein bestimmter Prozess zugreift, mit dem Werkzeug Process Explorer überprüfen (aus der Sysinternals Suite, siehe https://docs.microsoft.com/dede/sysinternals/). Dazu startet man den Process Explorer als lokaler Administrator, klickt mit der linken Maustaste auf einen beliebigen Prozess und aktiviert dann in der GUI mit View -> Lower Pane View -> Handles (oder per Tastatur mit Ctrl-H) die Handle-Übersicht im unteren Bereich.
Erinnerung: csrss.exe
csrss.exe ist ein legitimer Betriebssystemprozess, der bei der Erstellung eines jeden Prozesses und Threads eine Rolle spielt. Aus diesem Grund sieht man, dass er offene Handles für die meisten Prozesse (außer für sich selbst und seine übergeordneten Prozesse) besitzt, die auf dem System laufen. Eine der Methoden, die das bereits in Teil 1 vorgestellte Volatility-Plugin psxview verwendet, basiert darauf, die Handle-Tabelle von csrss.exe zu durchlaufen, um Prozessobjekte zu identifizieren. Wenn es mehrere gestartete Instanzen von csrss.exe gibt, analysiert psxview die Tabellen aller dieser Instanzen auf dem IT-System, um die laufenden Prozesse aufzulisten (mit Ausnahme von csrss.exe und seiner übergeordneten Prozesse smss.exe und „System“).
Das Plugin „handles“
Mithilfe des „handles“-Plugins kann man aus einem Hauptspeicherabbild eine Liste aller Kernel-Objekte erhalten, auf die von einem Prozess zugegriffen worden ist. Abbildung 1 zeigt beispielsweise die Handles des Prozesses mit PID 1496. Wenn man das Plugin ohne die Option „-p“ (für spezifische PIDs) aufruft, zeigt es Handle-Informationen für alle Prozesse an. Mit der Option „-t“ lassen sich die Ergebnisse zudem für einen bestimmten Objekttyp (Event, File, Key, Mutant, Port, Process, Thread oder Token) filtern.

Beim Beispiel in Abbildung 2 wurde das „handles“-Plugin gegen das bereits aus Teil 1 bekannte, mit EternalBlue infizierte Hauptspeicherabbild ausgeführt, um die sogenannten Mutexe – kurz für Mutual Exclusions – aufzulisten, die durch den bösartigen Prozess „CPUInfo“ verwendet werden.

Die Ausgabe zeigt, dass eine Mutex mit dem Namen „jghfhjdghgfhfdghfghfgh“ erstellt wurde, um die Anwesenheit der Schadsoftware auf dem System zu markieren. Solche Mutexes können nützliche Hinweise für das Monitoring sein. In Bezug auf die Suche nach Datei-Objekten wird im gezeigten Beispiel eines mit EternalBlue infizierten Systems deutlich, dass zwei Schadsoftwareprozesse (PID 1164/CPUInfo.exe und PID 1236/mssecsvc.exe) die Konfigurationsdateien „counters.dat“ und „R000000000018.clb“ teilen (Abbildung 3).

DLLs – unauffällig und wirksam
Eine gebräuchliche Möglichkeit, um sich im IT-System dauerhaft zu verankern, ist die Nutzung sogenannter „Dynamic Link Libraries“ (DLL), die von regulären Prozessen und ihrem privilegierten Securitykontext nachgeladen werden. Um alle geladenen Module (ausführbare Dateien und DLLs) aufzulisten, kann man das dlllist-Plugin von Volatility verwenden, das auch den vollständigen Pfad anzeigt, der einem Prozess zugeordnet ist.
Auflisten von DLLs mit dlllist
Ein neues Beispiel-Speicherabbild zeigt, wie die Schadsoftware Stuxnet ihre bösartige Funktionalität als Service-DLL implementiert (C:\WINDOWS\system32\KERNEL32.DLL.ASLR.0360b7ab), die vom Prozess lsass.exe geladen wird. Abbildung 4 ist die Ausgabe des dlllist-Plugins, in der man ein verdächtiges Modul mit der Nicht-Standard-Erweiterung „.0360b7ab“ sieht, das vom Prozess lsass.exe (PID 1928) geladen wurde. Die erste Spalte „Base“ gibt dabei die Adresse im Speicher an, in der das Modul geladen wird (Basisadresse).

Hintergrund: Process Environment Block (PEB)
dlllist bezieht seine Informationen über die geladenen Module aus dem „Process Environment Block“ (PEB). Die PEB-Struktur befindet sich im Prozess-Speicher (im User-Space) und enthält Metadaten darüber, wo eine ausführbare Prozessdatei geladen wird, sowie ihren vollständigen Pfad auf der Festplatte und Informationen über die geladenen Module (ausführbare Dateien und DLLs). Das Plugin findet die PEB-Struktur eines jeden Prozesses durch Rückgriff auf die _EPROCESSStruktur im Kernelspeicher: Sie besitzt ein Feld namens „Peb“, das den Zeiger auf den PEB enthält.
Der PEB enthält noch eine weitere Struktur namens „Ldr“ (vom Typ _PEB_LDR_DATA), die dreifachdoppelt verknüpfte Listen verwaltet – nämlich die InLoadOrderModuleList, InMemoryOrderModuleList und InInitializationOrderModule- List. Jedes Element darin ist eine Struktur vom Typ _LDR_DATA_TABLE_ENTRY, die Informationen über die geladenen Module enthält (Process-Executable und DLLs).
Es ist daher möglich, Informationen zu sammeln, indem man einer dieser dreifach-doppelt verlinkten Listen folgt: InLoadOrderModuleList organisiert Module in der Reihenfolge, in der sie geladen werden, InMemoryOrderModuleList in der Reihenfolge, in der sie sich im Prozess-Speicher befinden und InInitializationOrderModuleList organisiert Module in der Reihenfolge, in der ihre DllMain-Funktion ausgeführt wurde.
Alle drei PEB-Listen enthalten Informationen über die geladenen Module – die Basisadresse, Größe, den vollständigen Pfad, der dem Modul zugeordnet ist et cetera. Es ist jedoch wichtig, zu beachten, dass die InInitializationOrderModuleList keine Informationen über die ausführbare Prozessdatei enthält, da diese im Vergleich zu den DLLs anders initialisiert wird.
Das Problem beim Ermitteln der Modulinformationen aus einer dieser drei PEB-Listen ist, dass sie anfällig für Direct-Kernel-Object-Manipulation-(DKOM)-Angriffe sind: Denn sie alle befinden sich im User-Space – ein Angreifer kann also eine bösartige DLL in den Adressraum eines Prozesses laden und sie von einer oder allen PEB-Listen trennen (unlink), um sich vor Tools zu verstecken, die diese Listen sowie ihre Beziehungen untereinander auswerten. Um dieses Problem zu umgehen, kann man das ldrmodules-Plugin verwenden, das anders vorgeht.
Erkennung einer versteckten DLL mit ldrmodules
ldrmodules vergleicht Modulinformationen aus den drei PEB-Listen (im Prozessspeicher) mit den Informationen aus einer anderen Datenstruktur im Kernelspeicher, den „Virtual Address Descriptors“ (VADs). Der Speichermanager verwendet VADs, um virtuelle Adressen im Prozessspeicher zu verfolgen, die reserviert (oder frei) sind. Es handelt sich dabei um eine binäre Baumstruktur, die Informationen über sogenannte „nahezu zusammenhängende“ Speicherbereiche im Prozess-Speicher vorhält.
Für jeden Prozess verwaltet der Speichermanager einen Satz von VADs und jeder VAD-Knoten beschreibt einen nahezu zusammenhängenden Speicherbereich. Wenn der Prozessspeicherbereich eine speicherabgebildete Datei (z. B. eine ausführbare Datei, DLL) enthält, speichert der VAD-Knoten zudem Informationen über seine Basisadresse, den Dateipfad und den Speicherschutz.
Um die Informationen des DLL-Moduls zu erhalten, zählt das ldrmodules-Plugin alle VAD-Knoten auf, die gemappte ausführbare Images enthalten und vergleicht die Ergebnisse mit den drei PEB-Listen, um Abweichungen zu identifizieren. Abbildung 5 zeigt die Modulliste eines Prozesses aus einem Hauptspeicherabbild, das mit Stuxnet infiziert ist. Man kann erkennen, dass das Plugin (mit der Option „-v“) in der Lage ist, eine bösartige DLL namens KERNEL32.DLL.ASLR.0360b7ab zu identifizieren, die sich vor allen drei PEB-Listen (In- Load, InInit und InMem) versteckt, wie die entsprechenden Boole-Ausgaben des ersten Befehls verraten.

Programmcode-Extraktion
Hat man erst einmal verdächtige Prozesse und DLLs identifiziert, besteht ein wichtiger Schritt der Analyse darin, diese Binärdaten aus dem Hauptspeicherabbild zu extrahieren, um sie anschließend genauer untersuchen zu können (z. B. in einer Sandbox oder Laborumgebung – bspw. zum Extrahieren von Zeichenketten, Ausführen von Yara-Regeln, Disassemblierung oder Scannen/Prüfen mit Anti-Viren-Software).
Dass sich Strukturen in Hauptspeicherabbildern häufig während der Erhebung des Abbilds verändern, ist dabei ein nicht zu unterschätzendes Problem. Darüber hinaus veröffentlicht Windows- Update fortlaufend neue Kernelversionen, die dazu führen können, dass Binärdaten nicht korrekt extrahierbar sind.
Dumping ausführbarer Dateien und DLLs mit procdump/dlldump
Um eine ausführbare Prozessdatei aus dem Hauptspeicher auf die Festplatte zu übertragen (dumpen), kann man das procdump-Plugin verwenden – dazu muss entweder die zugehörige Prozess-ID oder ihr Offset im Speicher bekannt sein. Abbildung 6 zeigt, wie man aus einem Stuxnet-infizierten Speicherabbild per procdump die infizierte, ausführbare Prozess-Datei lsass.exe (PID 1928) extrahiert. Nach der Option „-D“ (oder „–dump-dir“) steht der Name des Verzeichnisses, in den der Dump erfolgen soll. Der Standard-Dateiname der Ausgabe lautet – basierend auf der PID des gedumpten Prozesses – executable.PID.exe.

Um einen Prozess mithilfe seines physischen Offsets zu dumpen, dient die Option „-o“ (oder „–offset“). Das ist etwa nützlich, wenn man einen versteckten Prozess aus dem Speicher extrahieren möchte. Das Beispiel der Abbildung 7 zeigt, wie man zuvor den Offset mithilfe des psxview-Plugins ermittelt – man kann ihn aber auch aus der Ausgabe von psscan erhalten (vgl. Teil 1).

Gibt man bei der Verwendung des procdump-Plugins weder die Optionen „-p“ (bzw. „–PID“) oder „-o“ (bzw. „–offset“) an, werden die ausführbaren Dateien aller aktiven Prozesse ausgegeben, die auf dem untersuchten System liefen. Dies ist je nach Situation hilfreich, wenn man möglichst alle einfach extrahierbaren Programme schnell weiteren Analyseschritten (z. B. einem Malware-Scan) zuführen möchte.
Ganz ähnlich lässt sich per dlldump-Plugin auch eine (ggf. bösartige) DLL auf einen Datenträger extrahieren. Dazu muss man Prozess-ID (Angabe über Option „-p“) des Prozesses, der diese DLL geladen hat, sowie die Basisadresse der DLL mit der Option „-b“ (bzw. „–base“) angeben. Die Basisadresse einer DLL kann man den Ausgaben der Plugins dlllist oder ldrmodules entnehmen. Abbildung 8 illustriert dies erneut anhand eines mit Stuxnet infizierten Speicherabbilds, bei dem die bösartige DLL vom Prozess lsass.exe (PID 1928) geladen wurde.

Wege ins Netz
Sind erst einmal verdächtige Prozesse direkt oder indirekt über Artefakte identifiziert, stellt sich die Frage nach den (identifizierbaren) Kommunikationsmöglichkeiten der Schadsoftware und ihrer Prozesse. Denn heutzutage gilt der Grundsatz: Es kommt über das Netz, es geht über das Netz – und es nimmt etwas mit!
Die meisten schädlichen Programme kommunizieren mit der Außenwelt, um entweder zusätzliche Komponenten nachzuladen, Befehle vom Angreifer zu empfangen, Daten zu exfiltrieren oder eine Hintertür (Remotezugang) auf dem System bereitzustellen. Die Überprüfung der Netzwerkaktivitäten hilft dem Analysten, Datenverbindungen einer Malware auf dem infizierten System zu bestimmen. In vielen Fällen ist es sinnvoll, auf dem infizierten System laufende Prozesse den im Netzwerk erkannten Aktivitäten zuzuordnen.
Auflistung auf Legacy-Systemen
Ein Plugin zur Anzeige von Netzwerkverbindungen auf Pre-Vista-Systemen ist connscan: Es verwendet dazu den bereits in Teil 1 vorgestellten sogenannten Pool-Tag-Scan-Ansatz und kann so auch bereits terminierte Verbindungen erkennen. Im Beispiel von Abbildung 9 eines mit dem R2D2-Rootkit infizierten Speicherabbilds liefert beispielsweise das connections-Plugin keine Ergebnisse, während das connscan-Plugin eine Netzwerkverbindung anzeigt. Das bedeutet allerdings nicht unbedingt, dass diese Verbindung versteckt wurde, sondern legt zunächst nur nahe, dass sie beim Erfassen des Speicherabbildes nicht aktiv (oder beendet) war. Der im Beispiel darüber identifizierte Prozess (PID 1956/explorer.exe) ist für Netzwerkkommunikation unüblich und daher auffällig.
Möchte man Informationen über offene Sockets und die damit verbundenen Prozesse für die Analyse nutzen, kann man diese auf Pre-Vista-Systemen inklusive der offenen Ports mithilfe der sockets- und sockscan-Plugins abrufen: sockets gibt eine Liste aller offenen Sockets aus. Das sockscan-Plugin verwendet wiederum Pool-Tag-Scanning, um auch geschlossene Ports erkennen zu können.

netscan listet Netzwerkverbindungen und Sockets
Einfacher geht es auf Vista und neueren Systemen (z. B. Windows 7 oder Server 2012): Dort kann man sich per netscan sowohl Netzwerkverbindungen als auch Sockets anzeigen lassen – dieses Plugin verwendet ebenfalls Pool-Tag-Scanning. Abbildung 10 zeigt einen Teil der netscan-Ausgabe eines Hauptspeicherabbilds, das durch Eternal Blue kompromittiert wurde. So werden die abgehend kontaktierten IPs erkennbar, die vom infizierten Prozess mssecsvr.exe (PID 1284) ausgingen.

Extraktion der Befehlshistorie
Nach der Kompromittierung eines IT-Systems führen Angreifer meist verschiedene Befehle auf der Kommando-Shell aus, um Benutzer, Gruppen und Freigaben im Netzwerk aufzulisten. Ein „Shoulder-Surfing“ kann hier wertvolle Hinweise über Art und Ziele des Angreifers liefern.
Durch die Analyse des Befehlsverlaufs lassen sich etwa ausgeführte Befehle, aufgerufene Programme sowie die von einem Angreifer genutzten Dateien und Ordner ermitteln. Die beiden Volatility-Plugins cmdscan (bzgl. cmd. exe) und consoles dienen dazu, die Befehlshistorie aus dem Hauptspeicherabbild zu extrahieren – Letzteres nutzt dazu die Prozesse srss.exe (vor Windows 7) beziehungsweise conhost.exe (Windows 7 und spätere Versionen).
Das cmdscan-Plugin listet die Befehle auf, die innerhalb der Eingabeaufforderung cmd.exe ausgeführt werden. Das Beispiel in Abbildung 11 gibt einen Einblick in Aktivitäten zum Diebstahl von Berechtigungen auf dem System: Aus der Ausgabe kann man ersehen, dass eine Diensteabfrage (Service) über die Befehlszeile (cmd.exe) aufgerufen wurde – den extrahierten Befehlen kann man die zugehörige Konfiguration entnehmen und den genutzten bösartigen Systemtreiber (winsys32.exe) im regulären Systemtreiberverzeichnis identifizieren.

Das cmdscan-Plugin zeigt im Beispiel die von einem R2D2 nutzenden Angreifer ausgeführten Befehle („sc query malware“). Um eine Vorstellung davon zu bekommen, ob ein solcher Befehl erfolgreich war oder nicht, kann man das console-Plugin verwenden: Seine Ausgabe (Abb. 12) zeigt sowohl die Rückmeldung auf die erste fehlerhafte Eingabe („malwar“) als auch die korrekte Ausführung („malware“).

Überprüfung der Registry
Die Registry kann aus forensischer Sicht ebenfalls wertvolle Informationen über den Kontext einer Malware liefern – so werden beispielsweise installierte Services (wie „malware“ im Beispiel der Abb. 11 und 12) dort eingetragen. Bösartige Programme können zudem der Registrierung Einträge hinzufügen, um einen Neustart zu „überleben“ (Abb. 13).

Zusätzlich zu dieser Persistenz verwendet Malware die Registry häufig, um Konfigurationsdaten, Krypto-Keys und so weiter zu speichern. Um Registrierungsschlüssel, Unterschlüssel und ihre Werte zu extrahieren, kann man das printkey-Plugin verwenden, indem der gewünschte Schlüsselpfad mit dem Argument „-K“ (bzw. „–key“) angeben wird.
Es gibt noch andere Registrierungsschlüssel, die forensisch wertvolle Informationen in binärer Form speichern. Volatility-Plugins wie userassist, shellbags und shimcache analysieren Registrierungsschlüssel, die Binärdaten enthalten, und geben Informationen in einem gut lesbaren Format aus.
Der Userassist-Registrierungsschlüssel enthält beispielsweise eine Liste der Programme, die ein Benutzer auf dem System ausgeführt hat – und die Zeit, zu der das geschah. Im Beispiel der Abbildung 14 zeigt ein Auszug des zugehörigen Volatility-Plugins, dass am 2017-12-08 um 07:53:39 Uhr eine verdächtig benannte ausführbare Datei (backup- fileserver.bat) vom Laufwerk W:\ (möglicherweise ein USB-Speicher) ausgeführt wurde – die ebenfalls dargestellten Rohdaten belegen eindrücklich, welch hilfreiche „Übersetzungsleistung“ das Plugin hier erbringt.

Auch shimcache und shellbags können bei der Untersuchung eines Malware-Vorfalls nützlich sein: Das shimcache-Plugin kann dabei helfen, die Existenz einer Malware auf dem System (und seine Laufzeit) nachzuweisen. shellbags kann indessen Informationen über den Zugriff auf Dateien, Ordner, externe Speichergeräte und Netzwerkressourcen liefern.
Fazit und Ausblick
Die Hauptspeicherforensik ist eine sehr effektive Technik, um forensische Artefakte im Speicher eines IT-Systems zu ermitteln und sie daraus zu extrahieren – denn viele Angriffe versuchen, möglichst wenig Spuren auf Festplatten zu hinterlassen. Neben dem Einsatz zur Malware-Suche kann sie auch im Rahmen der eigentlichen Malware-Analyse helfen, um zusätzliche Informationen über das Verhalten und die Eigenschaften eines (ggf. unbekannten) Schadprogramms zu finden.
Die beiden bisherigen Workshop-Teile haben die Grundlagen des Möglichen aufgezeigt und gewissermaßen an der Spitze des Schadsoftware-Eisbergs gekratzt. Im Herbst/Winter 2019 wird voraussichtlich die Volatility-Version 3.0 mit signifikanten Weiterentwicklungen erscheinen, die dann zu gegebener Zeit in einem eigenen Workshop-Beitrag mit Fokus auf Malware betrachtet werden sollen.
Jochen Schlichting (CISA, CISM, Auditteamleiter auf Basis von IT-Grundschutz) arbeitet als Consultant, Forensic Lead und Incident Responder bei der Secorvo Security Consulting in Karlsruhe (jochen.schlichting@secorvo.de – auch auf XING und LinkedIn).
Literatur
[1] Jochen Schlichting, Für alle (Vor-) Fälle, Prozesse und Open-Source-Tools für Incident-Response und Forensik, <kes> 2019#2, S. 27
[2] Jochen Schlichting, Volatility-Workshop (1), Der Weg in den Hauptspeicher und zu den Prozessen, <kes> 2019#4, S. 18