Nicht unhöflich sein, bleiben: Fork&Run .NET-Ausführung mit InlineExecute-Assembly vermeiden

Mann schaut auf seinen Computerbildschirm, während er spät in der Nacht arbeitet und Code schreibt.

Einige von Ihnen lieben es, andere hassen es, aber an diesem Punkt sollte es nicht überraschen, dass .NET Tradecraft etwas länger bleiben wird als erwartet. Das .NET Framework ist ein integraler Bestandteil des Betriebssystems von Microsoft; die aktuellste Version von .NET ist .NET Core. Core ist der plattformübergreifende Nachfolger des .NET Frameworks, das .NET auch auf Linux und macOS verfügbar macht. Dadurch ist .NET bei Angreifern und Red Teams für Post-Ausbeutung-Techniken beliebter denn je. Dieser Blogbeitrag befasst sich mit einer neuen Beacon Object File (BOF), die es Anwendern ermöglicht, .NET-Assemblys im Prozess über Cobalt Strike auszuführen, anstatt das traditionelle, integrierte Modul zum Ausführen von Assemblys zu verwenden, das die Fork-and-Run-Technik nutzt.

Hintergrund

Cobalt Strike, eine beliebte Software zur Simulation von Angriffen, erkannte den Trend, dass Red Teams aufgrund der zunehmenden Erkennungsfähigkeit von PowerShell von PowerShell-Tools zu C# übergingen, und führte 2018 mit Cobalt Strike Version 3.11 das Modul „execute-assembly“ ein. Dadurch konnten die Betreiber die Leistungsfähigkeit von .NET-Assemblys nach der Ausbeutung nutzen, indem sie diese im Speicher ausführten, ohne das zusätzliche Risiko einzugehen, diese Tools auf die Festplatte zu übertragen. Obwohl die Fähigkeit, .NET-Assemblys über nicht verwalteten Code in den Speicher zu laden, zum Zeitpunkt der Veröffentlichung weder neu noch unbekannt war, würde ich sagen, dass Cobalt Strike diese Funktion dem breiten Publikum zugänglich gemacht und dazu beigetragen hat, die Beliebtheit von .NET für die Ausbeutung nach der Ausbeutung weiter zu steigern.

Das Execute-Assembly-Modul von Cobalt Strike verwendet die Fork-and-Run-Technik, bei der ein neuer Opferprozess gestartet wird, Ihr bösartiger Code nach der Ausbeutung in diesen neuen Prozess injiziert wird, Ihr bösartiger Code ausgeführt wird und der neue Prozess nach Abschluss beendet wird. Das hat sowohl seine Vorteile als auch seine Nachteile. Der Vorteil der Fork-and-Run-Methode besteht darin, dass die Ausführung außerhalb unseres Beacon-Implantatprozesses erfolgt. Das bedeutet, dass, wenn etwas in unserer Maßnahme nach der Ausbeutung schief geht oder entdeckt wird, die Wahrscheinlichkeit, dass unser Implantat überlebt, viel größer ist. Vereinfacht gesagt, trägt es wesentlich zur allgemeinen Stabilität des Implantats bei. Da Sicherheitsanbieter dieses Fork-and-Run-Verhalten jedoch erkannt haben, ist es nun zu einem – wie Cobalt Strike selbst zugibt – kostspieligen OPSEC-Muster geworden.

Mit der im Juni 2020 veröffentlichten Version 4.1 führte Cobalt Strike eine neue Funktion ein, um dieses Problem mit der Einführung von Beacon Object Files (BOFs) anzugehen. BOFs ermöglichen es Operatoren, die bekannten Ausführungsmuster wie oben beschrieben oder andere OPSEC-Fehler wie die Verwendung von cmd.exe/powershell.exe zu vermeiden, indem Objektdateien im Speicher innerhalb desselben Prozesses wie unser Beacon-Implantat ausgeführt werden. Ich möchte zwar nicht auf das Innenleben von BOFs eingehen, aber hier sind ein paar Blogbeiträge, die ich interessant fand:

