Skip to content

Kernel Driver Analysis

Kernel drivers are where Windows security is decided: every callback an EDR registers (E2), every minifilter altitude, every handle-grant denial lives in ring 0, in .sys files you can enumerate, load, and — this is the uncomfortable part — legally sign and load yourself if you have admin. The modern attack surface is therefore double: analyzing security drivers to understand the telemetry architecture, and abusing legitimate-but-vulnerable drivers (BYOVD) as the pragmatic bridge into kernel privileges. This module covers the driver landscape, the signing regime that gates it, a real analysis workflow (static PE/COFF + IOCTL decoding + import fingerprinting), and the BYOVD phenomenon with its defensive counter — Microsoft’s vulnerable driver blocklist.

A kernel driver is a privileged kernel-mode module (.sys, PE/COFF) loaded via the service control manager or filter manager. Three families matter to security work:

FamilyRegisters withSecurity role
Security sensor driversPsSetCreateProcessNotifyRoutineEx, ObRegisterCallbacks, CmRegisterCallbackEx, FltRegisterFilterThe EDR kernel layer (E2)
General-purpose driversIRP dispatch tables (IRP_MJ_DEVICE_CONTROL)Attack surface: IOCTL handlers with user input
Vulnerable signed driversAnything — they’re legitimateBYOVD primitives: arbitrary kernel read/write
User process Kernel
──────────── ──────
CreateFile(\\.\Device) ───▶ I/O manager → driver's dispatch table
DeviceIoControl(h, IOCTL, ┌──────────────────────────────┐
inbuf, │ IRP_MJ_DEVICE_CONTROL → │
outbuf) ──▶│ IOCTL handler (validated?) │
│ ├─ METHOD_BUFFERED │
│ ├─ METHOD_IN_DIRECT │
│ ├─ METHOD_OUT_DIRECT │
│ └─ METHOD_NEITHER (raw!) │
└──────────────────────────────┘
IOCTL = (DeviceType << 16) | (Access << 14) | (Function << 2) | Method

The IOCTL code is a packed 32-bit value: device type, required access, function index, and transfer method — decodable with pure arithmetic, which is why IOCTL enumeration is the standard first step in driver attack-surface analysis.

For the defender: every telemetry layer from E2 is a driver, and drivers have their own vulnerabilities, load conditions, and tamper surfaces. A security program that never inventories drivers (what’s loaded, who signed it, what altitude, what IOCTLs) is defending a kernel it hasn’t mapped. For the operator: BYOVD is the pragmatic kernel-access path when exploits are out of reach — load a legitimately signed driver with a known arbitrary-write primitive, and the kernel is yours without violating any signature policy (until the blocklist). And for everyone: the signing regime — Microsoft cross-signing’s death, WHQL, attestation signing, HVCI — is the actual security boundary, and understanding why an old vulnerable driver still loads on some estates is a lesson in migration economics that no vendor page explains.

The analysis skills here are also the foundation for M8 (kernel rootkits): you cannot reason about callback-tampering rootkits without fluency in how callbacks register and where their dispatch tables live.

Driver loading is signature-gated, and the gates changed sharply over the years:

  • Legacy cross-signing (pre-2015 era, dead): vendors bought cross-certificates; thousands of vulnerable drivers were legitimately signed this way and still load on estates without HVCI.
  • WHQL / attestation (current): drivers submit to Microsoft for signing; attestation signing requires an EV certificate and Microsoft’s dashboard. A real signature means Microsoft vetted the submitter, not that the code is safe — hence the blocklist.
  • HVCI (Hypervisor-protected Code Integrity): when enabled, kernel memory is W^X and anything not explicitly allowed — including known-vulnerable drivers — cannot load. This is the structural fix, and its absence is why BYOVD works anywhere [verify estate-specific posture].

