Automated BYOVD hunting pipeline. Scans Windows kernel drivers for dangerous imports, extracts IOCTL dispatch surfaces, cross-references against LOLDrivers / MS Blocklist / KDU, and surfaces novel zero-day candidates not yet in any public database.
Python 3.10+, Windows. MIT-licensed.
pip install git+https://github.com/diabloidyobane/DriverScope.git
pip install capstone # optional, better IOCTL extraction
pip install speakeasy-emulator # optional, driver emulation via Speakeasy# scan your system drivers
driverscope scan C:\Windows\System32\drivers --lol --ioctl
# full zero-day hunt
driverscope hunt --deep --export findings.json
# extract IOCTLs from a specific driver
driverscope ioctl suspicious.sys --jsonActual output from C:\Windows\System32\drivers on a Windows 11 host:
$ driverscope scan C:\Windows\System32\drivers
SCAN RESULTS: 423 flagged / 463 total (40 clean)
dxgkrnl.sys 14 x64 YES PhysMem-Map, MSR, PCI-Config, Token-Priv +10
cldflt.sys 10 x64 CrossProc-Attach, PhysMem-Map, Process-Lookup +7
storport.sys 10 x64 YES PhysMem-Map, PhysMem-Section, MDL +7
acpi.sys 9 x64 YES MSR, PCI-Config, PhysMem-Map +6
ntfs.sys 9 x64 YES PhysMem-Map, PhysMem-Unmap, Token-Priv +6
tcpip.sys 9 x64 YES Callback-Bypass, CrossProc-Attach +7
... 417 more
$ driverscope ioctl bam.sys
bam.sys
SHA256: dcf689b7...a5e314c3
Method: capstone
Dispatcher RVA: 0x11920
IOCTLs found: 2
0x00000004 METHOD_BUFFERED
-> IoThreadToProcess
0x00000003 METHOD_NEITHER
-> ExAcquirePushLockExclusiveEx
-> KeEnterCriticalRegion
$ driverscope ioctl acpi.sys
acpi.sys
Method: brute
IOCTLs found: 34
0xfffc4e95 METHOD_IN_DIRECT
0xfffc4e94 METHOD_BUFFERED
0xfffc4eb3 METHOD_NEITHER
0xfffc4ca2 METHOD_OUT_DIRECT
... 30 more
Every flagged import maps to a kernel primitive that BYOVD attacks exploit:
| Class | Example Import |
|---|---|
| PhysMem-Map | MmMapIoSpace |
| PhysMem-Unmap | MmUnmapIoSpace |
| PhysMem-Section | ZwMapViewOfSection |
| PhysMem-Copy | MmCopyMemory |
| CrossProc-VA | ZwReadVirtualMemory |
| CrossProc-Attach | KeStackAttachProcess |
| Process-Lookup | PsLookupProcessByPid |
| CR-Regs | __readcr0, __writecr0 |
| MSR | __readmsr, __writemsr |
| Debug-Regs | __readdr |
| KernelAlloc | ExAllocatePoolWithTag |
| KernelExec | MmAllocateContiguous |
| I/O-Port | READ_PORT_UCHAR |
| PCI-Config | HalGetBusData |
| Interrupt | HalSetSystemInformation |
| Registry | ZwSetValueKey |
| Token-Priv | SePrivilegeCheck |
| Callback-Bypass | CmUnRegisterCallback |
| Command | What it does |
|---|---|
scan |
Scan .sys files for dangerous kernel imports |
ioctl |
Extract IOCTL dispatch surface from a driver |
emulate |
Speakeasy emulation: trace DriverEntry, extract device names, PDB paths, debug strings |
hunt |
Full-system zero-day hunting pipeline |
harvest |
Download OEM tools and extract embedded drivers |
regional |
Search LOLDrivers by regional vendor (CN/KR/JP/TW/RU) |
wdm |
Filter for WDM drivers with physmem primitives |
bulk |
Bulk-scrape vendor download portals via Playwright |
triage |
Bulk Claude API triage of scan/ioctl findings |
driverscope scan driver.sys # scan one file
driverscope scan C:\drivers --lol --blocklist # scan dir + cross-ref
driverscope scan C:\drivers --ioctl # scan + extract IOCTLs
driverscope scan C:\drivers --ioctl --json # full JSON output
driverscope scan C:\drivers --export out.json # write results to file
driverscope hunt # zero-day hunt (System32\drivers)
driverscope hunt --deep --export hits.json # include DriverStore + Program Files
driverscope ioctl driver.sys # extract IOCTL codes (single file)
driverscope ioctl C:\drivers --hits-only # batch directory, skip empty
driverscope emulate driver.sys # trace DriverEntry via Speakeasy
driverscope emulate C:\drivers --json # batch emulate a directory
driverscope harvest --output ./harvested --scan # download OEM tools, extract + scan
driverscope regional --region CN,JP # LOLDrivers by vendor region
driverscope wdm C:\drivers # WDM-only physmem filterThe bulk subcommand uses Playwright to scrape vendor download portals at scale. 55 vendors across 10 regions: TW, HK, CN, KR, JP, RU, DE, US, IN, and multi-vendor global archives. Use it to build a corpus of vendor-signed drivers far outside what's already in LOLDrivers.
| Region | Vendors |
|---|---|
| TW (10) | MSI, ASRock, Gigabyte, Asus, Acer, Biostar, ECS, Realtek, Foxconn-TW, PowerColor |
| HK (2) | ZOTAC, Sapphire |
| CN (10) | Lenovo, Huawei, Xiaomi, Colorful, Yeston, Galax, Onda, Foxconn-CN, ZTE, MAXSUN |
| KR (3) | Samsung, LG, GIGABYTE-KR |
| JP (7) | Buffalo, IO-Data, Elecom, Logitec-JP, Sony, NEC, Panasonic |
| RU (5) | DriverPack, Driver.ru, DRP-Catalog, 4PDA, Yandex |
| DE (4) | BeQuiet, Endorfy, Fujitsu, Medion |
| US (7) | EVGA, XFX, Dell, HP, Intel-DSA, AMD, Nvidia |
| IN (1) | iBall |
| global (6) | Station-Drivers, MS Update Catalog, DriverGuide, TechSpot, CNET, MajorGeeks |
pip install driverscope[bulk]
playwright install chromium
driverscope bulk --list # 55 vendor targets, see above
driverscope bulk --region CN,KR,RU --scan # crawl China + Korea + Russia, scan results
driverscope bulk --region JP --category laptop # Japanese laptop vendors only
driverscope bulk --vendors DriverPack-RU,4PDA-Files # RU aggregator focus
driverscope bulk --category gpu --output ./gpu_corpus
driverscope bulk --max-pages 10 # deep crawl, all vendorsOutput goes to <output>/<vendor>/<file>. Each vendor runs in parallel under a concurrency cap. Downloads cap at 200MB per file and skip files that already exist on disk (idempotent re-runs).
Why regional matters for BYOVD hunting: CN, KR, JP, and RU vendors ship signed drivers that rarely appear in English-language security research. Many never reach LOLDrivers because nobody English-speaking has looked. The same goes for OEM laptop manufacturers' bundled telemetry/overclock/fan-control drivers — signed, broad install base, often built by third-party contractors with no security review.
After scan/ioctl extraction, pipe the JSON output through triage to get per-IOCTL verdicts from Claude:
pip install driverscope[triage]
export ANTHROPIC_API_KEY=sk-ant-...
driverscope scan ./corpus --ioctl --json --export findings.json
driverscope triage findings.json --output triage.mdEach finding produces:
IOCTL 0x80102040 CONFIRMED-PRIMITIVE MmMapIoSpace exposed with no caller check
IOCTL 0x80102044 LIKELY-PRIMITIVE PhysMem write reachable; bounds check is weak
IOCTL 0x80102048 GATED Guarded by process-name allowlist
OVERALL: CONFIRMED-PRIMITIVE Driver exposes arbitrary physical R/W via two IOCTLs
Triage runs concurrently (default 4 in flight). Use --concurrency 8 if you have API capacity. Default model: claude-opus-4-6.
The emulate subcommand uses Mandiant Speakeasy to emulate DriverEntry without loading the driver. It traces kernel API calls, extracts device names, PDB paths, debug strings, and classifies primitives from runtime behavior that static import scanning alone can't reach.
pip install driverscope[emulate]
driverscope emulate driver.sys # single driver
driverscope emulate C:\drivers --json # batch directory
driverscope emulate a.sys b.sys c.sys --export results.jsonReal output from 4 drivers (0.6s total wall-clock):
# Driver Device EPs Crash PDB Primitives
-- ---------------------------- --------------------- --- ----- -------------------- ----------
1 GlobalVistaVentures_v3.sys GlobalVistaVentures 6 Kinkajou CR-Regs, CrossProc-VA, PageTable-Walk, VAD-Inject +4
2 signeddrv.sys 5 Windows-Memory-Info CrossProc-VA, KernelMem-Copy, PhysMem-Map
3 RTCore64.sys RTCore64 5 PhysMem-Map, PhysMem-Section
4 PawnIO.sys PawnIO 15 4 PawnIO_unsigned MSR-RW, PCI-Config, PhysMem-Direct, VirtMem-RW +1
What emulation finds that static import scanning doesn't:
- Full driver lifecycle: traces DriverEntry through IRP_MJ_CREATE, IRP_MJ_DEVICE_CONTROL, IRP_MJ_CLOSE, and DriverUnload, with API calls and return values per phase
- BSOD crash detection: PawnIO.sys has 4 crash sites where exported unregister functions dereference null at
[rdx + 8](invalid_read = kernel bugcheck on real hardware) - IoCreateDriver resolution: drivers that create hidden driver objects via
IoCreateDriverinstead of normalIoCreateDeviceregistration are auto-resolved using Capstone to extract the MajorFunction table from the initialization callback - PDB paths: reveal original project names and build infrastructure (Jenkins CI paths, dev machine directories)
- Debug strings: expose the full capability set in plain English (PawnIO's 37 named R/W operations, GlobalVistaVentures' VAD manipulation + page table walking)
- Device names: signeddrv uses
\Device\WinNotifyand references\Driver\MouClass(mouse input interception), invisible to import-only analysis - Runtime imports: drivers that resolve APIs via
MmGetSystemRoutineAddressinstead of the import table (e.g. resolvingIoCreateDeviceSecureat runtime instead of linking it)
Emulation runs at ~100ms per driver. It complements the scan and ioctl subcommands: scan catches the import table, ioctl maps the dispatch surface, emulate reveals everything the developer left in the binary's strings and initialization path.
export VT_API_KEY=your_key
driverscope scan C:\drivers --vtCached locally (30-day TTL), auto-throttles to free tier.
- Collect .sys files from system dirs or custom paths
- Parse PE imports, classify into 18 primitive categories
- Filter out drivers already in LOLDrivers, MS Blocklist, or KDU
- Filter out MS inbox drivers
- Score remaining by primitive weights + signed/x64/IOCTL bonuses
- Report top candidates with SHA256, signer, device names, IOCTLs
scanner.py: PE import scanner, VT/LOLDrivers/Blocklist integrationioctl.py: static IOCTL dispatch extraction (Capstone + bytescan)emulate.py: Speakeasy driver emulation, DriverEntry tracing, string/PDB/device extractionhunter.py: zero-day pipeline with novelty scoringharvester.py: OEM tool downloader + .sys extractorregional.py: regional vendor search (CN/KR/JP/TW/RU)wdm_filter.py: WDM vs KMDF filterkdu.py: KDU RMDX database parser
DriverScope surfaces candidates but doesn't reason about them. A useful workflow is to dump the JSON output (--json on any subcommand) along with the relevant decompilation, and ask Claude Opus 4.6 (or any equivalent reasoning model) to triage:
- "Does this IOCTL expose a write-what-where, or is it gated by a check I'm missing?"
- "Compare these handler imports to known LOLDrivers patterns. Is this technique novel?"
- "What's the exploit chain if I can call IOCTL
0xfffc4e94onacpi.sys?"
Claude Opus 4.6 handles long decompilation context well and is the default this was tested with.
If you're doing legitimate vulnerability research and Claude's safety filters trip on a prompt (kernel exploit chains, BYOVD primitives, etc.), Anthropic has a Cyber Verification Program for safeguard adjustment:
If you are experiencing cyber blocks on what you believe to be a legitimate use case under our Usage Policy, fill out this form to apply for safeguards adjustment under our Cyber Verification Program. (Organizations on Zero Data Retention are not currently eligible. Contact your Anthropic Sales Representative.)
Getting accepted is hard. In practice, the program is gated toward established orgs, vendors, and well-known names. Independent researchers (including ones with published CVEs) have been turned down after multiple attempts. If you're in that bucket, your options:
- Apply anyway. It costs nothing and policy may change.
- Affiliate with a security org, vendor, or research lab that already has access.
- Work the parts that don't trip safety filters (static triage, primitive classification, IOCTL surface mapping). Keep exploit-chain reasoning local or with a different toolchain.
Noted up front so you don't burn a week assuming access.
Either way: DriverScope finds the surface, the model helps you decide what's real.
Once DriverScope hands you a list of IOCTL codes, you still need to call them from user-mode to confirm they reach a primitive. The pattern here matches commercial driver SDKs and security-research test rigs:
- One C++ header per target driver (
*_comm.h) CTL_CODE(...)macro per IOCTL for self-documenting code, not raw hex- One
structper IOCTL's input layout, close to the call site. When you discover the layout is{phys, size, virt}instead of{addr, len, _}, you change it in one place. - A wrapper class with RAII handle cleanup and one typed method per IOCTL
- A generic
Invoke()for sweeping unknown codes
# 1. dump IOCTL surface
driverscope ioctl driver.sys --json > findings.json
# 2. seed the CTL_CODE macros from JSON
python examples/gen_comm_header.py findings.json > driver_comm.h
# 3. build the tester against your filled-in header
cl /EHsc examples/ioctl_tester.cpp # MSVC
g++ examples/ioctl_tester.cpp -o ioctl_tester.exe -lstdc++ -static # MinGWThen edit driver_comm.h to fill in the parts DriverScope can't guess:
DEVICE_PATH: DriverScope reportsdevice_nameswhen it can recover them. For stripped or runtime-created devices, look it up with WinObj,objdir, or by readingIoCreateSymbolicLinkin IDA.- Per-IOCTL request structs: DriverScope tells you which kernel APIs each handler imports (
MmMapIoSpace,READ_PORT_UCHAR, etc.) so you know what kind of primitive it is. The exact struct the handler reads from the input buffer is on you to reverse. Common shapes for reference:
| Primitive | Typical input struct |
|---|---|
| PhysMem-Map | { uint64 phys_addr; uint32 size; uint64 mapped_out; } |
| PhysMem-Copy | { uint64 phys_src; uint64 size; uint8 out[N]; } |
| MSR | { uint32 msr_index; uint64 value; } |
| I/O-Port | { uint16 port; uint8 width; uint32 value; } |
| CR-Regs | { uint32 reg_index; uint64 value; } |
| PCI-Config | { uint8 bus; uint8 dev; uint8 fn; uint16 offset; uint32 value; } |
| File | What it is |
|---|---|
examples/comm_template.h |
C++ header pattern: per-IOCTL structs plus ExampleDriver class with Open(), typed primitive wrappers (ReadPhysical, ReadMsr, Probe), generic Invoke() |
examples/ioctl_tester.cpp |
Three modes: structured sweep (validate each typed primitive against known-good inputs like BIOS shadow + EFER read), raw sweep (try N codes from a base), single call |
examples/gen_comm_header.py |
Generates CTL_CODE macros from driverscope ioctl --json output |
The structured sweep in ioctl_tester.cpp shows the pattern for confirming a primitive exists rather than just that the IOCTL code is defined:
- PhysMem-Map: ask the handler to copy out bytes from
0x000F0000(BIOS shadow region). On most hardware that's readable, so non-zero bytes returned means the handler walked physical memory. - MSR: read EFER (
0xC0000080) and check that bit 11 (NXE) is set. NXE has been on since XP SP2, so a zero return means the handler either didn'trdmsror is lying. - Probe: send a magic cookie and check the handler echoes back a status field. Confirms the handler reaches
IoCompleteRequestwith your buffer.
Add one of these per primitive class you find. The point: distinguish "IOCTL code is wired" from "IOCTL code reaches a real primitive". Easy to confuse in static analysis alone.
Every DeviceIoControl call hits ring 0. A bad struct layout against a physmem or MSR driver will BSOD the host. Test in a VM with a snapshot you can roll back, never on a host you care about.
DriverScope's static IOCTL extractor handles most drivers. Complex dispatchers (deeply nested, obfuscated, or virtualized) benefit from IDA Pro's full analysis. Use re-mcp-ida in headless mode:
# start IDA headless with the MCP plugin on a driver
idat64 -A -S"ida_mcp_server.py --port 13337" -L"ida.log" driver.sys
# then point DriverScope at the IOCTL surface
driverscope ioctl driver.sys --jsonCombine DriverScope's automated primitive classification with IDA's decompiler output to confirm what each IOCTL handler does before filing a report.
If you find a novel vulnerable driver: report to the vendor and MSRC before publishing. Request addition to the MS Blocklist. Submit to LOLDrivers after vendor response.
If DriverScope saved you time or surfaced a useful finding, you can support development:
- @jsacco: DriverBuddy Revolutions, which informed parts of the IOCTL/dispatcher detection approach
MIT