Wenn Sie die oben genannten Blogs gelesen haben, sollten wir nun erkennen, dass BOFs nicht gerade die erhoffte Rettung waren, und wenn Sie davon geträumt haben, all diese großartigen .NET-Tools neu zu schreiben und in BOFs umzuwandeln, sind diese Träume nun zerplatzt. Entschuldigung! Die Hoffnung ist jedoch nicht verloren, denn meiner Meinung nach gibt es einige großartige Elemente, die BOFs bieten können, und ich hatte kürzlich viel Spaß (und auch etwas Frustration) dabei, die Grenzen dessen auszuloten, was man damit machen kann. Zunächst wurde CredBandit entwickelt, das einen vollständigen Speicherauszug eines Prozesses wie LSASS erstellt und diesen über den bestehenden Beacon-Kommunikationskanal zurücksendet. Heute veröffentliche ich InlineExecute-Assembly, mit dem Sie .NET-Assemblys innerhalb Ihres Beacon-Prozesses ausführen können, ohne Ihre bevorzugten .NET-Tools ändern zu müssen. Lassen Sie uns einen Blick darauf werfen, warum ich das BOF geschrieben habe, welche Hauptmerkmale es bietet, welche Einschränkungen es gibt und wie es bei der Durchführung von Adversary-Simulationen/Red Teams nützlich sein kann.

Die neuesten Tech-News – von Experten bestätigt

Bleiben Sie mit dem Think-Newsletter über die wichtigsten – und faszinierendsten – Branchentrends in den Bereichen KI, Automatisierung, Daten und mehr auf dem Laufenden. Weitere Informationen finden Sie in der IBM Datenschutzerklärung.

Vielen Dank! Sie haben sich angemeldet.

Ihr Abonnement wird auf Englisch geliefert. In jedem Newsletter finden Sie einen Abmeldelink. Hier können Sie Ihre Abonnements verwalten oder sich abmelden. Weitere Informationen finden Sie in unserer IBM Datenschutzerklärung.

Warum InlineExecute-Assembly?

Der Grund für die Entwicklung von InlineExecute-Assembly ist recht einfach. Ich wollte eine Möglichkeit für unser Team zur Simulation von Gegnern schaffen, .NET-Assemblys im Prozess auszuführen, um einige der oben genannten OPSEC-Fallstricke zu vermeiden, die beim Einsatz von Cobalt Strike in ausgereiften Umgebungen auftreten. Ich benötigte das Tool auch, um unser Team nicht mit zusätzlicher Entwicklungszeit zu belasten, da wir keine Änderungen an den meisten unserer aktuellen .NET-Tools vornehmen müssten. Es musste außerdem stabil sein. So stabil ein komplexer BOF auch sein kann, denn das Letzte, was wir wollen, ist, einen unserer wenigen Beacons an die Umgebung zu verlieren. Grundsätzlich sollte es für den Bediener genauso nahtlos funktionieren wie das Execute-Assembly-Modul von Cobalt Strike.

Mixture of Experts | 12. Dezember, Folge 85

KI entschlüsseln: Wöchentlicher Nachrichtenüberblick

Schließen Sie sich unserer erstklassigen Expertenrunde aus Ingenieuren, Forschern, Produktführern und anderen an, die sich durch das KI-Rauschen kämpfen, um Ihnen die neuesten KI-Nachrichten und Erkenntnisse zu liefern.

Hauptmerkmale

Laden der Common Language Laufzeit (CLR)

Ich weiß, das ist ziemlich offensichtlich. Ohne sie kämen wir nicht weit, oder? Spaß beiseite, die Feinheiten der Funktionsweise der CLR und ihrer internen Abläufe könnten einen eigenen Blogbeitrag füllen. Wir werden daher nur ganz allgemein darauf eingehen, was die BOF beim Laden der CLR über nicht verwalteten Code verwendet.

Screenshot: CLR wird geladen

Laden der CLR

