Extending The Covenant: Part 3
Der vorläufig letzte Blogpost über meine Arbeit am Open Source C2 Framework Covenant. Ein wichtiger neuer Task, zwei neue Grunt Templates und QoL...
Untersuchungen und weiterführende Gedanken zu In-Memory Execution und Detection.
Manchmal geht es als Angreifer im Cyberspace nicht darum, einen Verteidigungsmechanismus vollständig auszuhebeln, sondern nur darum, die Chance auf eine Entdeckung möglichst gering zu halten. Da die Verteidigerseite nicht schläft und fortlaufend neue Detektionen und Mechanismen entwickelt, muss auch von der Angreiferseite Research betrieben werden. Wir bei avantguard cyber security sind stets darauf bedacht, unsere Werkzeuge und Prozesse weiterzuentwickeln, um mit den Bedrohungen von heute, aber auch von morgen mithalten zu können. Eines unserer wichtigsten und liebsten Werkzeuge ist Covenant, der Open-Source-Teil unserer Command & Control (C2) Infrastruktur, welcher z.B. auch von der APT Gruppe HAFNIUM eingesetzt wird. Covenant bietet einem Operator bereits viele Möglichkeiten, Werkzeuge nur im Speicher ("In-Memory") auszuführen. Diese Methode ist in den letzten Jahren bei Angreifern beliebt worden, da dadurch nichts auf die Festplatte geschrieben werden muss und Antivirenprogramme die schädlichen Dateien gar nicht scannen können. Dies führt dazu, dass Verteidiger nun auch den Speicher von möglicherweise bösartigen Prozessen untersuchen und überwachen müssen. Immer mehr EDRs nutzen deshalb sogenannte Memory Scanner.
Dieser Blogbeitrag beschreibt unsere derzeitigen Untersuchungen zu Covenant und Memory Scannern und richtet sich insbesondere an Blue Teams und andere Red Teams. Viele Konzepte werden nicht vorgängig erläutert, da der Beitrag ansonsten einen zu grossen Umfang annehmen würde. Es wird ein mögliches Vorgehen erläutert, mit dem gewisse Funktionalitäten in Covenant von Memory Scannern weniger entdeckt werden könnten, ohne bereits einen funktionierenden PoC dazu erstellt zu haben. Fragen und Rückmeldungen sind erwünscht und können an research@avantguard.io gesendet werden.
Covenant nutzt SharpSploit, welches wiederum DInvoke nutzt. DInvoke erlaubt es Entwicklern, gewisse Funktionalitäten in C# so zu implementieren, dass sie von EDR-Agents schwieriger zu detektieren sind. Eine solche Funktionalität ist das Laden und Ausführen von Funktionen in einer DLL. Dies wird durch zwei unterschiedliche Arten ermöglicht:
Für Memory Scanner wird es somit bereits schwieriger zu bestimmen, ob eine DLL in einem Prozess legitim ist oder für bösartige Zwecke genutzt wird. Glücklicherweise gibt es aber Tools, die dies bestimmen können. Beispiele dafür, welche wir bereits genutzt haben, sind pe-sieve und Moneta. Beide sind Open Source und auf GitHub verfügbar. Als Beispiel soll ein laufender Prozess eines Implants (Covenant nennt diese Grunts) mit pe-sieve untersucht werden, bevor und nachdem eine DLL via Overload Mapping in den Prozessspeicher aufgenommen wurde.
Der obige Ausschnitt zeigt die Ergebnisse von pe-sieve für einen Prozess, welcher via Doppelklick eines ausführbaren Grunt-Implants gestartet wurde. Der Antivirus auf dem Testsystem blockiert weder die Datei noch den entstandenen Prozess. Pe-sieve meldet drei verdächtige Befunde, welche für die derzeitige Untersuchung jedoch noch nicht relevant sind. Nun wird vom C2-Server der Befehl an das Implant gesendet, Mimikatz auszuführen. Konkret bedeutet dies, dass von SharpSploit Methoden in Mimikatz.cs und Overload.cs genutzt werden, um eine DLL vom Filesystem zu laden und die referenzierte "powerkatz_x64.dll" DLL in den Speicher überschrieben wird. Dann wird der gewünschte Befehl mithilfe der DLL ausgeführt. Nach diesem Ablauf gibt ein Scan mit pe-sieve folgende Werte zurück:
Auffällig ist, dass sich der Wert von "Replaced" von null auf eins verändert hat. Dies macht durchaus Sinn, da ja eine DLL geladen und mit einer anderen DLL überschrieben wurde. Wie genau die Änderung festgestellt wird, ist uns aus ersten Nachforschungen im Quellcode noch nicht ganz klar geworden. Es sieht danach aus, als ob das Tool die exportierten Funktionen der DLL auf dem Filesystem mit denen der DLL im Speicher vergleicht (ob da noch ein anderer Weg für Verschleierungen lauert?), dies wäre jedoch mit einem ersten PoC genauer zu testen. Dies waren soweit aber mal unsere ersten Erkenntnisse.
Um jetzt zum eigentlichen Punkt dieses Beitrags zu kommen, müssen wir einen Schritt zurückgehen. Wie aus den beiden obigen Screenshots ersichtlich ist, meldet pe-sieve nebst "Replaced" auch andere verdächtige Befunde. Obwohl das Implant so wie es zurzeit ist bereits relativ unentdeckt bleiben kann, gibt es da Verbesserungspotential, welches wir ausschöpfen wollen. Ein Ansatz, um Memory Scanner zu täuschen, wurde von mgeeky als PoC auf seinem GitHub veröffentlicht als ShellcodeFluctuation. Der PoC bezieht sich zwar auf das C2 Cobalt Strike, hat uns aber auch für Covenant zum Denken angeregt. Cobalt Strike ist Closed Source, aber sehr modular und bildet einen weiteren Teil unserer C2-Infrastruktur. ShellcodeFluctuation macht grob gesagt folgendes: Während das Implant nicht genutzt wird, also keine Befehle ausgeführt werden, sondern das Implant einfach im Speicher "schläft", wird der Code des Implants verschlüsselt. Beim "Aufwachen", also wenn das Implant wieder genutzt werden soll, wird der Code entschlüsselt und kann wieder ausgeführt werden. Nach erfolgter Ausführung wird wieder verschlüsselt und geschlafen. Die Speicherregion des Codes wird zudem als nicht-ausführbar gekennzeichnet. Ein Memory Scanner, welcher während der Schlafphase den Speicher überprüft, sieht also nur unlesbaren Code in einer nicht-ausführbaren Speicherregion. Dies mag merkwürdig erscheinen, wird jedoch beispielsweise von pe-sieve nicht als verdächtig eingestuft. Da die Schlafphase zudem oftmals deutlich länger dauert als das eigentliche Ausführen von Befehlen ist deshalb das Entdeckungsrisiko deutlich reduziert, wenn auch nicht ganz behoben.
Die Lösung von ShellcodeFluctuation ist so einleuchtend und elegant, dass wir uns gefragt haben: Geht so etwas nicht auch für Overload Mapping? Und mit unserem jetzigen Wissensstand behaupten wir: Ja, auch beim Overload Mapping sollte es möglich sein, das Entdeckungsrisiko durch Veränderung des Speichers während der Laufzeit deutlich zu reduzieren. Eine mögliche Lösung könnte wie folgt aussehen:
Der Schlüssel (von nun an key genannt) ist gleich gross wie powerkatz.dll. Im Moment ist immer noch win.dll in der Speicherregion, welche überschrieben werden soll. Dank den Eigenschaften von XOR kann nun win.dll XOR key ausgeführt werden, um powerkatz.dll in die gewünschte Speicherregion schreiben zu können.
Dies wird nur dann gemacht, wenn wir eine exportierte Funktion der powerkatz.dll benötigen. Sobald die Funktion durchgeführt wurde, wird nun wieder powerkatz.dll XOR key ausgeführt. Was schlussendlich wieder in der Speicherregion steht, ist win.dll.
Ein PoC wurde wie bereits erwähnt noch nicht implementiert. Die entsprechenden Stellen im Quellcode sollten zwar bekannt sein, jedoch wurde der Entwicklungsaufwand zum Zeitpunkt dieses Blogbeitrags noch nicht geleistet. Weitere Tests wären dann nötig, um die tatsächliche Effektivität dieses Vorgehens zu evaluieren. Offene Fragen sind beispielsweise:
Die avantguard cyber security ist froh und stolz, Research und Weiterentwicklungen eine hohe Priorität beimessen zu können. Nur so können die heutigen Bedrohungen verstanden und simuliert werden. Wir möchten uns bei allen bedanken, die trotz des eher trockenen Themas bis hierher gelesen haben und werden demnächst wieder einen Beitrag veröffentlichen, der eine grössere Zielgruppe anspricht. Hier möchten wir erneut nach Fragen und Rückmeldungen an research@avantguard.io bitten, denn Research profitiert von und macht am meisten Spass mit Wissens- und Erfahrungsaustausch!
Der vorläufig letzte Blogpost über meine Arbeit am Open Source C2 Framework Covenant. Ein wichtiger neuer Task, zwei neue Grunt Templates und QoL...
Wie Handles eines Prozesses für das Finden und Entfernen von Indicators of Compromise genutzt werden können