Introduction
During vulnerability research into kernel-mode drivers, we identified a signed anti-cheat driver shipped with a commercial game that exposes dangerous functionality to user-mode processes without adequate privilege checks. Kernel drivers operate at the highest privilege level on Windows, and anti-cheat systems leverage this power to protect games from tampering. However, when the driver's IOCTL interface is poorly secured, attackers can weaponize that same kernel-level access against the operating system itself.
This research demonstrates a Bring Your Own Vulnerable Driver (BYOVD) attack — a technique where attackers load a legitimately signed but vulnerable driver to gain kernel-level capabilities, bypassing security controls that are enforced only at the user-mode boundary.
Background
The driver exposes a device interface that allows a user-mode component to control its behavior through IOCTL (I/O Control) requests. It operates as part of an anti-cheat system where the privileged kernel component assists a companion user-mode client in enforcing process control. One supported command allows the user-mode component to specify a process identifier (PID), which the driver then uses to perform a process termination operation from kernel mode.
Anti-cheat drivers require kernel-level process termination capabilities to immediately stop unauthorized software from interacting with or modifying game memory. However, if this capability is exposed through an IOCTL handler without proper access validation, it becomes exploitable outside the intended anti-cheat context.
What Is PPL?
Protected Process Light (PPL) is a Windows security feature introduced in Windows 8.1. Processes marked as PPL — such as antivirus engines, LSASS, and certain system services — cannot be opened with full access rights from user-mode code, even by administrators. Any attempt to call OpenProcess or TerminateProcess against a PPL-protected process from user mode returns ACCESS_DENIED.
The critical detail: PPL is enforced at the user-mode boundary. Kernel-mode code operating with OBJ_KERNEL_HANDLE bypasses this check entirely.
IOCTL Enumeration
All IOCTLs use FILE_ANY_ACCESS (access bits 14–15 = 0b00), meaning the device handle requires no special privilege to obtain. The driver exposes 10 IOCTL codes:
| IOCTL | In | Out | Action |
|---|---|---|---|
0x222000 | 0 | 0 | Re-register Ps* kernel callbacks |
0x222004 | 0 | 0 | Unregister all kernel callbacks |
0x222008 | 0 | 1036 | Dequeue process entry (info leak) |
0x222018 | 1036 | 0 | Enqueue fake entry, add name to protected list |
0x22201C | 1036 | 0 | Kill process by PID via ZwTerminateProcess |
0x222020 | 1036 | 0 | Enqueue fake entry, flag=0 |
0x222024 | 8 | 0 | Set timeout + re-register callbacks |
0x222040 | 0 | 4 | Returns version DWORD (3) |
0x222044 | 8 | 0 | Register user event object |
0x222048 | 0 | 0 | Dereference registered event |
The Vulnerability: Arbitrary Process Termination
IOCTL 0x22201C allows termination of any process on the system, including PPL-protected processes.
Call Chain
DeviceIoControl
→ IRP_MJ_DEVICE_CONTROL Handler (0x140001540) [no privilege check]
→ IOCTL_0x22201C_TerminateFromList (0x14000264C)
└ reads target PID from first 4 bytes of 1036-byte input buffer
└ CRC32(name) → RemovePIDFromProtectedList
└ KillProcessByPID (0x140002848)
└ ZwOpenProcess(pid, PROCESS_ALL_ACCESS, OBJ_KERNEL_HANDLE)
└ ZwTerminateProcess(handle, 0)
The IOCTL dispatch routine accepts requests through IRP_MJ_DEVICE_CONTROL without performing any privilege validation. When the handler receives IOCTL 0x22201C, it forwards the supplied 1036-byte input buffer to an internal termination routine. The target PID is extracted from the first 4 bytes of the user-controlled buffer. The routine also performs a CRC32-based lookup and removal against the driver's internal protected process list before invoking KillProcessByPID.
KillProcessByPID opens the target process with ZwOpenProcess using PROCESS_ALL_ACCESS and OBJ_KERNEL_HANDLE, then immediately calls ZwTerminateProcess. Because these are kernel-mode calls with OBJ_KERNEL_HANDLE, PPL protection is bypassed entirely.
The result: antivirus engines, EDR agents, system services, and any PPL-protected process can be terminated by any user with access to the device handle.
Decompiled KillProcessByPID
__int64 __fastcall KillProcessByPID(unsigned int pid)
{
HANDLE ProcessHandle = 0;
OBJECT_ATTRIBUTES oa;
CLIENT_ID cid;
oa.Length = 48;
oa.Attributes = 514; // OBJ_KERNEL_HANDLE | OBJ_INHERIT
oa.RootDirectory = 0;
oa.ObjectName = 0;
cid.UniqueProcess = (HANDLE)pid;
cid.UniqueThread = 0;
NTSTATUS status = ZwOpenProcess(&ProcessHandle, 0x1FFFFF, // PROCESS_ALL_ACCESS
&oa, &cid);
if (NT_SUCCESS(status) && ProcessHandle) {
status = ZwTerminateProcess(ProcessHandle, 0);
ZwClose(ProcessHandle);
}
return status;
}
Input Buffer Layout
The IOCTL expects exactly 1036 bytes with the PID in the first 4 bytes:
#pragma pack(push, 1)
typedef struct {
DWORD pid; // offset 0 - target PID
WORD _pad; // offset 4
CHAR name[1030]; // offset 6 - CRC32'd for protected list removal
} KILL_INPUT;
#pragma pack(pop)
Proof of Concept: Disabling Windows Defender
To demonstrate real-world impact, we built a proof of concept targeting MsMpEng.exe (the Windows Defender antimalware service process). Because the Windows Service Control Manager (SCM) automatically restarts the WinDefend service on failure (default delay ~5 seconds), each restart spawns a new MsMpEng.exe with a different PID. The PoC addresses this by entering a loop that refreshes the target PID and re-terminates the process every 700ms, effectively keeping Defender disabled indefinitely.
This same technique applies to any AV/EDR agent running as a PPL-protected process.
Impact
- EDR/AV bypass: Terminate any endpoint security product, including PPL-protected processes that Windows is designed to shield from tampering
- Defense evasion: Clear the way for malware deployment by killing security monitoring before execution
- BYOVD weaponization: The driver is legitimately signed, meaning it loads without driver signature enforcement issues
- Persistence disruption: Continuously kill security processes faster than the SCM can restart them
- Low barrier: No special privileges required beyond the ability to load the driver and obtain a device handle
Defensive Recommendations
- Implement IOCTL access validation — verify the calling process identity and privilege level before processing sensitive commands
- Use driver block lists (Microsoft's Vulnerable Driver Blocklist) to prevent known-vulnerable signed drivers from loading
- Enable Hypervisor-Protected Code Integrity (HVCI) to restrict which drivers can load
- Monitor for suspicious driver loading events (Sysmon Event ID 6) in your SIEM
- Implement kernel callback protections that detect when security-critical callbacks are being unregistered
References
- ZeroMemoryEx/Blackout
- Windows Internals: Protected Processes
- OBJ_KERNEL_HANDLE documentation
- ZwOpenProcess / ZwTerminateProcess — Windows Driver Kit reference
Concerned about BYOVD or endpoint security bypass in your environment?
Redmount Cyber performs adversary simulation and red team assessments that test your defenses against real-world attack techniques.
Request an Assessment