Demontage von KillDisk: Reverse Engineering der destruktiven Komponente von BlackEnergy
Lassen Sie uns die lange Einführung in die BlackEnergy-Bedrohung überspringen und direkt zur Untersuchung der Malware-Komponente namens „ololo.exe“ gehen, auch bekannt als KillDisk. KillDisk ist ein Modul des BlackEnergy-Frameworks, das auf die Zerstörung von Daten abzielt und während der APT-Operationen Chaos und Ablenkung schafft.
https://www.virustotal.com/en/file/11b7b8a7965b52ebb213b023b6772dd2c76c66893fc96a18a9a33c8cf125af80/analysis/Die Hauptwerkzeuge, die wir heute in unserer Analyse verwenden, sind Process Monitor das Teil der SysInternals-Utilities von Mark Russinovich ist und IDA Pro Disassembler. Alle Manipulationen werden in einer virtuellen Umgebung basierend auf dem Betriebssystem Windows XP durchgeführt. Wir beginnen mit der schnellen Grundkonfiguration der Test-VM, schalten die Maschine ein und erstellen einen Snapshot namens „Vor der Infektion“. Lasst uns beginnen!
Um alle Ereignisse im Zusammenhang mit unserem Untersuchungsgegenstand zu verfolgen, starten wir Process Monitor und stellen sicher, dass es sichtbar bleibt:

sub_40E070:
ist. Lassen Sie uns direkt zu dieser Prozedur gehen. Hier finden wir Prozeduren, die für die Extraktion von Dateierweiterungen in den Speicher verantwortlich sind, und direkt danach wird eine interessante Sache namens sub_40E080:


CreateFile und ein paar Prozeduren sub_40C390 and sub_40C400. Es ist also an der Zeit, einen Snapshot unseres virtuellen Labors zu machen und unsere Reise fortzusetzen!
Untersuchen wir den Funktionsaufruf CreateFile, setzen wir den BreakPoint darauf und führen unser Studienobjekt aus:

Wie wir wissen, geben die meisten Compiler Argumente über einen Stack an eine Funktion weiter, und da wir es mit einem Beispiel zu tun haben, das ursprünglich in C geschrieben wurde, und gemäß den C-Direktiven werden Argumente von Stack von rechts nach links übergeben, sodass das erste Funktionsargument als letztes auf den Stack gepusht wird und schließlich als erstes Element oben endet. [1].
There is a special register for working with the stack – ESP (Extended Stack Pointer). The ESP, by definition, always points to the top of the stack:
Ein Blick in den Stack:

CreateFile Funktion) und in unserem Fall den Dateinamen enthält \.PHYSICALDRIVE0, somit ist das Ziel die erste physische Festplatte.Nun analysieren wir andere Argumente, die an die Funktion gesendet werden, indem wir ihre Werte aus dem Stack entnehmen:

- lpFileName – Zeiger auf eine ASCIIZ-Zeichenkette, die den Namen (Pfad) der geöffneten oder erstellten Datei enthält (wie wir bereits herausgefunden haben);
- dwDesiredAccess – Zugriffsart auf die Datei: in unserem Fall ist der Wert
0C0000000hwas bedeutetGENERIC_READ+GENERIC_WRITEorLese-Schreib-Zugriff; - DwShareMode – ein Modus, der das Teilen der Datei mit verschiedenen Prozessen ermöglicht und dieser Parameter kann unterschiedliche Werte haben; in unserem Fall übersetzt der Wert pf 00000003b in
FILE_SHARE_READ+FILE_SHARE_WRITEoderandere Prozesse können die Datei zum Lesen/Schreiben öffnen; - IpSecurityAttributes – Zeiger auf die SecurityAttributes-Struktur (Datei winbase.h), die Sicherheitseinstellungen im Zusammenhang mit dem Kernel-Dateiobjekt definiert; wenn die Sicherheit nicht festgelegt ist, wird ein
NULLWert verwendet; - dwCreationDistribution – entscheidet über Aktionen, wenn eine Datei existiert oder nicht existiert, in unserem Fall hat der Wert von
3die BedeutungOPEN_EXISTINGoröffnen Sie die Datei, wenn sie existiert, und geben Sie einen Fehler zurück, wenn sie nicht existiert; - DwFlagsAndAttributes – Flags und Attribute; dieser Parameter wird verwendet, um die Merkmale der erstellten Datei festzulegen und wird im Lesezugriffsmodus 0 ignoriert.
- hTemplateFile – Parameter wird nur verwendet, wenn eine neue Datei erstellt wird
0. - Bei Erfolg gibt die Funktion den Handler der neuen Datei an
ЕАХzurück, und wenn die Funktion fehlschlägt, wird einNULLin das Register geschrieben.ЕАХAlright, nachdem die Funktion
aufgerufen wurde, erhalten wir einen Wert ungleich Null für EAX, was bedeutet, dass er den Handler der angeforderten Datei enthält, z. B. CreateFile we get a non-zero value for EAX which means that it contains the handler of requested file e.g. \.PHYSICALDRIVE0:
Wir haben unseren Handler, fahren wir fort zum nächsten Aufruf,
sub_40C390:


