Entendiendo el PEB

Das Verständnis des PEB

Jacobo


Dies wird der erste einer Reihe von Artikeln sein, in denen versucht wird, die grundlegende Theorie zu verstehen, die notwendig ist, um in die Welt der Malware in Windows-Umgebungen einzutauchen. 

Es ist schwierig, einen Ausgangspunkt zu finden, um sich in diesem Bereich der Cybersicherheit zurechtzufinden. Ein Punkt, der komplexe und nicht alltägliche Aspekte verständlich macht und gleichzeitig leicht zugänglich ist, sodass man sagen kann, man beginnt auf Level 0, also grundlegend, aber ohne zu viele grundlegende Dinge zu behandeln, die jeder Informatiker wissen sollte. 

So wurde beschlossen, mit dem PEB zu beginnen. Was ist das? Warum ist es in der Malware-Entwicklung wichtig?

Ein bisschen Theorie:

Es ist interessant zu verstehen, was hinter den ausführbaren Windows-Dateien steckt, sei es eine Datei mit der für uns alle üblichen EXE-Erweiterung oder Erweiterungen (vielleicht weniger bekannt) wie DLL oder SYS. 

Um zu beginnen, muss man wissen, dass alle dieselbe Art von Datei sind, die Windows als Portable Executable Format bezeichnet. Das Verständnis der Struktur dieser Dateien ist Gegenstand der folgenden Artikel. Mindestens muss man jedoch wissen, dass, wenn eine dieser Dateien ausgeführt wird, Windows einen Prozess erstellt, mit mindestens einem Thread, der für die Ausführung des Codes der Datei verantwortlich ist.

Man könnte sagen, dass dieser erstellte Prozess dafür verantwortlich ist, dem ausführbaren Programm alle notwendigen Werkzeuge bereitzustellen, damit es korrekt gestartet wird. 

Ein Beispiel: Wenn man einen C-Code schreibt, der mittels des typischen printf ein "Hallo Welt" auf dem Bildschirm ausgibt, passieren Dinge im Hintergrund, die normale Benutzer, sogar Entwickler, oft nicht kennen. 

Printf ist eine Funktion, die in einer Bibliothek namens ucrtbase.dll definiert und deklariert ist. Diese Funktion ist jedoch auf sehr hohem Niveau, und wir wissen, dass Windows einen Kernel hat, der diese Funktionen nicht kennt. Wie gelingt es also, dass der Kernel Informationen erhält, die er verarbeiten kann? über die nativen Windows-APIs oder WinAPIs. 

Kurz gesagt, wenn wir ein Programm mit einem printf erstellen und ausführen, wissen wir, dass eine Reihe von Aufrufen von sehr hohem Niveau wie printf bis zu sehr niedrigem Niveau wie ZwWriteFile erzeugt werden. Das tiefere Verstehen der Funktionsaufrufe der WinAPIs wird Gegenstand eines anderen Artikels sein.

Wieder stellt sich die Frage: Wie stellt ein Prozess dem ausführbaren Programm die notwendigen Werkzeuge bereit, damit es korrekt gestartet wird? Die Antwort ist einfach: indem er die Kontrolle über alle Informationen hat, die ein ausführbares Programm zum Starten benötigt. Das heißt, wie wir gesehen haben, benötigt unser Programm mindestens die DLL namens ucrtbase.dll. Wie bekommt das ausführbare Programm (und lädt in seinen Speicher) die Adresse der DLL, um deren Funktionen wie printf zu verwenden? über den PEB. 

Der Process Enviroment Block ist eine Windows-Datenstruktur, die Informationen für den Start von Prozessen und damit von ausführbaren Dateien enthält, wie deren eigene PID, ob der Prozess gerade debuggt wird und vor allem Zeiger auf andere Strukturen wie LDR_DATA, die schließlich Informationen über die für die ausführbare Datei notwendigen DLLs wie deren Adresse enthalten.

Diese Struktur ist von Windows nur teilweise dokumentiert. Es werden uns nur klare Informationen zu 7 der 19 Variablen gegeben. 

 