The BYOVD pattern follows: pick a signed driver with a published arbitrary kernel read/write (the community catalogs are real and well-maintained — names like gdrv.sys (Gigabyte, CVE-2018-19320), RTCore64.sys (MSI Afterburner), DBUtil_2_3.sys (Dell, CVE-2021-21551) are the canonical examples), drop it, create its service (sc create ... binPath= ... type= kernel), open its device, and drive the IOCTL primitive. Every step emits telemetry (service install 7045, driver load Sysmon 6, blocklist events where HVCI/blocklist is active) — which is why BYOVD is best understood as a loud path whose noise defenders must be instrumented to catch.

Given an unknown .sys, the analysis order that pays:

  1. PE/COFF basics — sections, timestamps, imports: does it import the security-callback APIs (PsSetCreateProcessNotifyRoutineEx, ObRegisterCallbacks) → sensor-class; does it import MmMapIoSpace/ZwOpenProcess sets → primitive-class [verify heuristic boundaries].
  2. Dispatch table — locate DriverObject->MajorFunction initialization; count and characterize handlers (IRP_MJ_DEVICE_CONTROL present → userland interface exists).
  3. IOCTL enumeration — brute-decode every 0xNNNNNNNN constant or scan for CTL_CODE-shaped immediates in code; each decodes to device/access/function/method.
  4. Method assessment — METHOD_NEITHER handlers that skip ProbeForRead/Write are the classic vulnerability class; METHOD_BUFFERED with unchecked SystemBuffer sizes are the second.
  5. String/registry hints — device names (\Device\<name>), default SDDL strings (who can open the device — D:P(A;;GA;;;WD) = everyone = pre-auth userland attack surface).
Why METHOD_NEITHER dominates driver CVE lists

METHOD_NEITHER hands the driver raw user-mode pointers. Any handler that touches Irp->UserBuffer or the input SystemBuffer without ProbeForRead/ProbeForWrite (and without wrapping in try/except) can be pointed at kernel memory by a user with nothing but DeviceIoControl. Combined with SeCreateAccess-permissive SDDLs, this single pattern explains a large share of historical LPE drivers [verify: heuristic claim, not a census]. It is also the easiest class to grep for statically: find the IOCTL handler, look for the probe calls’ absence — which is exactly what this module’s tooling automates.

The blue-team loop: enumerate loaded drivers (driverquery /v, fltmc filters for altitudes, WinDbg lm t n on dumps), verify signers (Authenticode on each .sys), diff against baseline, and cross-check known-vulnerable catalogs. Registry autologger and service persistence checks catch BYOVD drops; Sysmon EID 6 (DriverLoad with signature state) is the streaming version of the same. Altitude knowledge (E1’s 328010 reference, the FSFilter activity band) tells you where in the I/O stack a filter sits — and therefore what it can and cannot see.

Analysis techniqueWhat it revealsTool classNotes
Import fingerprintingSensor vs primitive driver classPE parserCallback APIs = sensor; mem APIs = primitive
IOCTL decodeUserland attack surfaceStatic + dynamicCTL_CODE arithmetic
SDDL string extractionWho can open the deviceStrings/parseWD = everyone
Dispatch-table mappingHandler coverage, gapsDisassemblyMissing probes = CVE candidates
Altitude lookup (minifilters)I/O stack positionfltmc filtersFSFilter activity band ordering
Signature verificationLoad eligibilitysigcheck-classAttestation vs cross-signed history
Blocklist cross-checkBYOVD eligibilityMS catalog [verify update cadence]HVCI estates: moot

ioctlscan.py — a complete static attack-surface analyzer for .sys files: PE parsing, import fingerprinting (sensor vs primitive classification), brute IOCTL decode from immediate constants, SDDL/device-name extraction, and a verdict report. Pure stdlib; this is the analysis pipeline of the module, runnable.

