Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
BYOVD-DriverKiller — Driver Reverse & Exploitation | Kitploit
Tools/GitHubGitHub/alex3o/byovd-driverkiller
ExploitationReverse EngineeringPost-ExploitationBinary AnalysisLearning & EducationRed Teaming
GitHubalex3o/byovd-driverkiller

BYOVD-DriverKiller

Driver Reverse & Exploitation

Repository anzeigen
8314vor 11 MonatenVon Kitploit geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

BYOVD-DriverKiller

⚠️ Warnung: Dieses Projekt ist streng bildungspädagogisch und demonstrativ. Es ist nicht dafür gedacht, in einem böswilligen Kontext verwendet zu werden. Ziel ist es, die Methodik des Reverse Engineering und die Schritte zur Ausnutzung eines Windows-Treibers zu erlernen.


Hier erkläre ich das Vorgehen, das ich zur Lösung der von d1rk(SaadAhla) https://github.com/SaadAhla vorgeschlagenen Übung gewählt habe. Dabei geht es darum, Reverse Engineering und Exploitation an einem legitimen, signierten Treiber durchzuführen, der nicht in den Blocklisten (HVCI, LOLBIN...) enthalten ist. Ein C-Programm, mit dem sich über diesen Kernel-Mode-Treiber jeder aktive Prozess auf dem System beenden lässt, ist verfügbar; ich erläutere seine Funktionsweise weiter unten.

POC-BYOD

📃 Verwendung: DriverKiller.exe <nom_processus.exe> [-d]

Option -d: Ermöglicht das Entfernen des Dienstes und des Treibers vom System nach der Exploitation. Der Testsigning-Modus muss auf dem Zielcomputer aktiviert sein, da das Zertifikat des Treibers abgelaufen ist.


Teil 1 - Reverse Engineering:

Die Übung stellt eine .sys-Datei bereit, die nach ihrem SHA-256-Hash benannt ist. Der erste Schritt besteht darin, diese Datei mit IDA zu öffnen.
IDA ist kostenlos verfügbar. Es genügt, die Website von Hex-Rays zu besuchen, um eine Lizenz zu generieren und die Software herunterzuladen.

Wir beginnen damit, die IAT (Import Address Table) des Treibers aufzulisten und den Aufruf der relevanten API zu suchen: ZwTerminateProcess.

screen1-git

Durch einen Doppelklick auf ZwTerminateProcess leitet IDA uns zum kompilierten Code dieser Funktion weiter. Wenn man den Eintrag auswählt und dann die Cross-References anzeigt, erhält man die Liste der Treiberfunktionen, die ihn aufrufen.

screen2-git

Man sieht, dass es die Funktion sub_12EF4 bei Offset 1CE ist, die ZwTerminateProcess verwendet. Nach einem Doppelklick zeigt IDA ihren kompilierten Code an.

screen11-git

Der dekompilierte Code zeigt die Aufrufe von ZwOpenProcess (das einen Handle zum Zielprozess öffnet) und ZwTerminateProcess (das den Prozess über diesen Handle beendet).

Wenn man die Dokumentation von ZwOpenProcess (https://learn.microsoft.com/fr-fr/windows-hardware/drivers/ddi/ntddk/nf-ntddk-zwopenprocess) konsultiert, stellt man fest, dass der Parameter ClientID einem Zeiger entspricht, der die PID des Zielprozesses angibt.

In der Zeile darüber wird ClientId.UniqueProcess mit der Variablen v22 initialisiert. Diese wird direkt darüber definiert:

root@kitploit:~
v22 = (void )((_QWORD *)i + 10);

Um diese Zuweisung zu verstehen, muss man die Variable i und das Feld +10 identifizieren.

screen3-git

Weiter oben in dieser Funktion sieht man einen Aufruf von ZwQuerySystemInformation mit dem Parameter SYSTEM_PROCESS_INFORMATION. Man versteht auch, dass i zusammen mit der Variablen v6 der Iterator über die Einträge dieser Struktur ist.

Laut der Dokumentation von ZwQuerySystemInformation: (https://learn.microsoft.com/en-us/windows/win32/sysinfo/zwquerysysteminformation) gibt diese Funktion ein Array zurück, das einen Eintrag pro aktivem Prozess auf dem System enthält.

Die Struktur SYSTEM_PROCESS_INFORMATION wird hier beschrieben: https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation

root@kitploit:~
typedef struct _SYSTEM_PROCESS_INFORMATION {
    ULONG NextEntryOffset;
    ULONG NumberOfThreads;
    BYTE Reserved1[48];
    UNICODE_STRING ImageName;
    KPRIORITY BasePriority;
    HANDLE UniqueProcessId;
    PVOID Reserved2;
    ULONG HandleCount;
    ULONG SessionId;
    PVOID Reserved3;
    SIZE_T PeakVirtualSize;
    SIZE_T VirtualSize;
    ULONG Reserved4;
    SIZE_T PeakWorkingSetSize;
    SIZE_T WorkingSetSize;
    PVOID Reserved5;
    SIZE_T QuotaPagedPoolUsage;
    PVOID Reserved6;
    SIZE_T QuotaNonPagedPoolUsage;
    SIZE_T PagefileUsage;
    SIZE_T PeakPagefileUsage;
    SIZE_T PrivatePageCount;
    LARGE_INTEGER Reserved7[6];
} SYSTEM_PROCESS_INFORMATION;

Zur Erinnerung: Größen einiger Typen unter Windows x64

  • ULONG = 4 Bytes
  • USHORT = 2 Bytes
  • HANDLE = 8 Bytes
  • PWSTR = 8 Bytes
  • KPRIORITY (typedef eines LONG) = 4 Bytes
  • UNICODE_STRING = 16 Bytes, denn hier ist seine Struktur:
root@kitploit:~
  typedef struct _UNICODE_STRING {
    USHORT Length;        -> 2      
    USHORT MaximumLength; -> + 2 = 4
    PWSTR  Buffer;        -> + 8 = 12 (12 ist kein Vielfaches von 8, daher wird 4 Bytes Padding vor dem Buffer hinzugefügt) = 16
} UNICODE_STRING;

Berechnung des Offsets von UniqueProcessId:

root@kitploit:~
    ULONG NextEntryOffset;        -> 4
    ULONG NumberOfThreads;        -> + 4 = 8
    BYTE Reserved1[48];           -> + 48 = 56
    UNICODE_STRING ImageName;     -> + 16 = 72
    KPRIORITY BasePriority;       -> + 4 = 76 (76 ist kein Vielfaches von 8, daher werden 4 Bytes Padding hinzugefügt) = 80
    HANDLE UniqueProcessId;       -> + 8 = 88

Das Element UniqueProcessId befindet sich also bei Offset 0x50 (80 dezimal).

Wenn wir uns die Zuweisung unserer Variablen v22 ansehen, stellen wir fest, dass i in einen QWORD-Zeiger (8 Bytes) gecastet wird

root@kitploit:~
v22 = (void )((_QWORD *)i + 10);
v22 entspricht also der Adresse von i + 10 * 8 = 80 Bytes. Diese Variable enthält also die aus der Struktur SYSTEM_PROCESS_INFORMATION abgerufene PID.

Um herauszufinden, welche PID an ZwTerminateProcess übergeben wird, muss die Bedingung analysiert werden, die diese Zuweisung umgibt.

screen4-git

Man sieht, dass zunächst der Name des Prozessimages abgerufen wird:

root@kitploit:~
v9 = (wchar_t )((_QWORD *)i + 8);
Denn v9 = Adresse von i + 8 × 8 = 64 Bytes. Das entspricht dem Buffer des Elements ImageName, da sich dieses Element bei Offset 56 + 2 (USHORT) + 2 (USHORT) + 4 (Padding) = 64 befindet.

Angesichts der Manipulationen und Schleifen unten kann man die Hypothese aufstellen, dass ein Vergleich zwischen dem als Argument übergebenen Prozessnamen (a2) und den aktiven Prozessen auf dem System v9/String durchgeführt wird.

root@kitploit:~
sub_1C078(String, v9, (int)v13);
v17 = strupr(a2);
v18 = strupr(String);

Es ist also der Parameter a2, der den Namen des über ZwTerminateProcess zu beendenden Prozesses enthalten soll. Man beachte, dass a2 ein Parameter der Funktion sub_12EF4 ist. Um weiterzugehen, müssen die Referenzen dieser Funktion untersucht werden (ich habe sie zur besseren Lesbarkeit in ZwTerminateProcessCaller umbenannt).

screen5-git

Man sieht, dass ZwTerminateProcessCaller von der Funktion sub_13624 bei Offset 61A aufgerufen wird.

screen6-git

Bevor ich diesen dekompilierten Code analysiere, suche ich die Referenzen der Funktion sub_13624 (umbenannt in ZwTerminateProcessCallerCaller), um sicherzustellen, dass dieser Code tatsächlich nach einem API-Aufruf von DeviceIoControl aus dem UserMode verwendet wird.

screen§-git

Man sieht, dass ZwTerminateProcessCallerCaller von der Funktion sub_14130 aufgerufen wird (umbenannt in ZwTerminateProcessCallerCallerCaller ... zum Glück für uns ist es die letzte vor dem Einstiegspunkt 😅).

screen7-git

Man sieht, dass ZwTerminateProcessCallerCallerCaller von der Funktion sub_1A4A8 bei Offset 306 aufgerufen wird.

screen8-git

Man findet die Zuweisung der Funktion ZwTerminateProcessCallerCallerCaller:

root@kitploit:~
memset64(DriverObject->MajorFunction, (unsigned __int64)ZwTerminateProcessCallerCallerCaller, 0x1Cu);
Das bedeutet, dass diese Funktion allen Einträgen der MajorFunction-Tabelle zugewiesen wird (0x1B = 27, und es gibt 28 Major-IRPs).

screen9-git

Bevor wir zur Funktion sub_13624 (alias ZwTerminateProcessCallerCaller) zurückkehren, werden der Symbolic Name und der Device Name (hier identisch) abgerufen: Viragtlt.

screen12-git

Wenn wir zu ZwTerminateProcessCallerCaller zurückkehren, stellen wir fest, dass sein zweiter Parameter (also a2) MasterIrp->AssociatedIrp.SystemBuffer entspricht.

screen13-git

Direkt über dem Aufruf von ZwTerminateProcessCaller befindet sich der IOCTL-Code: -2106392528 (hexadezimal: 0x82730030).

Dank dieser Informationen lässt sich ableiten, dass zum Ausnutzen dieses Treibers ein DeviceIoControl-API-Aufruf an den Treiber gesendet werden muss, wobei der Name des zu beendenden Prozesses im SystemBuffer steht.


🔷 Durch das Reverse Engineering gewonnene Informationen:

  • IOCTL-Code: 0x82730030
  • Device Name: Viragtlt
  • Symbolic Name: Viragtlt
  • SystemBuffer muss den Namen des Zielprozesses enthalten

Teil 2 - Ausnutzung

Um diesen Treiber auszunutzen (sofern er auf dem Zielcomputer installiert und aktiv ist), muss ein Handle auf ihn geöffnet und dann ein DeviceIoControl-API-Aufruf mit einem Buffer gesendet werden, der den Namen des Prozesses enthält, den man beenden möchte.
Für diese Übung habe ich ein C-Projekt entwickelt, das:

  • prüft, ob der Treiber auf dem System vorhanden und aktiv ist (mit einem bestimmten Dienstnamen):
    • Wenn ja, nutzt das Programm den Treiber mit einem DeviceIoControl-API-Aufruf aus.
    • Wenn nein, extrahiert das Programm den Treiber aus seinen Ressourcen, legt ihn auf dem Desktop des Benutzers ab, erstellt einen aktiven Dienst und nutzt den Treiber anschließend mit einem DeviceIoControl-API-Aufruf aus. (Erfordert Administratorrechte, da ein Dienst erstellt wird.)
  • Wenn der Treiber auf dem System vorhanden ist, der Dienst aber nicht gestartet wurde, versucht das Programm, den Dienst zu starten, und nutzt ihn anschließend mit einem DeviceIoControl-API-Aufruf aus.

Ich habe außerdem eine Option -d hinzugefügt, die es ermöglicht, den Dienst und den Treiber nach der Ausnutzung vom System zu entfernen.

Hier ist das Verhalten des C-Programms in seinem vollständigen Ausführungszyklus:

git

AV/EDR-Umgehung

In diesem Fall wird DriverKiller.exe weder statisch noch dynamisch von Microsoft Defender erkannt. Die Umgehung ergibt hier nicht wirklich Sinn, da der ausgenutzte Treiber ein abgelaufenes Zertifikat besitzt; sein Einsatz unter realen Bedingungen ist daher kaum denkbar. Für eine bessere Tarnung hätte man jedoch implementieren können:

  • Das Maskieren bestimmter API-Aufrufe in der IAT-Tabelle durch eigene Implementierungen von GetProcAddress und GetModuleHandle
  • Das Verlagern der API-Aufrufausführung näher an den Kernel (Direct/Indirect Syscalls)
  • Anti-VM-/Anti-Debug-Techniken

Erkennung des Treibers am 29.08.2025 (bereits vorhandenes Ergebnis, ich habe aus offensichtlichen Gründen nichts bei VirusTotal eingereicht):

image

⚠️ Dieses Projekt wurde im Rahmen des Lernens erstellt. Es kann Ungenauigkeiten oder Fehler enthalten. Jeder Vorschlag, jede Korrektur oder Diskussion ist willkommen! 😃 Danke an d1rk(SaadAhla): https://github.com/SaadAhla!

Tool herunterladen