Wie in dem vereinfachten Screenshot oben dargestellt, umfasst der BOF zum Laden der CLR im Wesentlichen Folgendes:

  1. Führt einen Aufruf an CLRCreateInstance durch, mit dem unsere ICLRMetaHost-Schnittstelle abgerufen wird.
  2. Anschließend wird ICLRMetaHost ->GetRuntime verwendet, um die Laufzeitinformationen für die angeforderte .NET-Version zu erhalten. Wenn Ihre Assembly mit .NET Version 3.5 oder niedriger erstellt wurde, benötigen wir Version 2.0.50727, und wenn Ihre Assembly mit .NET 4.0 oder höher erstellt wurde, benötigen wir Version 4.0.30319. Es gibt tatsächlich eine Funktion im BOF, die uns dabei hilft, automatisch herauszufinden, welche Version unsere .NET-Assembly verwendet, aber darauf werden wir später eingehen.
  3. Sobald wir unsere Laufzeitinformationen haben, verwenden wir ICLRRuntimeInfo->IsLoadable, um zu prüfen, ob unsere Laufzeit in den Prozess geladen werden kann. Dabei wird auch berücksichtigt, ob bereits andere Laufzeiten geladen sind, und unser BOOL-Wert fLoadable wird auf 1 (true) gesetzt, wenn unsere Laufzeit im Prozess geladen werden kann.
  4. Wenn das alles passt, führen wir dann ICLRRuntimeInfo->GetInterface aus, um die CLR in unseren Prozess zu laden und eine Schnittstelle zu ICorRunTimeHost abzurufen.
  5. Zum Schluss rufen wir ICorRuntimeHost->Start auf, wodurch die CLR gestartet wird.

Die CLR ist nun also initialisiert, aber es muss noch ein wenig mehr passieren, bevor wir unsere bevorzugten .NET-Assemblys tatsächlich ausführen können. Wir müssen unsere AppDomain-Instanz erstellen, die Microsoft als „eine isolierte Umgebung, in der Anwendungen ausgeführt werden“ beschreibt. Mit anderen Worten: Dies wird zum Laden und Ausführen unserer .NET-Assemblys nach der Ausbeutung verwendet.

Screenshot: AppDomain wird erstellt und Assembly wird geladen/ausgeführt

AppDomain wird erstellt und Assembly wird geladen/ausgeführt

Wie im obigen vereinfachten Screenshot dargestellt, sind die wichtigsten Schritte, die der BOF zum Laden und Aufrufen unserer .NET-Assembly ausführt, folgende:

  1. Verwenden Sie ICorRuntimeHost->CreateDomain, um unsere eindeutige AppDomain zu erstellen.
  2. Verwenden Sie IUnknown->QueryInterface (pAppDomainThunk), um einen Zeiger zur AppDomain-Schnittstelle zu erhalten
  3. Erstellen Sie unser SafeArray und kopieren Sie unsere .NET-Assembly-Bytes hinein
  4. Laden Sie unsere Assembly über AppDomain->Load_3
  5. Holen Sie sich unseren Einstiegspunkt in unserer Assembly über Assembly->EntryPoint
  6. Rufen Sie zuletzt unsere Assembly über MethodInfo-> Invoke_3 auf

Hoffentlich haben Sie nun ein grundlegendes Verständnis der .NET-Ausführung über nicht verwalteten Code, aber damit sind wir noch weit von einem funktionsfähigen Tool entfernt. Daher werden wir uns einige Funktionen ansehen, die im BOF implementiert wurden, um es von „meh“ zu „totes legit“ zu machen.

Umleitung der Konsolenausgabe STDOUT zu einer Named Pipe oder einem Mail Slot: Vermeidung von Tooländerungen

Sie fragen sich wahrscheinlich, warum das wichtig ist. Wenn Sie wie ich sind und Ihre Zeit schätzen, möchten Sie diese sicher nicht damit verbringen, so gut wie jede .NET-Assembly so zu ändern, dass ihr Einstiegspunkt eine Zeichenfolge mit all Ihren Daten zurückgibt, die normalerweise einfach an die Standardausgabe der Konsole weitergeleitet würden, oder? Das habe ich mir gedacht. Um dies zu vermeiden, müssen wir unsere Standardausgabe entweder an eine Named Pipe oder einen Mail Slot umleiten, die Ausgabe lesen, nachdem sie geschrieben wurde, und sie dann wieder in ihren ursprünglichen Zustand zurückversetzen. Auf diese Weise können wir unsere unveränderten Assemblys genauso ausführen wie über cmd.exe oder powershell.exe. Bevor wir uns nun mit dem Code befassen, möchte ich mich bei @N4k3dTurtl3 und ihrem Blogbeitrag über die Ausführung von Assemblys und Mail Slots bedanken. Das war ursprünglich der Grund, warum ich diese Technik in mein eigenes privates C-Implantat implementiert habe, als sie zum ersten Mal auf den Markt kam, und viele Monate später habe ich dieselbe Funktionalität auf ein BOF portiert. Nachdem wir nun die Grundlagen behandelt haben, sehen wir uns ein vereinfachtes Beispiel dafür an, wie dies durch Umleiten von stdout zu einer benannten Pipe erreicht werden kann:

Screenshot: Umleitung der Konsolenausgabe in eine Named Pipe und anschließendes Zurücksetzen

Umleitung der Standardausgabe der Konsole auf benannte Leitung und anschließendes Zurücksetzen

Bestimmen Sie die .NET-Version der Assembly

Erinnern Sie sich, dass wir beim Laden der CLR über ICLRMetaHost ->GetRuntime angeben mussten, welche Version des .NET-Frameworks wir benötigen? Denken Sie daran, dass dies davon abhängt, mit welcher Version unsere .NET-Assembly kompiliert wurde? Es wäre doch ziemlich mühsam, jedes Mal manuell angeben zu müssen, welche Version benötigt wird, oder? Zu unserem Glück hat @b4rtik eine coole Funktion implementiert, um dies in ihrem execute-assembly-Modul für das Metasploit Framework zu handhaben, die wir ganz einfach in unser eigenes Tooling implementieren können, wie unten gezeigt:

Screenshot: Funktion, die unsere .NET-Assembly liest und dabei hilft, die benötigte .NET-Version beim Laden der CLR zu bestimmen.

Eine Funktion, die unsere .NET-Assembly liest und dabei hilft, die benötigte .NET-Version beim Laden der CLR zu bestimmen.

Im Wesentlichen liest diese Funktion, wenn ihr unsere Assembler-Bytes übergeben werden, diese Bytes durch und sucht nach den Hexadezimalwerten 76 34 2E 30 2E 33 30 33 31 39, die in ASCII umgewandelt v4.0.30319 ergeben. Hoffentlich kommt Ihnen das bekannt vor. Wird dieser Wert beim Lesen des Assembler-Codes gefunden, gibt die Funktion 1 oder True zurück, andernfalls 0 oder False. Damit können wir ganz einfach feststellen, welche Version geladen werden soll und ob 1/True oder 0/False zurückkommt, wie im folgenden Codebeispiel gezeigt:

Screenshot: If/Else-Anweisung zum Festlegen einer .NET-Versionsvariablen

If/Else-Anweisung zum Festlegen der .NET-Versionsvariablen

Patchen der Antimalware Scan Interface (AMSI)

Wir könnten unmöglich über offensive .NET-Strategien sprechen, ohne AMSI zu erwähnen. Wir werden nicht näher darauf eingehen, was AMSI ist und wie es umgangen werden kann, da dies bereits mehrfach behandelt wurde. Wir werden jedoch kurz darauf eingehen, warum das Patchen von AMSI je nach dem, was Sie über BOF ausführen möchten, erforderlich sein kann. Wenn Sie beispielsweise beschließen, Seatbelt ohne jegliche Verschleierung auszuführen, werden Sie schnell feststellen, dass Sie keine Ausgabe erhalten haben und Ihr Beacon tot ist. Ja, KOMPLETT TOT. Dies liegt daran, dass AMSI Ihre Assembly abgefangen, sie als bösartig eingestuft und sie daraufhin abgeschaltet hat, wie eine Hausparty, die zu viel Lärm macht. Nicht ideal, oder? Nun haben wir zwei gute Optionen, wenn es um AMSI geht: Wir können entweder unsere .NET-Tools über etwas wie ConfuserX oder Invisibility Cloak verschleiern oder AMSI mithilfe verschiedener Techniken deaktivieren. In unserem Fall verwenden wir einen von RastaMouse, der die Datei „amsi.dll” im Speicher so patcht, dass sie „E_INVALIDARG” zurückgibt und das Scan-Ergebnis 0 ergibt. Wie in ihrem Blogbeitrag erwähnt, wird dies normalerweise als AMSI_RESULT_CLEAN interpretiert. Sehen wir uns unten eine vereinfachte Version des Codes für einen x64-Prozess an:

Screenshot: In-Memory-Patching von AmsiScanBuffer

In-Memory-Patching von AmsiScanBuffer

Wie Sie dem obigen Screenshot entnehmen können, gehen wir einfach wie folgt vor:

  1. Laden Sie amsi.dll und erhalten Sie einen Zeiger auf AmsiScanBuffer.
  2. Ändern Sie den Speicherschutz
  3. Patch in unseren amsiPatch[] Bytes
  4. Den Speicherschutz auf seinen ursprünglichen Zustand zurücksetzen