{1-6}
"""
ioctlscan.py — static attack-surface analyzer for Windows drivers.
Parses a .sys file: imports (sensor/primitive fingerprint), IOCTL
constants (CTL_CODE decode), device/SDDL strings, verdict report.
Pure stdlib. Python 3.8+.
"""
import struct
import sys
from pathlib import Path
# ---- PE/COFF core (same pattern as E3/E8; self-contained) --------
def sections_of(data: bytes):
e = struct.unpack_from("<I", data, 0x3C)[0]
nsec = struct.unpack_from("<H", data, e + 6)[0]
opt = struct.unpack_from("<H", data, e + 20)[0]
s0 = e + 24 + opt
out = []
for i in range(nsec):
o = s0 + i * 40
name = data[o:o + 8].rstrip(b"\x00").decode(errors="replace")
vsize, vaddr, rsize, raddr = struct.unpack_from("<IIII", data, o + 8)
out.append((name, vaddr, vsize, raddr, rsize))
return out
def rva_to_off(data: bytes, rva: int) -> int:
for name, vaddr, vsize, raddr, rsize in sections_of(data):
if vaddr <= rva < vaddr + max(vsize, rsize):
return raddr + (rva - vaddr)
raise ValueError(f"unmapped rva {rva:#x}")
def imports_of(data: bytes) -> list:
e = struct.unpack_from("<I", data, 0x3C)[0]
opt = e + 24
magic = struct.unpack_from("<H", data, opt)[0]
dd = opt + (96 if magic == 0x10B else 112)
imp_rva = struct.unpack_from("<I", data, dd + 8)[0]
if not imp_rva:
return []
funcs = []
off = rva_to_off(data, imp_rva)
while True:
oft, _t, _f, name_rva, first_thunk = struct.unpack_from("<IIIII",
data, off)
if name_rva == 0:
break
dll = data[rva_to_off(data, name_rva):]
dll = dll[:dll.index(b"\x00")].decode(errors="replace")
thunk = oft or first_thunk
while True:
val = struct.unpack_from("<Q", data,
rva_to_off(data, thunk))[0]
if val == 0:
break
if val & (1 << 63): # import by ordinal
funcs.append(f"{dll}!ordinal_{val & 0xFFFF}")
else:
hint = struct.unpack_from("<H", data,
rva_to_off(data, val & 0xFFFF))[0]
p = rva_to_off(data, (val & 0xFFFF) + 2)
fname = data[p:data.index(b"\x00", p)].decode(
errors="replace")
funcs.append(f"{dll}!{fname}")
thunk += 8
off += 20
return funcs
# ---- fingerprinting -----------------------------------------------
SENSOR_APIS = ["PsSetCreateProcessNotifyRoutine", "ObRegisterCallbacks",
"CmRegisterCallback", "FltRegisterFilter",
"PsSetLoadImageNotifyRoutine"]
PRIMITIVE_APIS = ["MmMapIoSpace", "ZwOpenProcess", "ZwOpenKey",
"MmCopyVirtualMemory", "KeStackAttachProcess",
"MmGetSystemRoutineAddress"]
def fingerprint(imports: list) -> str:
s = [a for a in SENSOR_APIS if any(a in i for i in imports)]
p = [a for a in PRIMITIVE_APIS if any(a in i for i in imports)]
if len(s) >= 2:
return f"SENSOR-class (callbacks: {len(s)})"
if len(p) >= 2:
return f"PRIMITIVE-class (mem/key APIs: {len(p)})"
return f"mixed/other (sensor={len(s)}, primitive={len(p)})"
# ---- IOCTL decode --------------------------------------------------
def decode_ioctl(v: int) -> dict:
return {"device_type": (v >> 16) & 0xFFFF,
"access": (v >> 14) & 0x3,
"function": (v >> 2) & 0xFFF,
"method": v & 0x3} # 0=BUFFERED 1=IN_DIRECT 2=OUT_DIRECT 3=NEITHER
def brute_ioctls(data: bytes) -> list:
"""Scan .text for plausible CTL_CODE immediates."""
found = []
for _, vaddr, vsize, raddr, rsize in sections_of(data):
if rsize == 0:
continue
blob = data[raddr:raddr + rsize]
for i in range(0, len(blob) - 4):
v = struct.unpack_from("<I", blob, i)[0]
dev = (v >> 16) & 0xFFFF
fn = (v >> 2) & 0xFFF
method = v & 3
# plausible: custom device types (0x8000+), sane function range
if 0x8000 <= dev <= 0x8FFF and 0x800 <= fn <= 0x9FF:
found.append((v, decode_ioctl(v)))
return found
# ---- strings: devices + SDDL ---------------------------------------
def interesting_strings(data: bytes) -> dict:
text = data.decode("latin-1")
out = {"devices": set(), "sddl": set()}
for tok in text.split("\x00"):
if tok.startswith("\\Device\\") and len(tok) < 64:
out["devices"].add(tok)
if tok.startswith("D:") and ("GA" in tok or "GR" in tok):
out["sddl"].add(tok[:80])
return {k: sorted(v) for k, v in out.items()}
def analyze(path: str) -> dict:
data = Path(path).read_bytes()
imports = imports_of(data)
rep = {
"file": path,
"imports": len(imports),
"fingerprint": fingerprint(imports),
"ioctls": brute_ioctls(data)[:12],
"devices": interesting_strings(data)["devices"],
"sddl": interesting_strings(data)["sddl"],
}
return rep
if __name__ == "__main__":
target = sys.argv[1] if len(sys.argv) > 1 else \
"C:/Windows/System32/drivers/WdFilter.sys"
r = analyze(target)
print(f"=== {r['file']} ===")
print(f"imports: {r['imports']} fingerprint: {r['fingerprint']}")
print(f"plausible IOCTLs: {len(r['ioctls'])}")
for v, d in r["ioctls"][:8]:
print(f" 0x{v:08X} dev={d['device_type']:#06x} "
f"acc={d['access']} fn={d['function']:#05x} "
f"method={['BUF','IN_DIR','OUT_DIR','NEITHER'][d['method']]}")
print(f"devices: {r['devices'][:5]}")
print(f"sddl : {r['sddl'][:3]}")
  • imports_of (lines 24–53) — full x64 import-table walk: image import descriptors → original first thunk array → hint/name RVAs; ordinal imports flagged by the high bit. This is the fingerprinting data source and the third time this PE-parser pattern appears in the series (E3, E8, now drivers) — by now it should feel like muscle memory.
  • fingerprint (lines 60–69) — the classification heuristic: two-plus security-callback APIs = sensor-class; two-plus memory/registry primitives = primitive-class. Honest limits: drivers that do both exist; the label is a triage hint, not a verdict.
  • brute_ioctls (lines 82–96) — scans executable sections for 32-bit immediates shaped like CTL_CODE with custom device types (0x8000–0x8FFF) and function indices in the common custom range (0x800–0x9FF). False positives are expected (that’s why it’s “plausible”); the decode makes each candidate readable for manual review.
  • decode_ioctl (lines 72–79) — the packed-bit arithmetic: device type, access, function, transfer method — with the method ladder (NEITHER = highest interest) flagged in the printout.
  • interesting_strings (lines 101–110) — device paths and SDDL strings from the binary. An SDDL granting GA (generic all) to WD (World/Everyone) on a primitive-class driver is the classic pre-auth surface finding.

