Kernel objects and IRPs
TL;DR: Drivers expose functionality through
DEVICE_OBJECTs; user mode sends them I/O Request Packets (IRPs). TheIRP_MJ_DEVICE_CONTROLmajor function backed byDeviceIoControlis the dominant attack surface.
What it is
The Windows I/O Manager translates every CreateFile/ReadFile/WriteFile/DeviceIoControl call against a device into an IRP — a heap-allocated structure describing the operation, the caller’s buffers, and a stack of per-driver locations. Each driver’s DriverObject->MajorFunction[] array points to handlers for each IRP major code. Vulnerability research lives almost entirely inside those handlers, particularly IRP_MJ_DEVICE_CONTROL, where IOCTL codes carry a method (METHOD_BUFFERED, METHOD_IN_DIRECT, METHOD_OUT_DIRECT, METHOD_NEITHER) that dictates how buffers are passed.
Preconditions / where it applies
- Driver
IoCreateDevice+IoCreateSymbolicLinkexposing a\\.\Nameaccessible to the caller’s integrity level - DACL on the device permits the caller’s token; many drivers default to
World/Everyonewhich is the LPE jackpot - IOCTL codes enumerable via static analysis (
CTL_CODEmacro pattern) or runtime hooks
Technique
IOCTL code structure (CTL_CODE(DeviceType, Function, Method, Access)):
1
#define IOCTL_FOO CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
Method behaviour and bug-class implications:
- METHOD_BUFFERED — I/O manager copies user buffer into a kernel pool; driver reads/writes through
Irp->AssociatedIrp.SystemBuffer. Safest, but pool overflow risk if size is mis-handled. - METHOD_IN_DIRECT / OUT_DIRECT — user buffer locked into an MDL; driver gets
Irp->MdlAddress. Race/pin issues. - METHOD_NEITHER — raw user pointers in
Type3InputBufferandUserBuffer. Every read/write must useProbeForRead/ProbeForWriteand SEH; missing probes give arbitrary kernel R/W. Major source of LPE bugs.
Attacker workflow:
- Open the device:
CreateFileW(L"\\\\.\\HEVD", GENERIC_ALL, ...) - Discover IOCTL codes via reverse engineering or fuzzing — see fuzzing-windows-drivers
- Send
DeviceIoControl(hDev, IOCTL, inbuf, inlen, outbuf, outlen, ...) - Observe bug-class: pool overflow, type-confusion-kernel, use-after-free-kernel, double-fetch, uninitialised-memory-disclosures, kernel-stack-overflow
Key structures researchers learn cold:
1
2
3
4
5
DRIVER_OBJECT.MajorFunction[] — dispatch table
DEVICE_OBJECT — links to driver, attached stack
IRP — request packet
IO_STACK_LOCATION — per-driver state slot inside IRP
IRP_MJ_* — major codes (CREATE, CLOSE, DEVICE_CONTROL, READ, WRITE, CLEANUP, ...)
!devstack, !drvobj /a, !irp in WinDbg make these inspectable (see kernel-debugging-with-windbg).
Detection and defence
- Probe every user buffer once into a local kernel copy; never re-read user memory across operations — see double-fetch
- Restrict device DACLs (
IoCreateDeviceSecure,SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_R); deny non-admin where possible - Validate
InputBufferLength/OutputBufferLengthagainst your struct sizes and array bounds - Driver Verifier with IRP/IOCTL options and Microsoft Pool Quotas catches many bug classes before shipping
References
- Microsoft: Introduction to IRPs — official IRP guide
- Pavel Yosifovich, Windows Kernel Programming — driver internals reference
- HEVD project — seeded IOCTL bugs to practise on
Related: fuzzing-windows-drivers, double-fetch, type-confusion-kernel, kernel-debugging-with-windbg