Sogenannte EDRs (Endpoint Detection and Response Software) und AV (Anti-Virus) Software verwenden unterschiedliche Techniken zur Detektion von potenziell schädlichem Code.

Unter vielen anderen Techniken, existieren drei einfache Möglichkeiten, um schädliches Verhalten zu erkennen:

  1. Userland API Hooks: Hooks werden für bestimmte API Funktionen gesetzt (wie z.B. `CreateRemoteThread`) um die Parameter der aufgerufenen Funktionen zur Laufzeit zu inspezieren
  2. Call Stacks: Über Call Stacks kann der Kontext, aus welcher eine Funktion ausgeführt wird, nachvollzogen werden. Aufgrund dieser Informationen lässt sich beurteilen ob es sich dabei um einen legitimen Funktionsaufruf handelt.
  3. Unbacked Memory: Generell ist Code, welcher aus ‚unbacked memory‘ (also Arbeitsspeicher, der nicht einem Programm auf der Festplatte entspricht) ausgeführt wird, nicht üblich.

In diesem Blogpost soll es darum gehen, mit welchen Techniken Angreifer Userland API Hooks umgehen können. Dieses Wissen ist vor Allem in sogenannten Red-Teaming- (oder Purple-Teaming)-Assessments relevant, da hier fähige Angreifergruppen simuliert und nachgestellt werden. Dazu gehört unter anderem, dass dieselben Techniken angewendet werden. Im Folgenden wird nach einer kurzen Einleitung auf die Möglichkeiten eingegangen, Userland Hooks zu umgehen. Dabei werden die folgenden drei Möglichkeiten betrachtet:

  • Entfernen der Hooks
  • Direkte Syscalls
  • Indirekte Syscalls

Einleitung:

Zuerst muss erläutert werden wie Programme mit dem Betriebssystem interagieren können.

Als Beispiel dient das Erstellen eines neuen Threads: Microsoft bietet hierfür (unter anderem) die Funktion `CreateRemoteThread` aus der Windows API an.

Da das Erstellen von neuen Threads die Aufgabe des Windows Kernels ist, muss an irgendeiner Stelle vom Usermode in den Kernelmode gewechselt werden. Aus einer Abstrahierten Sichtweise sieht der Aufruf von `CreasteRemoteThread` wie folgt aus:

Userland Hooks Malware 1

Die Windows API implementiert die dokumentierten ‚highlevel‘ Funktionen beispielsweise in Bibliotheken wie Kernel32.dll und Kernelbase.dll. Die dort implementierten Funktionen rufen eine Ebene tiefer die sogenannten nativen Funktionen auf (`Nt-` / `Zw` Funktionen) in der Bibliothek ntdll.dll.

In dieser Bibliothek wird über die Instruktion `syscall` die Ausführung an den Kernel übergeben.

Ein AV-Hersteller kann nun an zwei Stellen sogenannte Hooks platzieren. Einmal auf Ebene der Windows API (Hier: Kernel32) in der Funktion `CreateRemoteThread` und einmal in der nativen Bibliothek (Hier: ntdll) in der Funktion `NtCreateThreadEx`.

Vor einigen Jahren wurden Hooks lediglich auf Windows API Ebene platziert. Allerdings hat es nicht lange gedauert bis Angreifer direkt die undokumentierten Funktionen aus der nativen API aufgerufen haben, um somit auf direktem Weg Hooks zu umgehen.

AV-Hersteller platzieren Hooks daher direkt auf nativer Ebene in ntdll:

Userland Hooks Malware 2

Um das Prinzip der Hooks besser zu veranschaulichen, einmal ein Beispiel einer ungehookten und einer gehookten Funktion auf Assembly-Level.

Der folgende Code Ausschnitt stammt direkt aus der nativen Bibliothek (ntdll.dll) für die Funktion `NtCreateThreadEx`:

ntdll!NtCreateThreadEx`:
mov r10,rcx
mov eax,18
test byte ptr ds:[7FFE0308],1
jne ntdll.7FFEC298D2E5
syscall
ret

Zusammengefasst besteht der Code aus den folgenden Teilen:

  • In Zeile 2 wird die sogenannte Syscall Nummer (SSN) in das Register EAX geschoben
  • In Zeile 5 wird die Ausführung über die instruction `syscall` an den Kernel übergeben
  • Zeile 6 gibt die Ausführung zurück an die Funktion welche `NtCreateThreadEx` aufgerufen hat

Schaut man sich im Gegenzug dazu die gehookten Version der Funktion an, erkennt man eine jump (`jmp`) Instruktion an erster Stelle um in ein Code Segment des AV Herstellers zu springen, bevor der eigentliche Syscall ausgeführt wird:

ntdll!NtAllocateVirtualMemory:
jmp 7FFCC50A0948
mov eax,18
test byte ptr ds:[7FFE0308],1
jne ntdll.7FFEC298D2E5
syscall
ret

Im Code Segment des AV-Herstellers finden unterschiedliche Überprüfungen statt, die den Funktionsaufruf für entweder gut- oder bösartig klassifizieren und entsprechend erlauben oder blockieren.

Entfernen der Hooks:

Ein einfacher Weg die vorhandenen Hooks zu umgehen, ist diese wieder Rückgängig zu machen und durch die originalen Bytes zu ersetzen. Eine `jmp` Instruktion besteht (in der x86 Architektur) immer aus fünf Bytes.

Das heißt, es reicht die originalen Bytes zu kopieren und im Arbeitsspeicher an die entsprechende Stelle zu kopieren um die `jmp` Instruktion der Hooks zu überschreiben. An die originalen Bytes kommt man durch das Parsen der DLL von der Festplatte. Dort sind immer die unveränderten Instruktionen zu finden.

Grafisch kann man sich das Vorgehen wie folgt vorstellen:

Userland Hooks Malware 3

Direkte Syscalls:

Ein weiterer Weg geht über sogenannte „Direkte Syscalls“. Dabei wird die entsprechende Syscall-Nummer dynamisch aufgelöst (dafür gibt es mehrere Methoden wie HellsGate, HalosGate, ..) und die `syscall` Instruktion wird nicht aus der ntdll.dll selbst aufgerufen, sondern aus dem eigenen Code.

Dabei wird im Endeffekt der obenstehende Code einer ungehookten Funktion im eigenen Code implementiert und aufgerufen. Das einzige Hindernis ist die Syscall-Nummer, da sich diese in jeder Windows Version ändert. Dafür gibt es wie oben erwähnt mehrere Methoden, die jedoch den Rahmen dieses Blog-Posts sprengen würden.

Userland Hooks Malware 4

Umsetzen lässt sich das beispielsweise über das Tool SysWhispers3, welches unter folgendem Link zu finden ist: https://github.com/klezVirus/SysWhispers3.

Hierbei ist wichtig zu erwähnen, dass diese Technik nur für Hooks in der native API (ntdll.dll) funktioniert, da nur diese Funktionen über die `syscall` Instruktion aufgerufen werden können.

Befinden sich Hooks in anderen (nicht `Nt-` Funktionien), hilft diese Technik nicht.

Direkte Syscalls lassen sich gut daran erkennen dass `Nt-` Funktionen (bzw. deren `syscall` Instruktion) im Regelfall immer aus der ntdll.dll aufgerufen werden. Ist das nicht der Fall, ist es ein eindeutiger Indikator einer Schadsoftware.

Indirekte Syscalls:

Eine Möglichkeit die Erkennung von direkten Syscalls zu verhindern und trotzdem gesetzte Hooks zu umgehen sind die sogenannten indirekten Syscalls.

Indirekte- und direkte Syscalls unterscheiden sich nur geringfügig. Ein Unterschied zu direkten Syscalls ist, dass hierbei die `syscall` Instruktion weiterhin aus der nativen API (ntdll.dll) ausgeführt wird.

Das Auflösen der Sycall Nummern erfolgt weiterhin in eigenem Code. Zusätzlich wird noch die Adresse einer `syscall` Instruktion in NTDLL.dll selbst benötigt.

Der generelle Ablauf ist wie folgt:

  1. Auflösen der entsprechenden Syscall Nummer (Wie bei direkten Syscalls via HellsGate, HalosGate, …)
  2. Ausführen des ersten Teils einer nativen API im eigenen Code (Syscall Stub)
    • Dabei geht es hauptsächlich darum die Syscall-Nummer in das entsprechende Register (EAX) zu bringen
  3. Springen zur eigentlichen `syscall` Instruktion in NTDLL.dll selbst
Userland Hooks Malware 5

Wie oben zu sehen ist, wird hierdurch die `syscall` Instruktion durch legitimen Code in der ntdll ausgeführt.

Auch indirekte Syscalls können mit weiteren EDR-Techniken, wie beispielsweise Call-Stack Analyse aufgedeckt werden.  Auch dafür gibt es weitere, etwas komplexere Möglichkeiten diese zu umgehen.