Stadeo ist eine Reihe von Tools, die dazu entwickelt wurden, die Analyse von Stantinko zu erleichtern. Statinko ist ein Botnetz, das Klickbetrug, Anzeigeninjektion, Betrug in sozialen Netzwerken, Passwortdiebstahl und Kryptomining durchführt.

Stadeo wurde erstmals auf der Black Hat USA 2020 demonstriert und anschließend zur freien Verwendung veröffentlicht.

Die vollständig in Python geschriebenen Skripte, befassen sich mit Stantinkos einzigartigen Techniken des Control-Flow-Flattening (CFF) und der Obfuskation (Verschleierung) von Zeichenfolgen, die in unserem Blogpost vom März 2020 beschrieben wurden. Darüber hinaus können sie für andere Zwecke verwendet werden: Beispielsweise haben wir unseren Ansatz erweitert, damit er die Deobfuskation des in Emotet enthaltenen CFF unterstützt. Dieser bekannte Trojaner stiehlt Bank- sowie zusätzliche Nutzerdaten und lädt Ransomware herunter.

Unsere Deobfuskationsmethoden verwenden IDA, ein branchenübliches Standardwerkzeug, und Miasm - ein Open-Source-Framework, das uns verschiedene Datenflussanalysen, eine Symbolic-Execution-Engine und eine dynamische Symbolic-Execution-Engine bietet, sowie die Möglichkeit geänderter Funktionen wieder zu integrieren

Sie finden Stadeo unter https://github.com/eset/stadeo .

Anwendungsbeispiele

Um mit Stadeo arbeiten zu können, müssen wir zunächst in IDA einen RPyC-Server (Remote Python Call) einrichten, über den wir von einem beliebigen Python-Interpreter aus auf die IDA-API zugreifen können. Mit diesem Skript können Sie einen RPyC-Server in IDA öffnen.

In allen folgenden Beispielen richten wir einen RPyC-Server ein, der auf Port 10.1.40.164:4455 lauscht (4455 ist der Standardport) und kommunizieren dann über eine Python-Konsole mit dem Server.

Wir werden zwei Stadeo-Klassen verwenden:

  • CFFStrategies für CFF-Deobfuskation
  • StringRevealer für String-Deobfuskation

Beide Klassen können für 32- und 64-Bit-Architektur initialisiert werden.

Deobfuskation einer einzelnen Funktion

SHA-1 des Samples: 791ad58d9bb66ea08465aad4ea968656c81d0b8e

Der folgende Code deobfuskiert die Funktion bei 0x1800158B0, mit dem Parameter im R9-Register auf 0x567C gesetzt ist, und schreibt seine deobfuskierte Version in die 0x18008D000-Adresse.

Der Parameter R9 ist eine Steuervariable zum Zusammenführen von Funktionen der CFF-Schleife (Zusammenführungsvariable) und muss mithilfe von Miasm-Ausdrücken angegeben werden, die hier dokumentiert sind. Beachten Sie, dass RSP_init/RSP_init anstelle von RSP/ESP verwendet werden muss, um auf die Stapelparameter zu verweisen. Zum Beispiel würden wir ExprMem (ExprId ("ESP_init", 32) + ExprInt (4, 32), 32) verwenden, um den ersten Stapelparameter auf einer 32-Bit-Architektur anzuvisieren.

from stadeo.cff.cff_strategies import CFFStrategies
from miasm.expression.expression import ExprId, ExprInt
cs = CFFStrategies(64)
cs.solve_loop(0x1800158B0,
              0x18008D000,
              context={ExprId("R9", 64): ExprInt(0x567c, 64)},
              ip='10.1.40.164')

Abbildung 1. Aufruf der obfuskierten Funktion

Abbildung 2. Obfuskiertes (links) und deobfuskiertes (rechts) CFG

Verarbeitung erreichbarer Funktionen

SHA-1 der Probe: e0087a763929dee998deebbcfa707273380f05ca

Der folgende Code erkennt nur verschleierte Funktionen, die von 0x1002DC50 aus erreichbar sind, und sucht nach Kandidaten für das Zusammenführen von Variablen. Erfolgreich erkannte und deobfuskierte Funktionen beginnen bei 0x10098000.

from stadeo.cff.cff_strategies import CFFStrategies
strat = CFFStrategies(32)
res = strat.process_merging(0x1002DC50,
                            0x10098000,
                            ip="10.1.40.164")

Partial output:

skipping 0x1002dc50 with val None: 0xbadf00d
mapping: 0x1001ffc0 -> 0x10098000 with val @32[ESP_init + 0x8]: 0x6cef
skipping 0x100010f0 with val None: 0xbadf00d
mapping: 0x1000f0c0 -> 0x100982b7 with val @32[ESP_init + 0x8]: 0x2012
mapping: 0x1003f410 -> 0x10098c8a with val @32[ESP_init + 0x4]: 0x21a4
mapping: 0x1003f410 -> 0x10098f3d with val @32[ESP_init + 0x4]: 0x772a
skipping 0x1004ee79 (library func)
...