Ein Blick in den Stack direkt vor dem Aufruf dieser Funktion:
- hDevice – Gerätedescriptor (wir erkennen hier einen bekannten Wert
\.PHYSICALDRIVE0); - dwIoControlCode – Operationscode. In unserem Fall ist der Wert
0x70000hwas bedeutetIOCTL_DISK_GET_DRIVE_GEOMETRYoder Abruf der Informationen zur Plattengeometrie (Anzahl der Zylinder, Medientyp, Spuren pro Zylinder, Sektoren pro Spur, Bytes pro Sektor); - lpInBuffer – Puffer mit Eingabedaten. Wir benötigen keine Eingabedaten, daher
NULList es; - nInBufferSize – Größe des Eingabepuffers in Bytes gleich
0in unserem Fall; - lpOutBuffer – Zeiger auf den Ausgabepuffer. Sein Typ wird durch den Parameter dwIoControlCode festgelegt
0x0011FCA8h; - nOutBufferSize – Größe des Ausgabepuffers in Bytes
0x18h(24); - lpBytesReturned – ein Zeiger auf eine Variable, die die Anzahl der Bytes enthält, die in den Ausgang geschrieben wurden
0x0011FCA4h; - lpOverlapped – Zeiger auf eine OVERLAPPED-Struktur;
- Sobald der Aufruf verarbeitet ist, schauen wir in den Puffer (an der Adresse, die als Argument an lpOutBuffer gesendet wurde)
0x0011FCA8h):

- An der Adresse von
0x0011FCA4hhaben wir eine Rückgabekodierung der Anzahl der in den Ausgangspuffer geschriebenen Bytes, wie von der Funktion beabsichtigt (grün markiert), genau so viele, wie wir angefordert haben, nämlich 24 Symbole. - An der Adresse von
0x0011FCA8hwir haben Informationen über die Geometrie der ersten physischen Festplatte erhalten (\.PHYSICALDRIVE0):- Anzahl der Zylinder –
0x519h(1305) - Medientyp –
0x0Ch(12), feststehende Festplatte. - Spuren pro Zylinder –
0x0FFh(255) - Sektoren pro Spur –
0x3Fh(63) - Bytes pro Sektor –
0x200(512)
- Anzahl der Zylinder –
Jetzt beziehen wir uns auf die dritte Prozedur, sub_40C400:

SetFilePointerExundWriteFile:
Wie üblich werfen wir einen Blick in den Funktionsaufruf-Stack:
- hfile – unser bekannter Gerätedeskriptor
\.PHYSICALDRIVE0; - liDustanceToMove – anfängliche Position zum Beginn eines Schreibens
0; - lpNewFilePointer – ein Zeiger auf eine Variable, die einen neuen Positionszeiger aus der Datei abruft
0x0011FCA8h. Falls der Parameter LEER (NULL) ist, wird kein neuer Zeiger in der Datei zurückgegeben; - dwMoveMethod – Größe des Eingabepuffers in Bytes. In unserem Fall
0x200h(512) – Bytes im Sektor;
Jetzt verwendet unser „Viech“ die WriteFile Funktion und um die Datei mit Nullen zu füllen, auf die es bereits direkten und zuverlässigen Zugriff hat.
Ein Funktionstipp:
Eine WriteFile Funktion schreibt Daten in die angegebene Datei oder das Eingabe-/Ausgabegerät (I/O-Gerät), beginnend mit der in der Datei angegebenen Position. Diese Funktion ist für sowohl synchrone als auch asynchrone Operationen ausgelegt.
Liste der Funktionsargumente:

- 1 – Bleibend beim Funktionsaufruf
WriteFile - 2 – Process Monitor wartet gespannt auf den kleinsten Zucken der „Muskeln“ unseres „Viech“
- 3 – Argumente von Registern in den Stack gedrückt
- 4 – und nun, der Stack selbst
- Ein spezielles Quadrat im Fenster Hex View-EBP markierten wir ein Gebiet, das von dem zweiten Funktionsargument (Datenpuffer) adressiert wird und Sie können mir glauben – es enthält genau
0x200h(512) Nullen.
Wir führen den Befehl durch Drücken der F8-Taste aus und beobachten die Änderung:
Dann verlässt die Prozedur und wird erneut aufgerufen, jedoch nun mit unterschiedlichen Werten:
Nun nehmen wir die nächsten 512 Bytes, was insbesondere bei 0x200 sichtbar ist (im Gegensatz zu vorherigen Argumenten in dem wir den Stack durch einen blauen Rahmen markierten)

0x40C865h Adresse den Wert von EBX Registrierung gleich 0x100h (255 Mal ):


256 von Überschreiboperationen:
CloseHandle Funktion aufruft.
Funktionshinweis:
Funktionsargumente:


Wie erwartet wird unser bekannter Deskriptor
0x44h an den Stack als Funktionsargument übergeben.
Wir sehen die von der Funktion zurückgegebenen Werte im EAX-Register: Wenn die Funktion erfolgreich endet, ist der zurückgegebene Wert
nicht-null.
Wenn die Funktion mit einem Fehler endet, ist der zurückgegebene Wert null.
Diese Aktion entging nicht dem wachsamen Auge unseres Process Monitor:

Lass uns weitermachen:
Eraser. Nachdem es erfolgreich die Daten auf PhysicalDrive0zerstört hat, wird der Zähler im ESI Register um 1erhöht, mit dem Wert von 0x0Ah (10) verglichen und startet den Überschreibungsvorgang mit einem neuen Wert:
Auf diese Weise wandert das Viech weiter über alle physischen Laufwerke und löscht die ersten 512 * 255 = 128kb (dies hängt von der Anzahl der Bytes in einem Sektor ab, die „Viech“ aufgrund der Kenntnis der Plattengeometrie gelernt hat).
Dieses Mal wird jedoch das Ergebnis der Funktion CreateFileW ein folgender Wert sein: Was bedeutet, dass in unserem „improvisierten Labor“ kein zweites physisches Laufwerk (oder nicht irgendeine Art von Laufwerk mit einer Sequenznummer größer als 0) existiert und der Zyklus nichts zu überschreiben hat.


sub_40E080 die den Funktionsaufruf CreateFileWenthält. Da wir wissen, dass der Wert von 0xFFFFFFFFh nach Abschluss der Funktion CreateFileW nur dann erscheinen würde, wenn kein physisches Laufwerk vorhanden ist und in einem solchen Szenario die Funktion „Datenzerstörung“ einen Ausgang ausführt, ändern wir eine bedingte Verzweigungsanweisung an der 0x0040C7E4h Adresse in eine unbedingte:
IDA Pro ist ein cooles Instrument, sodass wir einfach einen Befehl schreiben müssen.
Das Einzige, worauf wir achten müssen, ist die Befehlsgröße vorher und nachher, also lassen Sie uns überprüfen, wie viele Bytes von der bedingten Sequenz verwendet werden:

6 Bytes, während die bedingungslose Sequenz nur 5 nimmt, und deshalb sollten wir einen weiteren Befehl hinzufügen – NOP, um die Dinge gleich zu halten:


Nach diesen Manipulationen sieht die Prozedur einfach aus: eintreten und austreten, eine leere Schleife erstellen (da wir nicht wissen, welche anderen Teile des Programms darin eingreifen könnten, belassen wir sie in diesem Zustand).

Quellen:
[1] – Hacker Disassembling Uncovered. Kris Kaspersky, 2009.
Übersetzt aus dem Original von Andrii Bezverkhyi | CEO SOC Prime