The BYOVD chain end-to-end: operator identifies estate posture (HVCI on? blocklist current? → game over; legacy estate → continue) → drops the vulnerable signed driver to disk → sc create + sc start (service install telemetry) → opens \\.\Device\<name> → drives the arbitrary kernel read/write IOCTL primitive → disables or corrupts the security drivers’ callbacks (the M8 boundary), reads kernel memory, or escalates a token. Every arrow emits telemetry: file drop, service install (7045), driver load (Sysmon 6 with signer), device opens, then the effects — which is why the defender answer is posture (HVCI), inventory (baseline + blocklist cross-check), and behavioral alerting on the load chain, in that order.

  1. Posture recon — query HVCI/device-guard state and service list to confirm the estate tolerates unsigned-primitive loading [verify query surfaces per build]
  2. Select — choose a signed driver with a published kernel R/W primitive from community catalogs (gdrv.sys / RTCore64.sys / DBUtil_2_3.sys class)
  3. Drop — write the .sys to disk; file-write telemetry (Sysmon 11) fires here
  4. Load — sc create + sc start: Event 7045 + Sysmon 6 with signature; blocklist-checked where active
  5. Open — CreateFile on the device; SDDL decides success; ioctlscan-style recon mapped the IOCTL set beforehand
  6. Primitive — DeviceIoControl the arbitrary R/W IOCTL: kernel memory is now readable/writable
  7. Objective — escalate token, tamper callbacks (M8 territory), or map sensor internals; every kernel-side action is the defender’s highest-value alert surface

