Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
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

View Repository
8314111 year agoReviewed by Kitploit

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

BYOVD-DriverKiller

This README (EN) may contain errors !

⚠️ Disclaimer: This project is strictly educational and demonstrative. It is not intended for malicious use.
The goal is to learn reverse engineering methodology and the exploitation steps of a Windows driver.


Here I explain the approach I followed to solve the exercise proposed by d1rk (SaadAhla) https://github.com/SaadAhla, consisting of performing reverse engineering and exploitation on a legitimate, signed driver, not present in blocklists (HVCI, LOLBIN...).
A C program allowing to terminate any active process on the system via this Kernel-mode Driver is available, I detail its operation below.

POC-BYOD

📃 Usage:

DriverKiller.exe <process_name.exe> [-d]

Option -d: Removes the service and the Driver from the system after exploitation.

Testsigning mode must be enabled on the target machine because the Driver’s certificate has expired.


Part 1 - Reverse Engineering

The exercise provides a .sys file, named with its SHA-256 hash.
The first step is to open this file with IDA.
IDA is available for free. You just need to go to the Hex-Rays website to generate a license and download the software.

We start by listing the IAT (Import Address Table) of the Driver and searching for the API call we are interested in: ZwTerminateProcess.

screen1-git

Double-clicking on ZwTerminateProcess redirects us to the compiled code of this function. By selecting the entry and displaying the cross-references, we obtain the list of Driver functions that call it.

screen2-git

We see that the function sub_12EF4, at offset 1CE, uses ZwTerminateProcess. After double-clicking, IDA displays its compiled code.

screen11-git

The decompiled code reveals calls to ZwOpenProcess (which opens a handle to the target process) and to ZwTerminateProcess (which terminates the process via this handle).

Looking at the documentation of ZwOpenProcess (https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-zwopenprocess), we see that the parameter ClientID corresponds to a pointer indicating the PID of the target process.

On the line above, ClientId.UniqueProcess is initialized with variable v22. This is defined just above:

v22 = (void *)(*(_QWORD *)i + 10);

To understand this assignment, we must identify variable i and the field +10.

screen3-git

Earlier in this function, we see a call to ZwQuerySystemInformation with parameter SYSTEM_PROCESS_INFORMATION. We also see that i is the iterator over the entries of this structure with variable v6.

According to the documentation of ZwQuerySystemInformation: https://learn.microsoft.com/en-us/windows/win32/sysinfo/zwquerysysteminformation, this function returns an array containing one entry per active process on the system.

The structure SYSTEM_PROCESS_INFORMATION is described here: https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation

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;

Reminder: sizes of some types on Windows x64:

  • ULONG = 4 bytes
  • USHORT = 2 bytes
  • HANDLE = 8 bytes
  • PWSTR = 8 bytes
  • KPRIORITY (typedef of LONG) = 4 bytes
  • UNICODE_STRING = 16 bytes, since its structure is:
typedef struct _UNICODE_STRING {
    USHORT Length;        -> 2      
    USHORT MaximumLength; -> + 2 = 4
    PWSTR  Buffer;        -> + 8 = 12 (12 is not a multiple of 8 so 4 bytes of padding are added before Buffer) = 16
} UNICODE_STRING;

Offset calculation of UniqueProcessId:

    ULONG NextEntryOffset;        -> 4
    ULONG NumberOfThreads;        -> + 4 = 8
    BYTE Reserved1[48];           -> + 48 = 56
    UNICODE_STRING ImageName;     -> + 16 = 72
    KPRIORITY BasePriority;       -> + 4 = 76 (76 is not a multiple of 8 so 4 bytes of padding are added) = 80
    HANDLE UniqueProcessId;       -> + 8 = 88

So UniqueProcessId is at offset 0x50 (80 in decimal).

Looking at the assignment of variable v22, we see that i is cast as a pointer QWORD (8 bytes):

v22 = (void *)*((_QWORD *)i + 10);

So v22 corresponds to address of i + 10 * 8 = 80 bytes. This variable thus contains the PID retrieved from the SYSTEM_PROCESS_INFORMATION structure.
To know which PID will be passed to ZwTerminateProcess, we need to analyze the condition surrounding this assignment.

screen4-git

We can see that the process image name is first retrieved:

v9 = (wchar_t *)*((_QWORD *)i + 8);

Because v9 = address of i + 8 × 8 = 64 bytes. This corresponds to the Buffer of the ImageName member, since this member is located at offset 56 + 2 (USHORT) + 2 (USHORT) + 4 (padding) = 64.

Given the manipulations and the loops below, we can hypothesize that a comparison is made between the process name passed as argument (a2) and the active processes on the system (v9/String):

 sub_1C078(String, v9, (int)v13); v17 = strupr(a2); v18 = strupr(String); 

Thus, the parameter a2 is expected to contain the name of the process to be terminated via ZwTerminateProcess. We can see that a2 is a parameter of the function sub_12EF4. To go further, we need to examine the references to this function (I renamed it ZwTerminateProcessCaller for better readability).

screen5-git

We can see that ZwTerminateProcessCaller is called by the function sub_13624 at offset 61A.

screen6-git
Download Tool