Skip to content

Proof of Concepts code for Bring Your Own Vulnerable Driver techniques

Notifications You must be signed in to change notification settings

0xJs/BYOVD_EDRKiller

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BYOVD EDRKiller

Disclaimer

⚠️ This project is provided exclusively for educational purposes and is intended to be used only in authorized environments. You may only run or deploy this project on systems you own or have explicit, documented permission to test. Any unauthorized use of this project against systems without consent is strictly prohibited and may be illegal.

By using this project, you agree to use it responsibly and ethically. The author assumes no liability for misuse or any consequences arising from the use of this project.

Tested on:

  • Windows 11 (24H2)
  • Windows Server 2022 (21H2)

General

To practice Bring Your Own Vulnerable Driver (BYOVD) techniques covered in the CETP course, I set out to build an EDR-killer using a vulnerable driver that is not currently blocked by Microsoft's recommended driver blocklist so I can load it on my latest W11 testing system with secure boot and HVCI enabled.

A well-known example, truesight.sys from Adlice (also listed on LOLDDrivers), is already blocked by Microsoft and will trigger an error explicitly stating that the driver is vulnerable when attempting to load it:

Pasted image 20250727172458

From @d1rkmtr's tweet, another potentially useful vulnerable driver was shared, but like the newly indexed BdApiUtil.sys on LOLDDrivers, it fails to load on the latest Windows 11 versions. This behavior is due to HVCI (Hypervisor-Protected Code Integrity) and Secure Boot, not the driver blocklist. These protections enforce stricter requirements for driver signing and integrity, which unsigned or improperly signed drivers can't meet.

Pasted image 20250727172100

Interestingly, the wsftprm.sys driver, listed on LOLDDrivers, is not on Microsoft's blocklist and loads successfully on fully patched Windows 11 with Secure Boot and HVCI enabled. Its digital signature is valid and accepted by the system.

Pasted image 20250727172206

The vulnerability in this driver was discovered by Northwave, indicating that it could be used for a EDR-killer. Making it a viable candidate for further exploration and development of a proof-of-concept.

Reversing the driver

Disclaimer: I am by no means an experienced reverse engineer. The vulnerability in this driver was already publicly known, and I did not discover it myself. Full credit for the original vulnerability research goes to Northwave.

The first step is to inspect the driver's import table and check whether it imports the following native APIs:

  • ZwOpenProcess
  • ZwTerminateProcess
Pasted image 20250727143504

If these functions are present, the next step is to cross-reference where they are called within the driver. This helps identify code paths that may open or terminate processes. While analyzing the references, I traced the usage of ZwOpenProcess and ZwTerminateProcess back to the driver's entry point, where they are ultimately invoked through the dispatch routine assigned to MajorFunction[14], which corresponds to IRP_MJ_DEVICE_CONTROL.

This dispatch routine is implemented in the function sub_140001540.

Pasted image 20250727144806 Pasted image 20250727145950

The function is currently misidentified as handling IRP_MJ_READ, but based on the driver setup, we know it is actually a IRP_MJ_DEVICE_CONTROL (MajorFunction[14]). This function is a driver dispatch routine, which means its prototype should conform to:

NTSTATUS DriverDispatch(
  _In_ PDEVICE_OBJECT DeviceObject,
  _Inout_ PIRP Irp
);

So we can change the input parameters;

Pasted image 20250727150140

As we've determined that this function handles IRP_MJ_DEVICE_CONTROL requests (not IRP_MJ_READ), several structure references need to be corrected. For example:

Pasted image 20250727150317 Pasted image 20250727150852

Next, I revisited the ZwTerminateProcess import and found that it is called within the function sub_140002848. Inside this function, the flow is straightforward:

  • ZwOpenProcess is first called to obtain a handle to the target process.
  • If the handle is valid, ZwTerminateProcess is invoked to kill that process.
Pasted image 20250727151547 Pasted image 20250727151631 Pasted image 20250727151712

The function takes a1 as input, which is passed into the fourth parameter of ZwOpenProcess. According to the ZwOpenProcess prototype, the fourth argument is a pointer to a _CLIENT_ID structure, which specifies the process

NTSYSAPI NTSTATUS ZwOpenProcess(
  [out]          PHANDLE            ProcessHandle,
  [in]           ACCESS_MASK        DesiredAccess,
  [in]           POBJECT_ATTRIBUTES ObjectAttributes,
  [in, optional] PCLIENT_ID         ClientId
);