Goal: drvmap.py — the operational wrapper around ioctlscan.py: a driver inventory + attack-surface mapper for a host you own. It enumerates loaded drivers, runs the ioctlscan analysis per file, classifies sensor/primitive, flags everyone-accessible devices, and produces the inventory report defenders actually baseline with. Pure stdlib, ~150 lines, read-only.

Design decisions: (1) inventory-from-disk (driverquery output cross-referenced to files) — no kernel introspection, fully userland; (2) reuses the ioctlscan engine as an import (composition over duplication — the same rule as the vendor world: engine separate from tool); (3) report keyed on risk hints (primitive + permissive SDDL + blocklist-candidate names) rather than a fake CVE database.

{1-3}
"""
drvmap.py — loaded-driver inventory + attack-surface mapper.
Enumerates loaded drivers, runs ioctlscan analysis on each, and
produces a baseline report with risk hints.
Usage: python drvmap.py [--dir C:/Windows/System32/drivers] [--json out]
Read-only. Windows host. Pure stdlib.
"""
import argparse
import csv
import io
import json
import subprocess
import sys
from pathlib import Path
from ioctlscan import analyze
BLOCKLIST_CANDIDATES = ("gdrv", "rtcore", "dbutil", "drvloader",
"ams.sys", "procexp", "process lasso")
# name fragments seen in community BYOVD catalogs [verify against the
# current Microsoft blocklist — it changes; this is a hint set only]
def loaded_drivers() -> list:
"""driverquery /v /fo csv -> rows"""
out = subprocess.run("driverquery /v /fo csv", capture_output=True,
text=True, timeout=60, shell=True).stdout
rows = list(csv.reader(io.StringIO(out)))
if not rows:
return []
hdr = [h.strip().lower() for h in rows[0]]
idx = {k: i for i, k in enumerate(hdr)}
out_rows = []
for r in rows[1:]:
if len(r) < len(hdr):
continue
out_rows.append({
"module": r[idx.get("module name", 0)],
"state": r[idx["state"]] if "state" in idx else "?",
"path": r[idx["path"]] if "path" in idx else "",
})
return out_rows
def risk_hints(rep: dict) -> list:
hints = []
fp = rep.get("fingerprint", "")
if "PRIMITIVE" in fp:
hints.append("primitive-class APIs imported")
for sddl in rep.get("sddl", []):
if "WD" in sddl and "GA" in sddl:
hints.append(f"permissive SDDL: {sddl}")
for dev in rep.get("devices", []):
if dev.endswith("}") or "DosDevices" in dev:
hints.append(f"device: {dev}")
return hints
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--dir", default="C:/Windows/System32/drivers")
ap.add_argument("--json", dest="out")
args = ap.parse_args()
drivers = loaded_drivers()
print(f"loaded drivers: {len(drivers)}")
analyzed = flagged = 0
report = []
for d in drivers:
path = d["path"]
if not path or not Path(path).exists():
continue
low = Path(path).name.lower()
if any(c in low for c in BLOCKLIST_CANDIDATES):
print(f" [!] BLOCKLIST-CANDIDATE: {path}")
flagged += 1
try:
rep = analyze(path)
except Exception as e:
continue
analyzed += 1
hints = risk_hints(rep)
if "PRIMITIVE" in rep["fingerprint"] or hints:
print(f" [?] {Path(path).name:<28} "
f"{rep['fingerprint']}")
for h in hints[:3]:
print(f" - {h}")
flagged += 1
report.append({"path": path, "fingerprint": rep["fingerprint"],
"hints": hints, "ioctls": len(rep["ioctls"])})
print(f"\nanalyzed: {analyzed} flagged: {flagged}")
if args.out:
json.dump(report, open(args.out, "w"), indent=2)
print(f"report -> {args.out}")
return 0
if __name__ == "__main__":
sys.exit(main())
  • loaded_drivers (lines 24–40) — CSV-parses driverquery /v /fo csv into module/state/path records; the inventory source of truth without any kernel API.
  • BLOCKLIST_CANDIDATES (lines 18–21) — name fragments from community catalogs, explicitly documented as hints against the live Microsoft blocklist [verify current list]. The design point: the tool never claims “vulnerable” — it flags candidates for the human’s cross-check, which is the honest division of labor for a static tool.
  • risk_hints (lines 43–52) — the scoring heuristic: primitive-class imports, permissive SDDLs (GA to WD), and exposed devices. Each hint maps to a finding class from the Detailed Explanation table.
  • main (lines 55–90) — inventory → per-file analysis → flag printout → JSON baseline. Two passes over a big System32\drivers directory take seconds; schedule it, diff it, and you have drift detection for free.
PS> python drvmap.py --dir C:\Windows\System32\drivers --json baseline.json
loaded drivers: 214
[?] WdFilter.sys SENSOR-class (callbacks: 3)
[?] SomeVendor.sys PRIMITIVE-class (mem/key APIs: 3)
- permissive SDDL: D:P(A;;GA;;;WD)
- device: \Device\SomeVendor
analyzed: 214 flagged: 6
report -> baseline.json
# drift workflow: run weekly; diff baseline.json; investigate new PRIMITIVE-class
# entries and every BLOCKLIST-CANDIDATE flag against the live MS blocklist.

Validation homework: run on a clean VM, snapshot the baseline; then install Sysmon (its driver appears as sensor-class), then — in a disposable VM with snapshots and for research purposes only — drop a known catalog driver and watch the blocklist-candidate flag and the full telemetry chain (11 → 7045 → 6) fire in order. That chain is the BYOVD detection story in one experiment.

drvmap.py itself is benign inventory tooling; its abusive cousin (driver inventory before BYOVD) is E2’s discovery pattern again. The BYOVD chain is the detection target: Sysmon EID 11 (drop) → 7045 (service) → EID 6 (load, with signature state) in minutes on one host is the alert regardless of tooling. Posture controls: enable HVCI where the estate tolerates it, keep the vulnerable-driver blocklist current (it updates via Windows Update [verify cadence]), and alert on blocklist blocks — a blocked BYOVD attempt is a high-fidelity “someone is trying” signal, arguably more valuable than a successful one.

  1. Signature verification per file (Authenticode via ctypes WinVerifyTrust) — closes the loop on “signed by whom”.
  2. Dispatch-table disassembly: port a minimal x86-64 linear disassembler for the MajorFunction region and flag METHOD_NEITHER handlers missing ProbeFor* — the real CVE-finder, still userland.
  3. Blocklist sync: fetch and cache the current Microsoft recommended blocklist [verify distribution format] and replace the name-fragment hint set.
  4. Altitude mapping for minifilters: parse FLTFILTER registry metadata to report I/O-stack position per filter.
  5. Historical diff: store baselines per machine in git and emit a weekly drift email — boring, effective, deployable tomorrow.
SourceIDWhat firesNotes
Sysmon6DriverLoad with signature stateTHE driver telemetry; baseline signers
Sysmon11FileCreate of dropped .sysChain start
Windows Security7045New service installed (kernel type)BYOVD load step
Windows Security4697Service install (Security log variant)Join with 7045
Microsoft DefenderVulnerable driver blocklistLoad blocked (HVCI/WDAC estates)A block is a high-fidelity alert
Sysmon13Registry value sets under service keysPersistence of driver services
Wazuh7045/6-based rules [verify IDs vs ruleset]Chain correlationInventory + drift jobs
ETWMicrosoft-Windows-CodeIntegrity [verify channel]Signature enforcement eventsHVCI estates’ ground truth
  • Posture first: HVCI-enabled estates end most BYOVD conversations before they start; reconnaissance of that state is the difference between a plan and a crash.
  • Pick drivers that still load: the blocklist only binds where deployed; catalog freshness is the operator’s responsibility — stale lists burn load attempts and leave telemetry.
  • The load chain is the noise: drop → service → load is three telemetry points; nothing here is “quiet,” so plan the analysis around a short dwell and snapshot-free (test-estate) execution.
  • Signed ≠ safe, and defenders know it: expect blocklist cross-checks and prevalence baselining; a signed driver seen once on one host is more suspicious than an unsigned admin tool seen everywhere.
  • Do not tamper callbacks from userland assumptions: callback removal is kernel work (M8); the primitive gives you the capability — the detection question is what you do in the five minutes after.
  • Inventory tools are not stealthy when chained: driverquery + reg + file reads in one session is sensor-mapping recon (E2); batch and blend, or accept the discovery alert.

⚠️ What caught me: in a lab exercise I loaded a catalog driver without checking the estate’s HVCI state — the load succeeded (driver service running) but every primitive IOCTL returned STATUS_ACCESS_DENIED because Code Integrity had neutered the signed-but-blocklisted image after load. I burned an hour debugging IOCTLs when the answer was in posture enumeration I skipped. The lesson generalizes: verify the enforcement state, not just the presence of the mechanism you’re abusing — loaded and enforceable are different words.

  1. Run ioctlscan.py against WdFilter.sys (sensor-class baseline) and any primitive-class driver in your lab; compare import fingerprints and IOCTL ladders until the classification feels obvious.
  2. Run drvmap.py, save the baseline, install Sysmon, re-run, and diff — your first driver drift detection.
  3. Build the BYOVD telemetry chain in a disposable VM: drop → service-create → load a harmless catalog-listed driver (one you have rights to test), and observe EID 11 → 7045 → 6 sequencing; then write the Sigma/Wazuh correlation for that chain.
  4. Decode lab: pick three IOCTL constants from your brute_ioctls output and hand-decode them with the CTL_CODE arithmetic; verify against the tool’s output — the arithmetic must be reflex-level before you touch M8.
  5. HVCI lab: enable HVCI/memory integrity on a test VM [verify your VM’s compatibility], retry the same load, and document exactly which step now fails — the structural answer to BYOVD, observed.

Technical: Decode CTL_CODE(0x8000, 0x830, METHOD_NEITHER, FILE_ANY_ACCESS) into its packed 32-bit value and name which transfer method class dominates driver CVEs — and why. Technical: What is the difference between WHQL, attestation signing, and legacy cross-signing — and why does the dead one still matter in 2026? Deep-dive: You find a primitive-class driver with a permissive SDDL and a METHOD_NEITHER handler. Design the full static→dynamic validation path for the suspected missing-probe bug, naming each checkpoint. Conceptual: “A blocked BYOVD attempt is more valuable than a successful one” — defend or attack this claim from the SOC’s perspective. Scenario: An estate cannot enable HVCI this year. Prioritize the three highest-value compensating controls for the BYOVD threat, with rationale. Practical: Write the pseudo-Sigma for the drop→service→load chain with the false-positive controls you would tune first.

E2 (EDR Internal Architecture) · M8 (Kernel Rootkit Analysis) · H4 (JTAG/SWD Analysis) · BT15 (Firmware Security)