ioctlance: windows driver vulnerability detection
on this page
overview
ioctlance - enhanced fork of the vulnerability detection tool for windows driver model (wdm) drivers using symbolic execution and taint analysis.
- detects 13 types of kernel vulnerabilities (original had ~11)
- original teamt5 version found 117 vulnerabilities resulting in 41 cves
- this enhanced version finds additional vulnerabilities the original missed
- 25-35% faster than original implementation
- plugin-based architecture for extensibility
- discovers critical buffer overflows with controllable pc (rce)
background
the problem
windows kernel drivers operate with highest privileges. vulnerabilities enable:
- privilege escalation to system/kernel
- arbitrary physical memory access
- kernel code execution
- byovd (bring your own vulnerable driver) attacks
notable examples:
- rtcore64.sys: used in blackbyte ransomware
- hw.sys: candiru spyware rootkit installation
- amdpowerprofile.sys: cve-2023-20562 arbitrary memory access
symbolic execution approach
ioctlance uses angr to:
- find ioctl handler: trace driverentry to locate irp_mj_device_control
- create symbolic inputs: make user buffers symbolic variables
- explore execution paths: analyze all possible code paths
- detect vulnerabilities: monitor dangerous api calls and memory access
enhanced detection capabilities
detector comparison
original ioctlance (~11 detectors):
- map physical memory (mmmapiosspace, zwmapviewofsection)
- controllable process handle (zwopenprocess, obopenobjectbypointer)
- read/write controllable address
- null pointer dereference
- arbitrary shellcode execution
- dangerous file operations
- arbitrary process termination
- basic buffer overflow (memcpy size check)
- arbitrary wrmsr (cpu instruction)
- arbitrary out (i/o port)
- dest/src controllable (memcpy/memmove)
enhanced version (13 plugin detectors + cpu hooks):
newly added detectors (6):
- use after free - detects access to freed memory
- double free - catches multiple free operations
- race conditions - toctou vulnerabilities
- integer overflow - arithmetic vulnerabilities
- format string - format string bugs
- probe bypass - detects actual probeforread/write bypasses (vs just tracking)
enhanced detectors:
- stack buffer overflow - now detects controllable pc/rce (vs basic size check)
- process termination - merged with process handle abuse detection
recently restored from original:
- ObOpenObjectByPointer - process handle manipulation for privilege escalation
- dest/src controllable - arbitrary read/write via controlled memcpy addresses
new vulnerabilities found
example: ilp60x64_3.sys analysis
- original ioctlance: 19 vulnerabilities (read/write primitives)
- enhanced version: 35 vulnerabilities total
- includes all 19 original detections
- plus 10 critical buffer overflows with controllable pc
- plus 6 additional read/write vulnerabilities
why original missed these:
# original: stopped at initial vulnerability
simgr = proj.factory.simgr(state) # save_unconstrained=False
# enhanced: continues to find exploitation paths
simgr = proj.factory.simgr(state, save_unconstrained=True)
if state.regs.pc.symbolic: # detects control flow hijack
report_buffer_overflow_rce()
the difference: original finds “unlocked door” (write primitive), enhanced proves “can take over building” (rce).
vulnerability types detected
memory vulnerabilities
- physical memory mapping: mmmapiosspace with controllable parameters
- arbitrary read/write: user controls memory addresses
- null pointer dereference: unvalidated null pointers
- buffer overflow: stack and heap overflows
- use after free: accessing freed memory
control flow vulnerabilities
- arbitrary code execution: controllable function pointers
- arbitrary wrmsr: write to model-specific registers
- arbitrary out: i/o port operations
access control vulnerabilities
- process handle abuse: missing obj_force_access_check
- dangerous file operations: privileged file access
- process termination: unauthorized process killing
architecture
plugin-based detectors
from ioctlance.detectors import VulnerabilityDetector
class PhysicalMemoryDetector(VulnerabilityDetector):
name = "physical_memory_mapping"
def check_state(self, state, event_type, **kwargs):
# check if user controls physical address
if self._is_tainted(physical_address):
return self.create_vulnerability_info(
title="Arbitrary Physical Memory Mapping",
description="User controls MmMapIoSpace parameters",
state=state
)
modular hook system
# hooks organized by subsystem
hooks/
├── memory.py # allocation and mapping
├── process.py # process management
├── file_ops.py # file operations
├── kernel.py # core kernel apis
└── opcodes.py # cpu instructions
usage
command line
# analyze single driver
uv run python -m ioctlance.cli driver.sys \
--timeout 120 \
--output results.json \
--verbose
# analyze directory of drivers
uv run python -m ioctlance.cli ./drivers/ \
--timeout 60 \
--batch
rest api
# start api server
uv run uvicorn ioctlance.api:app --host 0.0.0.0
# upload and analyze
curl -X POST http://localhost:8080/analyze \
-F "file=@driver.sys" \
-F "timeout=120"
docker
# docker-compose.yml
services:
ioctlance-api:
image: ioctlance:latest
ports:
- '8080:8080'
volumes:
- ./drivers:/drivers
results format
{
"driver": {
"path": "RTCore64.sys",
"size": 36864,
"arch": "amd64"
},
"vulnerabilities": [
{
"title": "Arbitrary Physical Memory Mapping",
"description": "User controls address in MmMapIoSpace",
"eval": {
"IoControlCode": "0x80002048",
"SystemBuffer": "0x41414141",
"InputBufferLength": "0x8"
},
"raw_data": {
"constraints": [...],
"memory": {...},
"registers": {...}
}
}
]
}
performance optimizations
improvements over original
- lru caching: frequently called functions cached
- pre-compiled regex: 25-35% speedup for pattern matching
- data structures: frozensets for o(1) tainted buffer checks
- state deduplication: avoid analyzing duplicate states
benchmarks
metric | original | refactored |
---|---|---|
analysis speed | baseline | +25-35% |
memory usage | ~500mb | ~400mb |
detectors | 7 | 13 |
test coverage | 0% | 48% |
testing approach
real driver validation
// test_physical_memory.c - minimal vulnerable driver
NTSTATUS IoctlHandler(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
if (stack->Parameters.DeviceIoControl.IoControlCode == 0x800020C0) {
PHYSICAL_ADDRESS PhysAddr;
// vulnerability: user controls physical address
RtlCopyMemory(&PhysAddr, Irp->AssociatedIrp.SystemBuffer,
sizeof(PHYSICAL_ADDRESS));
MmMapIoSpace(PhysAddr, PAGE_SIZE, MmNonCached);
}
return STATUS_SUCCESS;
}
compile test drivers:
# build with mingw-w64
x86_64-w64-mingw32-gcc -shared -nostdlib \
-I/usr/share/mingw-w64/include/ddk \
-o test_driver.sys test_driver.c \
-Wl,--subsystem,native -Wl,--entry,DriverEntry
limitations
false positives
sources of false positives:
- try-except blocks not modeled
- indirect validation checks
- administrator-only access paths
- kernel-only operations
analysis constraints
- timeout limits exploration depth
- path explosion in complex drivers
- indirect jumps may be missed
- some anti-analysis techniques work
references
original work
- original ioctlance by teamt5 - found 117 vulnerabilities, 41 cves
- code blue 2023 presentation - “enhanced vulnerability hunting in wdm drivers”
this enhanced fork
- mjbommar/ioctlance - refactored version with improved detection
- discovers vulnerabilities original missed (buffer overflows with rce)
- 104 vulnerable driver test dataset included
frameworks and tools
related tools
- cab-fuzz: coverage-guided kernel fuzzing
- diode: directed fuzzing for drivers
- screwdrivers: symbolic execution approach
- popkorn: previous taint analysis tool
ioctlance improves on these by combining symbolic execution with comprehensive taint analysis and addressing path explosion through optimized state management.