Signed to Kill: Reverse Engineering a 0-Day Used to Disable CrowdStrike EDR

Signed to Kill: Reverse Engineering a 0-Day Used to Disable CrowdStrike EDR

Original text by Jehad Abudagga

The article presents a reverse-engineering analysis of a kernel driver used in a BYOVD (Bring Your Own Vulnerable Driver) attack to disable security software, including CrowdStrike Falcon EDR. The researcher discovered multiple variants of a Microsoft-signed driver that expose a dangerous IOCTL interface capable of terminating arbitrary processes. Because the driver is legitimately signed and not blocklisted, Windows allows it to run in kernel mode without restrictions. 

During analysis in IDA Pro, the author found that the driver implements a simple DeviceIoControl handler that processes specific IOCTL codes. One of these IOCTLs triggers a routine responsible for killing processes. The driver receives a process ID as a string, converts it to an integer, and invokes a kernel routine that terminates the specified process. This allows attackers to terminate protected services such as EDR agents. 

The research highlights how attackers can load vulnerable but trusted drivers to gain kernel-level capabilities and bypass endpoint defenses. Because the driver is signed and undetected by antivirus engines, it can be used to disable security monitoring before deploying additional malicious payloads.

I’ve been reversing kernel drivers for over a year now and last week i came across several interesting Kernel drivers that have been used in BYOVD attacks against several EDRs including the well known CrowdStrike EDR

I decided to make a blog about it and publish the POC and drivers for everyone.

Lets start.

In this picture we can see the driver and its variants (15+ variants) all have the same code inside.

Press enter or click to view image in full size

Identified Drivers

All the Drivers are signed by Microsoft and have valid signatures and not blocked or revoked by anyone.

Drivers signed by Microsoft

When opening one of the drivers in virus total we can see that its not detected by any AV or EDR vendor.

link: https://www.virustotal.com/gui/file/6fbaad2f00afaa94723fa7d5bd46e7ea4babb7ce478a8e7229ce7bd4b85e0f51/detection

Virus total scan of one of the drivers

Reverse Engineering

Now for the reverse engineering part, i opened Ida pro and loaded the driver in it, Ida pro was not able to decompile the main function (DriverEntry) in the driver.

This is a known IDA issue with drivers that use non-standard stack frames or that have had their entry point obfuscated. Rather than fight it, I skipped DriverEntry and went straight for the dispatch handler which is where the interesting logic lives anyway.

Decompilation failure in DriverEntry

The DeviceIoControlHandler function was decompiled but looked terrible, raw offsets for CurrentStackLocation fields, unnamed sub-functions, meaningless variable names.

DeviceIoControlHandler before fixing.

After fixing the types and names:

DeviceIoControlHandler after fixing.

We can see 2 IOCTL’s, the first i was too lazy to reverse but the second one (0x22E010) is leading to a function responsible for process killing i named procKiller.

When opening the procKiller decompilation code still it looks ugly and garbage, i had to fix it like i did before.

procKiller function before fixing
procKiller function after fixing

The procKiller function flow is as following:
1. The IOCTL input buffer is treated as a null-terminated ASCII string containing the decimal PID.

2. The driver calls atoi() on it, passes the integer to TerminateProcess.

3. Writes "ok" back to the output buffer on success. That’s the entire interface.

Inside the TerminateProcess is the true flow of killing the process:

TerminateProcess function

the TerminateProcess takes the PID and opens the process using ZwOpenProcess function to obtain a handle, then a call to ZwTerminateProcess using the obtained process handle.

This is why CrowdStrike (and other EDRs running as PPL) can be killed. In user mode, OpenProcess against a PPL process returns Access Denied. But from the kernel, ZwOpenProcess doesn’t care.

Now that we have reversed the killing part and identified the IOCTL responsable for process killing, we still miss one critical piece which is the driver’s symblolic link , without it we cant send process termination requests from usermode.

For the symbolic link hunting i decided to go for the dynamic approach which is loading the driver and seeing if any new symlinks pop up in WinObj and we were able to find the driver symbolic link:

Driver Symlink

Now we have all the pieces:

Driver symlink: \\.\{F8284233–48F4–4680-ADDD-F8284233}

Kill Process IOCTL : 0x22E010

Creating the POC

First i added all driver symlink and the ioctl as variables:

in the main function the flow is the following:

  1. Opens \\.\{F8284233–48F4–4680-ADDD-F8284233} via CreateFileW.
  2. Converts the PID to a decimal ASCII string.
  3. Sends it via DeviceIoControl with IOCTL 0x22E010.
  4. Process Terminated !!!
main function part 1
main function part 2

Now its time to test the POC against CrowdStrike:

First we load the driver using OSRLOADER.

you can also load the driver using sc.exe:

sc.exe create PoisonX binPath="C:\Path\to\Driver.sys" type=kernel
sc.exe start PoisonX
Loading the driver using OSRLOADER

Then we run the POC against crowdstrike.

Before running the POC
After running POC

For the POC and the drivers, i just published them on GitHub.
https://github.com/j3h4ck/PoisonKiller/

Comments are closed.