We can change the parameters and structures to reflect this, I also renamed the function to TerminateProcessByID

Pasted image 20250727163908

TerminateProcessByID is called by sub_14000264C with a supplied buffer. I renamed the function to BeforeTerminateProcessById

Pasted image 20250727163949

Next, I investigated how to trigger the functions BeforeTerminateProcessById and TerminateProcessByPID. With the help of ChatGPT, I discovered that it is invoked when a DeviceIoControl call is made with IOCTL 0x22201C and when the input buffer is 1036 bytes.

Pasted image 20250727164333

To determine the IOCTL code being handled when IOCTLCode2 == 4, we need to walk backward from that comparison and calculate the corresponding value for the IOCTL code. The logic is as follows:

v8 = IOCTLCode - 0x222000            // IOCTL 0x222000
v9 = v8 - 4                          // IOCTL 0x222004
v10 = v9 - 4                         // IOCTL 0x222008
v11 = v10 - 16                       // IOCTL 0x222018
IOCTLCode2 == 4                      // IOCTL 0x22201C
Pasted image 20250727165443

When we go back to TerminateProcessByID we can see that the first 4 bytes of the input buffer will be set within the _CLIENT_ID struct. Which is then passed into ZwOpenProcess.

Pasted image 20250727164956

This means that, of the 1036-byte input buffer expected by the driver when handling IOCTL 0x22201C, the first 4 bytes must contain the target Process ID. In C we can define this with the following example;

// Custom struct for the IOCT call
typedef struct _wsftprmKillBuffer {
    DWORD   dwPID;
    BYTE    bPadding[1032];
} wsftprmKillBuffer;

Back in the driver’s entry point, we can see that the symbolic link is created using the result of the sub_140001410 function. This symbolic link is important because it defines the user accessible name under \\DosDevices\\ that user mode applications will use to interact with the driver via CreateFileW.

Pasted image 20250727170526

These functions use XOR-encoded byte sequences to obfuscate the symbolic link name. In the case of this driver, the function sub_140001410 performs the decryption at runtime.

Pasted image 20250727170610

ChatGPT successfully decrypted the obfuscated symbolic link string as: \\DosDevices\\Warsaw_PM. This was the final missing piece needed to fully interact with the driver and begin building our EDRKiller.

Summary of Key Details

  • Device Name: \\DosDevices\\Warsaw_PM
    • Accessed from user-mode as \\\\.\\Warsaw_PM)
  • IOCTL Code: 0x22201C (Triggers the vulnerable process termination routine)
  • Expected Input Buffer:
    • Size: 1036 bytes
    • Format: The first 4 bytes represent the target PID as a DWORD
typedef struct _wsftprmKillBuffer {
    DWORD   dwPID;
    BYTE    bPadding[1032];
} wsftprmKillBuffer;

Proof of Concept

This C project includes a proof-of-concept (POC) that enumerates EDR-related processes and repeatedly sends the vulnerable IOCTL to terminate them in a loop, continuing until the user presses q to exit.

EDR's

Currently, the targeted EDR solutions and associated processes are:

  • Microsoft Defender Antivirus
  • Microsoft Defender for Endpoint
  • Elastic EDR
  • Sysmon

What does it do

  • Writes vulnerable driver to C:\Windows\System32\Drivers\<FILE> and loads the driver (Configurable in settings.h)
  • Keeps looping and enumerating EDR Processes
  • Kills EDR Process using the IOCTL of the vulnerable driver
  • Exits when q is pressed
  • Unloads the vulnerable driver and removes the file from C:\Windows\System32\Drivers\<FILE>

Test

The testing environment has Secure Boot, Virtualization-Based Security (VBS), and Hypervisor-Protected Code Integrity (HVCI) enabled. These mitigations were verified using my EnumMitigations tool.

Pasted image 20250727190301 Pasted image 20250727190532

Cleanup

  • The payload should unload and remove the driver. If it didn't then manually remove it
sc stop wsftprm
sc delete wsftprm
del C:\Windows\System32\Drivers\wsftprm.sys

Credits

I got inspired to expand upon the tools provided in the Evasion Lab (CETP from Altered Security), taught by Saad Ahla.

To NorthWave for finding the vulnerable driver

About

Proof of Concepts code for Bring Your Own Vulnerable Driver techniques

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages