
Python api zur Verwendung mit der External C2-Spezifikation von cobalt strike
Python-Framework zum Erstellen und Verwenden von Schnittstellen zur Datenübertragung zwischen Frameworks, mit besonderem Fokus auf die Erweiterung von Command-and-Control-Frameworks.
Aktuell ist dies nur als Implementierung der External-C2-Spezifikation von Cobalt Strike gedacht, wie in diesem Spezifikationsdokument beschrieben, unterliegt aber Änderungen, wenn das Projekt reifer wird.
Ein großer Dank geht an xychix. Ohne die wertvollen Beiträge von xychix wäre dieses Projekt überhaupt nicht möglich gewesen. Im Grunde ist dieses Projekt ein Neuaufbau und eine Erweiterung von Outflank's External C2 project
Dieses Projekt besteht aus den folgenden Hauptteilen:
Der Builder liest eine Konfigurationsdatei ein und verwendet die konfigurierten Optionen, um einen Build zu erzeugen, indem er markers innerhalb von skeletons ersetzt.
Der Builder kann mit build_files.py verwendet werden. Eine Beispiel-Builder-Konfiguration wird als sample_builder_config.config.sample bereitgestellt.
skeletons sind die verschiedenen „Skelette“ aus Code, die der builder dynamisch befüllt, um einen vollständig nutzbaren Build zu erzeugen. skeletons enthalten markers, die vom builder durch nutzbare Werte ersetzt werden. Es gibt drei verschiedene „Typen“ von Skeletten:
Ein Marker kann in jeder Datei eines skeleton platziert werden und wird durch einen in der Builder-Konfiguration angegebenen Wert ersetzt. In der Praxis sollten Marker niemals verwendet werden, um Variablen direkt zu schreiben, sondern nur, um Werte zu setzen. Wenn der Wert eines Markers wiederverwendet werden muss, sollte man den Wert in einer Variablen speichern und auf diese Weise darauf referenzieren, anstatt denselben Marker erneut zu verwenden.
Das Format eines Markers lautet: ```[var:::identifier_for_the_marker]```
Strings werden direkt in ein Skeleton geschrieben, umgeben von einfachen Anführungszeichen, und Zahlen werden unverändert geschrieben.
Falls ein String in der Konfiguration in doppelte Anführungszeichen gesetzt ist, wird der String stattdessen direkt in doppelte Anführungszeichen geschrieben, und die umgebenden einfachen Anführungszeichen werden entfernt.
Dieser Zusammenhang lässt sich wie folgt veranschaulichen:
#################
# Skeleton Code #
#################
# Skeleton contains the following line of code:
foo = ```[var:::bar]```
##############
# End Result #
##############
# Stored in config as:
# foo = bar
# Written as:
foo = 'bar'
# Stored in config as:
# foo = "bar"
# Written as:
foo = "bar"
# Stored in config as:
# foo = 2
# Written as:
foo = 2
# Stored in config as:
# foo = "2"
# Written as:
foo = "2"
Frameworks sind die Basis-Anwendung, die bestimmt, welche Daten vom transport und encoder verwendet werden und wie diese Daten verwendet werden. Was ein bestimmtes framework tatsächlich tut, spielt keine große Rolle, solange eine Logik existiert, um den encoder und den transport zu importieren und zu verwenden. Die meisten wesentlichen Teile eines Frameworks (hauptsächlich die client-Logik) werden als ein skeleton gespeichert, wobei eine Schnittstelle zur Interaktion mit dem Server-Teil als Basis-framework-Objekt gespeichert wird.
Im Allgemeinen enthält ein Framework einen server und einen client und nutzt encoders und transports, um Daten zwischen ihnen weiterzuleiten.
Es gibt einige Grundprinzipien, die beim Erstellen eines framework zu beachten sind:
framework ist dafür verantwortlich, dass der encoder dem transport zur Verwendung zur Verfügung gestellt wird.framework eine Client-Server-Beziehung verwendet, sollte es entsprechend organisiert sein.client eines Frameworks interagiert. Wenn du also Dinge auf dem client neu konfigurierbar haben möchtest, muss dieser in der Lage sein, dies zur Laufzeit ohne direkte Interaktion zu tun.server-skeleton zu erstellen, da der Endnutzer direkt mit dem server eines Frameworks interagieren wird. Stattdessen sollte man sowohl Optionen aus einer Konfiguration einlesen als auch dem Endnutzer die Möglichkeit geben, Optionen (wie einen Block-Timer oder die Ausführlichkeit) zur Laufzeit zu ändern.framework-skeleton wird vom Builder verarbeitet, der jede Datei darin durchläuft. Wenn also ein bestimmtes Argument zur Build-Zeit konfigurierbar sein muss, kann das leicht umgesetzt werden.framework-server sollte über einen gemeinsamen framework_manager angesprochen werden können.Der Server ist die Anwendung, die die Kommunikation zwischen dem client und dem c2 server vermittelt. Die Serverlogik ist in erster Linie statisch. Die Logik für den Server des cobalt_strike-Frameworks, die in der Spezifikation als third-party Client Controller bezeichnet wird, ist unten dargestellt:
encoder-Modul kodierentransport-Modul übertragentransport empfangen wirdencoder-Modul dekodierentransport an den Client weiterleitentransport empfangen wirdencoder-Modul dekodierenEin server sollte die Fähigkeit unterstützen, mehrere Clients zu verwalten (noch nicht implementiert), und über einen framework_manager angesprochen werden können.
Der Client ist im Wesentlichen die Payload, die auf dem Endpunkt läuft. Die Logik des Clients für das cobalt_strike-Framework ist in erster Linie statisch und unten dargestellt:
transport erforderlich sindtransport vom Beacon zum C2-Server weiterleitentransport auf neue Aufgaben beobachtentransport weiterleitenDer Client verwendet den angegebenen encoder und transport, um Daten zwischen sich und dem jeweiligen server weiterzuleiten.
Encoder empfangen Daten und verändern diese dann entweder, um sie für die Übertragung über den Transport vorzubereiten, oder dekodieren über den Transport empfangene Daten zurück in ihre Rohform, damit sie von der jeweiligen framework-Komponente interpretiert werden können, die sie verwendet.
Encoder sollten direkt vom Transport angesprochen werden und Daten auf eine Weise verarbeiten, die unabhängig von Framework und Komponente ist.
Transports übernehmen das Senden und Empfangen von Daten über einen Kommunikationskanal und arbeiten mit dem Encoder zusammen, um sicherzustellen, dass Daten in das jeweils erforderliche Format umgewandelt werden. Transports sollten Daten von einer Framework-Komponente oder über den Kommunikationskanal empfangen und in der Lage sein, Daten über den Kommunikationskanal weiterzuleiten. Transports sind dafür verantwortlich, den encoder aufzurufen, um Daten bei Bedarf zu kodieren oder zu dekodieren.
Transports sollten direkt von der framework-Komponente angesprochen werden und Daten auf eine Weise verarbeiten, die unabhängig von Framework und Komponente ist.
Bestimme zuerst, welches Transport- und welches Kodierungsmodul du verwenden möchtest. Wir verwenden für das folgende Beispiel transport_imgur und encoder_lsbjpg.
Erstelle als Nächstes eine builder_config.config, die deinen Anforderungen entspricht. Siehe die bereitgestellte Beispiel-Konfiguration und Vorlage für Hinweise, wie das geht.
Erzeuge einen Build mit build_files.py. Als Beispiel könnte man mit dem folgenden Befehl einen Build im Verzeichnis builds für Cobalt Strike mit encoder_lsbjpg und transport_imgur ausführlich erzeugen:
python build_files.py -b builds -f cobalt_strike -c sample_builder_config.config.sample -e encoder_lsbjpg -t transport_imgur -v
Führe auf dem Rechner, auf dem der Server läuft, Folgendes aus:
python server.py
Für eine ausführlichere Ausgabe kannst du Folgendes ausführen:
python server.py -v
Für eine noch ausführlichere Ausgabe sowie zusätzliche, für die Fehlersuche nützliche Ausgaben kannst du Folgendes ausführen:
python server.py -d
Führe anschließend den Client auf dem Ziel-Endpunkt aus.
Wenn alles funktioniert hat, wird ein neues Beacon in der Cobalt-Strike-Konsole registriert, mit dem du interagieren kannst.
Warum hast du das geschrieben?: Es gab nur sehr wenige veröffentlichte Implementierungen der Cobalt-Strike-Spezifikation, und von den veröffentlichten waren entweder keine in einer Sprache, die ich beherrsche, oder sie boten nicht die Modularität und Abstraktion, die ich gesucht habe.
Warum Python 2?: Ich bin faul, und es ist einfach, darin neue Transport- und Kodierungskanäle zu implementieren.
Dein Code ist schlecht: Das ist keine Frage.
Kann ich neue Transport- und/oder Encoder-Module einreichen?: Ja, bitte! Reiche einen Pull-Request ein, und ich schaue ihn mir gerne an.
Wie kompiliere ich den Client in eine ausführbare Datei, die ich verteilen kann?:
Ich habe das erfolgreich in Kali getestet. Um meine Umgebung nachzubilden, stelle einfach sicher, dass veil-evasion installiert ist und du dessen Setup durchlaufen hast. Es sollte eine Wine-Umgebung mit installiertem Python eingerichtet haben, die alle benötigten Abhängigkeiten enthält. Möglicherweise musst du das Modul pefile zusätzlich in dieser Umgebung installieren.
Dann kannst du in das Verzeichnis des Clients gehen, für den du eine ausführbare Datei erzeugen möchtest, und Folgendes ausführen:
chmod +x compile_dll.sh
./compile_dll.sh
wine "C:\\Python27\\python.exe" /usr/share/veil/pyinstaller/pyinstaller.py -F -r c2file.dll -w --key "ayyyyyyylmao" client.py
Ersetze den Wert für key durch das, was du möchtest. Du solltest die ausführbare Client-Datei im Verzeichnis dist/ sehen. Wenn du eine ausführbare Datei erzeugen möchtest, die eine Konsole für das Debugging bereitstellt, kompiliere die ausführbare Datei mit wine "C:\\Python27\\python.exe" /usr/share/veil/pyinstaller/pyinstaller.py -F -r c2file.dll -c client.py