Wir wissen, dass alle Variablen vom Typ Byte einen Byte Speicherplatz belegen und PVOID-Variablen Zeiger auf etwas sind und daher 8 Bytes Speicher belegen. Es ist notwendig, mindestens ihre Größe zu kennen, um die Speicheradressen und Offsets korrekt berechnen zu können, wenn wir den Code debuggen oder versuchen, darauf in unserem Code zuzugreifen.

Zum Glück haben wir die Community, die zu diesen Themen forscht und uns inoffizielle, aber genauere Daten als die offizielle Microsoft-Dokumentation bietet. Ein Beispiel ist die Seite https://www.vergiliusproject.com/, auf der wir die große Mehrheit der Windows-Strukturen einsehen können. Wir empfehlen, die PEB-Struktur auf dieser Webseite https://www.vergiliusproject.com/kernels/x64/windows-11/24h2/_PEB zu besuchen.. Sie ähneln sich in gewisser Weise und geben eine reale Vorstellung von ihren Variablen. 

Wenn wir uns an die offizielle Windows-Dokumentation halten, ist das, was am meisten interessiert (vorerst) am PEB die Variable, die als Ldr definiert ist. Diese Variable ist wiederum eine Windows-Struktur vom Typ _PEB_LDR_DATA und wie der PEB teilweise von Microsoft dokumentiert. 

Laut offizieller Dokumentation sieht sie so aus.

 


Unser Ziel ist es, den PEB zu verstehen, wie die Kontrollstruktur der Prozesse aussieht und wie sie es ausführbaren Dateien (PE) ermöglicht, alle benötigten DLLs zu verwenden, unter anderem. 

PEB_LDR_DATA hat eine Variable vom Typ LIST_ENTRY namens InMemoryOrderModuleList, die aus zwei Zeigern besteht. Diese Zeiger geben die Speicheradresse aller DLLs an, die die ausführbare Datei benötigt, sowohl die nächste DLL als auch die vorherige DLL, wodurch eine doppelt verkettete Liste der zu ladenden DLLs entsteht. 

 


Diese Zeiger heißen laut Microsoft-Dokumentation Flink und Blink. 

An dieser Stelle ist es interessant, darüber nachzudenken, wie diese doppelt verkettete Liste einem ausführbaren Programm alle Informationen darüber gibt, welche DLL geladen werden soll. Wenn man ein wenig darüber nachdenkt, ist es mindestens notwendig, den Namen der DLL und den absoluten Pfad zu kennen, wo sie auf der Festplatte gespeichert ist, um sie in den virtuellen Speicher laden zu können. 

Wir haben eine doppelt verkettete Liste, gebildet durch struct _LIST_ENTRY, wobei flink auf die Adresse des nächsten _LIST_ENTRY zeigt, von dem wiederum der nächste flink abgerufen wird, und so weiter. Wo sind die Informationen zu jeder benötigten DLL?

Das ist ganz einfach. Jeder _LIST_ENTRY gehört zu einer größeren Struktur namens _LDR_DATA_TABLE_ENTRY. Auch dies ist eine Windows-Struktur, die von Microsoft teilweise dokumentiert ist.

 


Wie man sehen kann, enthält die zweite Zeile die Struktur LIST_ENTRY (Achtung, es ist kein Zeiger auf die Struktur, sondern die 16 Bytes der beiden Zeiger sind dort enthalten). Man kann auch die Variablen DllBase, EntryPoint oder FullDllName erkennen, die tatsächlich alle notwendigen Informationen enthalten, um alle DLLs zu laden, die die ausführbare Datei benötigt. 

Zusammenfassend ist der PEB eine Windows-Struktur, die alle notwendigen Informationen des Prozesses enthält. Dank ihr kann eine ausführbare Datei alle erforderlichen DLLs laden, damit die Funktionen des Programms und die für den Kernel benötigten geladen werden. Wie gelernt, geschieht dies alles durch eine Reihe von Strukturen, die alle Informationen in Ordnung halten. 

 



An die Arbeit:

Um den PEB und seine gesamte Struktur zu analysieren und zu beobachten, wird WinDBG, der offizielle Windows-Debugger, verwendet. Außerdem wird mit Hilfe von VisualStudio ein kleines C-Programm erstellt, das alle Daten überprüft und dazu dient, das Wissen zu festigen und zu verstehen, dass von einer ausführbaren Datei aus auf diese zugegriffen werden kann. 

Zunächst beginnen wir mit WinDBG. Dieser Debugger ermöglicht es uns, sowohl eine ausführbare Datei von der Festplatte zu starten und ihren Prozess zu analysieren, als auch uns an einen laufenden Prozess eines ausführbaren Programms anzuhängen.

 


In diesem ersten Fall starten wir unser Programm mit dem printf von "Hallo Welt" von Grund auf.

 


Sobald gestartet, verwenden wir den ersten Befehl, der !peb sein wird (WinDBG ist case sensitive). Damit zeigt uns der Debugger alle Informationen über diese Struktur und ihre Unterstrukturen an.

 


Dank des Befehls !peb können diese Strukturen und Speicheradressen bereits eingesehen werden. Zunächst zeigt der erste Pfeil auf die Speicheradresse des PEB. Es wurde noch nicht erwähnt, aber offensichtlich ist diese Adresse eine Adresse im virtuellen Speicher und das PEB ist für jeden Prozess einzigartig. 

Ein Prozess kann mehrere Threads haben. Da dies möglich ist und jeder Thread seine eigene TEB-Struktur (Thread Environment Block) hat, hat jeder TEB der Threads desselben Prozesses auch sein eigenes PEB?

Der nächste Pfeil zeigt uns die Adresse der Variable Ldr, die den Zeiger auf die erste LIST ENTRY-Struktur enthält. 

Schließlich kann man beobachten, wie vom PEB aus alle geladenen DLLs abgerufen wurden, darunter ucrtbased.dll, die für printf zuständig ist.

WinDBG ermöglicht es uns außerdem, einen Speicherabbild zu erstellen und den Inhalt dieser zu sehen, also werden wir uns den Speicher ansehen und wie diese Strukturen darin gespeichert sind. 

Zunächst wird auf die PEB-Struktur (0x00000042393fc000) zugegriffen.

 




Nach einer Verschiebung von 0x18 gelangt man zur Variable Ldr, die ein Zeiger auf die Struktur ist. Wir werden ihr sowohl an dieser Adresse als auch an den folgenden Adressen folgen, bis wir zum ersten geladenen Modul gelangen.

 


Dies ist das Ergebnis. Der einzige Hinweis, den ich geben werde, ist, dass wir mit dem Befehl du den Speicherabbild der Variable FullDllName des ersten geladenen Moduls erstellt haben. Die restlichen Kästchen, die farblich getrennt sind, müsst ihr versuchen zu identifizieren 😉.



Nächste Schritte:

Warum ist diese ganze Struktur notwendig und warum muss man als Malware-Entwickler mit dem Debugger umgehen können?

Sicherheitsmaßnahmen wie EDRs oder der Windows Defender selbst versuchen täglich, sich weiterzuentwickeln und Dinge auf noch niedrigeren Ebenen zu erkennen, beispielsweise wiederholte Opcode-Ketten. Das macht es notwendig, das Funktionsprinzip dessen, was wir entwickeln, auf niedrigster Ebene so gut wie möglich zu verstehen. 

Insbesondere das Verständnis der Existenz des PEB, welche Daten es enthält und wie man darauf zugreifen kann, öffnet uns den Weg, Anti-Hooking-Maßnahmen zu implementieren, Anti-Debugging-Maßnahmen zu ergreifen oder fortgeschrittenere Techniken wie Reflective Loader Injection anzuwenden, bei denen es notwendig sein wird, alle Abhängigkeiten des ausführbaren Programms manuell zu laden. 

Es wurde eine exe erstellt, die einige der zuvor gesehenen Daten abruft.

 


Es steht dem Leser frei, die .c-Datei zu nehmen und zu modifizieren, um zu versuchen, Informationen über die restlichen Variablen der PEB-Struktur zu erhalten.







Zurück zum Blog

Hinterlasse einen Kommentar

Bitte beachten Sie, dass Kommentare vor der Veröffentlichung genehmigt werden müssen.