Zugeordnete Funktionen wurden ordnungsgemäß deobfuskiert und übersprungene Funktionen wurden nicht als verschleiert angesehen. Das Format des Mappings in der Ausgabe lautet:

mapping: %obfuscated_function_address% -> %deobfuscated_function_address% with val %merging_variable%: % merging_variable_value%

Der Standardwert für merging_variable_value ist 0x0BADF00D. Die überspringenden Zeilen folgen demselben Muster, es gibt jedoch keine deobfuscated_function_address. Von IDA erkannte Bibliotheksfunktionen werden einfach ohne weitere Verarbeitung übersprungen.

Verarbeitung aller Funktionen

SHA-1 der Probe: e575f01d3df0b38fc9dc7549e6e762936b9cc3c3

Wir verwenden den folgenden Code nur für CFF in Emotet, dessen CFF-Implementierung in die Beschreibung des allgemeinen Control-Flow-Flattening hier passt.

Wir bevorzugen diesen Ansatz, da die Methode CFFStrategies.process_all () nicht versucht Zusammenführungsvariablen zu erkennen, die in Emotet nicht vorhanden sind, und nur nach einer CFF-Schleife pro Funktion sucht. Daher ist sie effizienter.

Erfolgreich erkannte und deobfuskierte Funktionen werden nacheinander von 0x0040B000 geschrieben. Das Format der Ausgabe ist das gleiche wie bei der process_merging-Methode, die im Beispiel für die Verarbeitung erreichbarer Funktionen verwendet wird, aber natürlich gibt es keine Zusammenführungsvariablen.

from stadeo.cff.cff_strategies import CFFStrategies
strat = CFFStrategies(32)
res = strat.process_all(0x0040b000,
                        ip="10.1.40.164")

Partial output:

mapping: 0x00401020 -> 0x0040b000 with val None: 0xbadf00d
skipping 0x00401670 with val None: 0xbadf00d
mapping: 0x00401730 -> 0x0040b656 with val None: 0xbadf00d
...

Abbildung 3. Beispiel für eine verschleierte (links) und eine deobfuskierte (rechts) Funktion in Emotet

Umleiten von Verweisen auf deobfuskierte Funktionen

Stadeo aktualisiert Verweise auf Funktionen nach ihrer Deobfuskation nicht automatisch. Im folgenden Beispiel wird gezeigt, wie ein Funktionsaufruf in Abbildung 1 aus dem Beispiel der Deobfuskation einer einzelnen Funktion gepatcht wird. Die gepatchte Referenz ist in Abbildung 4 dargestellt.

Wir verwenden den folgenden Code, um Aufrufe, deren vierter Parameter 0x567C ist, an die verschleierte Funktion bei 0x1800158B0 mit der deobfuskierten Funktion bei 0x18008D000 zu patchen. Beachten Sie, dass Sie sicherstellen müssen, dass IDA die Parameter der Funktion korrekt erkannt hat und sie möglicherweise korrigieren.

from stadeo.utils.xref_patcher import patch_xrefs
patch_xrefs(0x1800158B0,
            0x18008D000,
            {3: 0x567c},
            ip='10.1.40.164')

Abbildung 4. Gepatchte Referenz

Verschleierte Zeichenfolgen in einer Funktion anzeigen

Die Funktion StringRevealer.process_funcs () zeigt verschleierte Zeichenfolgen in der angegebenen Funktion an und gibt eine Zuordnung von deobfuskierten Zeichenfolgen und deren Adressen zurück.

Beachten Sie, dass der Control-Flow der Zielfunktion bereits deobfuskiert sein muss.

Im folgenden Beispiel werden die Zeichenfolgen der Funktion bei 0x100982B7 (siehe Abbildung 5) deobfuskiert. Die Funktion selbst wurde im vorherigen Beispiel für die Verarbeitung erreichbarer Funktionen deobfuskiert.

from stadeo.string.string_revealer import StringRevealer
sr = StringRevealer(32)
strings = sr.process_funcs([0x100982B7],
                           ip="10.1.40.164")

Abbildung 5. Teil der Funktion bei 0x100982B7, die eine Zeichenfolge eindeutig zusammensetzt

Der Inhalt der Zeichenfolgenvariablen nach der Ausführung lautet:

{0x100982B7: {'SeLockMemoryPrivilege'}}

Wir hoffen, dass Sie die Stadeo-Tools und diese Erklärung für ihre Verwendung hilfreich finden. Wenn Sie Fragen haben, wenden Sie sich an threatintel[at]eset.com oder öffnen Sie ein Problem unter https://github.com/eset/stadeo.