Die Prozessumgebung auf Windows-Systemen steckt voller versteckter Strukturen, die für Malware-Entwickler von großem Interesse sind. Eine dieser Strukturen, die Process Environment Block (PEB), spielt eine entscheidende Rolle beim Verbergen von Aktivitäten und beim Umgehen von Erkennungs- und Analysemechanismen. Malware nutzt die PEB, um wichtige Prozessinformationen zu erlangen, wie zum Beispiel die Basisadresse des Images oder den Status eines Debuggers. Doch die traditionellen Methoden, um auf die PEB zuzugreifen, sind mittlerweile gut dokumentiert und leicht zu erkennen. In der Folge haben sich diverse Techniken zur sogenannten PEB-Obfuskation herausgebildet, deren Ziel es ist, das Auslesen der PEB zu verschleiern und somit statische Analysen zu erschweren.
Dabei liegt der Schwerpunkt nicht ausschließlich darauf, die Information zu erhalten, sondern ebenso darauf, wie der Zugriff darauf erfolgt, um die Detektion durch Sicherheitslösungen oder Endpoint Detection and Response (EDR) Systeme zu umgehen. Die meist genutzte Variante zum Zugriff auf die PEB beruht darauf, den GS- bzw. FS-Segmentregisterwert auszulesen. Für 64-Bit-Prozesse wird der GS-Segmentregister verwendet, für 32-Bit-Prozesse hingegen FS. Die klassische Instruktion unter x64 lautet: mov rax, gs:[0x60].
Diese Anweisung lädt die Speicheradresse des PEB, die sich im GS-Segment an Offset 0x60 befindet, in das Register RAX. In der x86-Welt entspräche dies mov eax, fs:[0x30]. Doch genau diese festen Bytes kann eine statische Analyse nutzen, um verdächtigen Code zuverlässig zu erkennen. Spezielle YARA-Regeln, die Bytefolgen wie 65 48 8B 04 25 60 00 00 00 identifizieren, erfassen diese typischen PEB-Lese-Techniken. Um die statische Erkennung zu umgehen, stellt sich die Herausforderung, diese Bytefolgen zu verschleiern oder durch alternative Instruktionsfolgen zu ersetzen, ohne dabei das Ergebnis zu verändern.
Ein relativ einfacher Ansatz besteht darin, zusätzliche CPU-Operationen einzufügen. Statt direkt auf gs:[0x60] zuzugreifen, kann beispielsweise ein Register mit einem Wert initialisiert werden, dann Offsetwerte hinzuaddiert und erst danach auf gs:[Registerwert] zugegriffen werden. Diese Erweiterung bewirkt, dass der Assembler andere Maschineninstruktionen generiert, deren Bytecode nicht mit den statischen Signaturen übereinstimmt. Auch wenn der Zugriff letztlich identisch ist, variiert die Darstellung und erschwert somit die automatische, statische Mustererkennung. Ein Beispiel hierfür wäre, Register wie RDX mit Null zu initialisieren, anschließend Offsetwerte hinzuzufügen und zuletzt anstatt eines direkten konstanten Offsets ein indirektes Memory-Operand mit Registern zu referenzieren.
Das führt dazu, dass die entsprechenden Bytefolgen sich deutlich von den typischen Mustern unterscheiden. Diese Technik setzt auf das Prinzip, durch minimale Verschiebungen und Berechnungen im Code eine individuelle Umsetzung der gleichen Logik zu bekommen. Eine weitere raffinierte Methode verwendet spezieller CPU-Befehle wie RDGSBASE (Read GS Base) oder RDFSBASE (Read FS Base). Diese ermöglichen es, den Basiswert des GS- oder FS-Segments direkt auszulesen. Anschließend kann durch Addition des bekannten Offsets der Pointer auf den PEB erreicht werden.
Da der RDGSBASE-Befehl nicht in älteren Microarchitekturen existiert und neue Bytesequenzen erzeugt, können klassische Mustererkennungen hier an ihre Grenzen stoßen. Die Adressierung erfolgt so ohne den unmittelbaren Zugriff auf sg:[konstanter Offset], sondern durch indirekte Adressberechnung. Somit erhält man eine weitere Verschleierungsebene, die statische Tools schnell überlisten kann. Neben der Änderung des Zugriffsmodus auf den PEB gibt es weiterreichende Ansätze, sogenannte Syscalls direkt auszuführen, um Informationen über den aktuellen Prozess vom Kernel anzufordern. Über den NT-Subsystem Schnittstellenruf NtQueryInformationProcess kann unter Windows der PEB-Adressraum abgefragt werden, ohne dabei das GS-Segment explizit zu benutzen.
Zwar ist die Implementierung etwas komplexer, da man den Systemaufruf manuell mit Parametern bestücken muss und die exakte Syscall-ID ermitteln sollte, dennoch eröffnet diese Methode die Möglichkeit, typische Muster zu umgehen und dennoch ans Ziel zu gelangen. Die direkte Nutzung von Syscalls hat jedoch Nachteile. Zum einen wird der Code umfangreicher und kann unerwünschte Aufmerksamkeit durch Überwachungssysteme erzeugen, da dort häufig Hooks und Überprüfungen von Systemaufrufen vorgenommen werden. Weiterhin ist das Risiko höher, dass Änderungen im Betriebssystem oder Updates die Mechanik und Syscall-ID verändern, was die Zuverlässigkeit der Methode beeinträchtigen kann. Deshalb ist sie häufig eher ein Proof of Concept als eine praktikable Standardlösung.
Erfolgreiche PEB-Obfuskation verfolgt das Ziel, sowohl vor statischer wie auch vor dynamischer Analyse geschützt zu sein. Während die statische Analyse sich auf Byte-Muster und Signature-Scanning konzentriert, kann eine dynamische Analyse mit Emulation oder Verhaltensbeobachtung auch versteckte oder verschleierte Codes aufdecken. Die hier vorgestellten Methoden zur Bytecode-Verschleierung reduzieren allerdings deutlich die Chancen, dass signatur-basierte Scanner den Code erkennen. Ein wichtiger Hinweis dabei ist, dass viele der vorgestellten Techniken weiterhin den GS- oder FS-Segmentzugriff verwenden, dieser aber durch Umwege und zusätzliche Zwischenschritte weniger offensichtlich ist. Zusätzlich dazu zeigt sich, dass sich die hier diskutierten Methoden nicht nur auf den Zugriff auf die PEB an sich beschränken.
Sie lassen sich durchaus auf das Auslesen anderer kritischer Parameter innerhalb der PEB übertragen. So kann der Status eines Debuggers, die Basisadresse des Image-Moduls oder bestimmte Flag-Werte ausgespäht werden. Die Bedeutung und der Nutzen von Obfuskationstechniken beschränken sich daher nicht nur auf einen Anwendungsfall, sondern sind eine universelle Technik für fortgeschrittene Malware-Entwicklung. Für Entwickler von Sicherheitslösungen stellt das eine erhebliche Herausforderung dar. Die Erkennung muss dynamischer, kontext- und verhaltensbasiert werden, denn rein statische Ansätze können zunehmend umgangen werden.
Gleichzeitig zeigt sich, dass moderne Malware immer mehr auf technische Tricks setzt, die über die reine API-Nutzung hinausgehen. Die ständige Weiterentwicklung der EDR-Systeme ist daher gefragt, um neben den bekannten Signaturen auch ungewöhnliche Zugriffswege und auch Muster in der Codeausführung zu erkennen. Für x86-Architekturen gelten ähnliche Prinzipien, obgleich die verwendeten Register und Segmentpunkte sich unterscheiden. Statt GS wird FS genutzt, und die Offsets sind abweichend. Auch hier gilt, dass einfache Muster aufgrund der begrenzten Anzahl möglicher Befehlsemulationen erkannt werden können.