Durch die Implementierung dieser Funktion in unser Tool sollten wir nun in der Lage sein, die Standardversion von Seatbelt.exe mit dem Parameter –amsi auszuführen, um die AMSI-Erkennung zu umgehen, wie unten gezeigt:

Screenshot: InlineExecute-Assemby AMSI-Bypass-Beispiel

Beispiel für InlineExecute-Assemby-AMSI-Umgehung

Patching von Event Tracing für Windows (ETW)

Zum Glück für Verteidiger gibt es mehr als nur AMSI, um mithilfe von ETW bösartige .NET-Techniken zu erkennen. Leider lässt sich auch dies, ähnlich wie AMSI, von Angreifern relativ leicht umgehen, und @xpn hat einige wirklich beeindruckende Untersuchungen dazu durchgeführt, wie dies geschehen könnte. Im Folgenden finden Sie ein vereinfachtes Beispiel, wie Sie ETW patchen könnten, um es vollständig zu deaktivieren:

Screenshot: In-Memory-Patching von EtwEventWrite

In-Memory-Patching von EtwEventWrite

Wie Sie auf dem Screenshot oben sehen können, sind die Schritte fast identisch mit denen, die wir bei AMSI angewendet haben, daher werde ich sie hier nicht noch einmal wiederholen. Unten sehen Sie einen Vorher-Nachher-Screenshot der Ausführung des Flags –etw:

Screenshot: Verwendung von Process Hacker zum Anzeigen der Eigenschaften von PowerShell.exe vor dem Ausführen von inlineExecute-Assembly mit dem Flag –etw

Verwendung von Process Hacker zum Anzeigen der Eigenschaften von PowerShell.exe vor dem Ausführen von inlineExecute-Assembly mit dem Flag –etw

Screenshot: Ausführen von inline-Execute-Assembly mit dem Flag –etw

Ausführen von inline-Execute-Assembly mit dem Flag –etw

Screenshot: Verwendung von Process Hacker zum Anzeigen derselben PowerShell.exe-Eigenschaften nach dem Ausführen von inlineExecute-Assembly

Verwendung von Process Hacker zum Anzeigen derselben PowerShell.exe-Eigenschaften nach dem Ausführen von inlineExecute-Assembly

Einzigartige AppDomains, Named Pipes, Mail Slots

Standardmäßig verwendet der erstellte AppDomain-, Named Pipe- oder Mail-Slot den Standardwert „TotesLegit“. Diese Werte können angepasst werden, um sich besser in die Umgebung einzufügen, die Sie testen. Dies kann entweder durch Änderung im bereitgestellten Aggressor-Skript oder über Befehlszeilenflags im laufenden Betrieb erfolgen. Ein Beispiel für die Änderung über die Kommandozeile ist unten aufgeführt:

Terminal-Screenshot, der die Ausführung des Befehls inlineExecute-Assembly --dotnetassembly /root/Desktop/MessageBoxCS.exe in Beacon zeigt. Die Ausgabe umfasst Statusmeldungen: Ausführen von inlineExecute-Assembly, Host namens „home“ (16319 Bytes gesendet), empfangene Ausgabe „Hello From .NET!“ und Abschlussmeldung „inlineExecute-Assembly Finished“.

Beispiel für InlineExecute-Assembly unter Verwendung eines eindeutigen AppDomain-Namens und eines eindeutigen Named-Pipe-Namens

Screenshot: Beispiel für den eindeutigen AppDomain-Namen ChangedMe

Beispiel für den eindeutigen AppDomain-Namen ChangedMe

Screenshot: Beispiel für eine eindeutige Named Pipe LookAtMe

Beispiel für eine eindeutige Named Pipe LookAtMe

Screenshot: Beispiel für das Entfernen einer AppDomain nach erfolgreicher Ausführung

Beispiel für das Entfernen von AppDomain nach erfolgreicher Ausführung

Screenshot: Beispiel für das Entfernen einer Named Pipe nach erfolgreicher Ausführung

Beispiel für das Entfernen einer Named Pipe nach erfolgreicher Ausführung

Vorbehalte

Dieser Abschnitt wiederholt im Wesentlichen das, was ich bereits im GitHub-Repository erwähnt habe, aber ich hielt es für wichtig, einige Punkte zu wiederholen, die Sie bei der Verwendung dieses Tools beachten sollten:

  1. Ich habe versucht, das Ganze so stabil wie möglich zu machen, aber es gibt keine Garantie dafür, dass es nie zu Abstürzen kommt und die Beacons nicht ausfallen. Wir haben nicht den zusätzlichen Luxus von „Fork and Run“, bei dem unser Beacon weiterlebt, wenn etwas schiefgeht. Das ist der Nachteil von BOFs. Vor diesem Hintergrund kann ich gar nicht genug betonen, wie wichtig es ist, dass Sie Ihre Assemblys vorab testen, um sicherzustellen, dass sie ordnungsgemäß funktionieren.
  2. Da BOF im laufenden Prozess ausgeführt wird und während der Ausführung Ihren Beacon übernimmt, sollte dies vor der Verwendung für langlaufende Assemblys berücksichtigt werden. Wenn Sie sich dafür entscheiden, etwas auszuführen, dessen Ergebnisse erst nach langer Zeit vorliegen, kann Ihr Beacon keine weiteren Befehle ausführen, bis die Ergebnisse vorliegen und Ihre Assembly vollständig ausgeführt wurde. Dies entspricht auch nicht dem Schlafrhythmus. Wenn Sie beispielsweise Ihre Wartezeit auf 10 Minuten einstellen und den BOF ausführen, erhalten Sie die Ergebnisse zurück, sobald die Ausführung des BOF abgeschlossen ist.
  3. Sofern keine Änderungen an Tools vorgenommen werden, die PE-Dateien in den Speicher laden (z. B. SafetyKatz), werden diese höchstwahrscheinlich Ihren Beacon zerstören. Viele dieser Tools funktionieren gut mit Execute Assembly, da sie ihre Konsolenausgabe vor dem Beenden aus dem Opferprozess senden können. Wenn sie über unseren In-Process-BOF aussteigen, beenden sie unseren Prozess, wodurch unser Beacon beendet wird. Diese können so geändert werden, dass sie funktionieren, aber ich würde empfehlen, diese Art von Assemblys über „Assembly ausführen“ zu starten, da sonst andere, nicht OPSEC-freundliche Elemente in Ihren Prozess geladen werden könnten, die nicht entfernt werden.
  4. Wenn Ihre Assembly Environment.Exit verwendet, muss dies entfernt werden, da dadurch der Prozess und der Beacon beendet werden.
  5. Named Pipes und Mail Slots müssen eindeutig sein. Wenn Sie keine Daten zurückerhalten und Ihr Beacon noch aktiv ist, liegt das Problem höchstwahrscheinlich darin, dass Sie einen anderen Named Pipe oder Mail Slot-Namen auswählen müssen.

Überlegungen zur Verteidigung

Nachfolgend einige Überlegungen zur Verteidigung:

  1. Hierbei wird PAGE_EXECUTE_READWRITE beim Patchen von AMSI- und ETW-Speicher verwendet. Diese Maßnahme wurde bewusst gewählt und sollte als Warnsignal verstanden werden, da nur sehr wenige Programme über Speicherbereiche mit dem Speicherschutz PAGE_EXECUTE_READWRITE verfügen.
  2. Der Standardname der erstellten Named Pipe ist „totesLegit“. Diese Maßnahme wurde bewusst gewählt und Signaturen-Erkennungen könnten verwendet werden, um das zu kennzeichnen.
  3. Der Standardname des erstellten Mail Slots lautet „totesLegit“. Diese Maßnahme wurde bewusst gewählt und Signaturen-Erkennungen könnten verwendet werden, um das zu kennzeichnen.
  4. Der Standardname der geladenen AppDomain lautet „totesLegit“. Das wurde mit Absicht gemacht und Signaturen-Erkennungen könnten verwendet werden, um das zu kennzeichnen.
  5. Gute Tipps zum Erkennen von böswilliger Nutzung von .NET (von @bohops) hier, (von F-Secure) hier und hier
  6. Suche nach .NET CLR-Ladevorgängen in verdächtigen Prozessen, wie z. B. nicht verwalteten Prozessen, in denen CLR niemals geladen werden sollte.
  7. Mehr zum Event Tracing.
  8. Suche nach weiteren bekannten IOCs für Cobalt Strike Beacons oder C2-Ausgangs-/Kommunikations-IOCs.