kernel-dev
Use when designing, writing, reviewing, debugging, or hardening Windows WDM/KMDF drivers, IRP and IOCTL paths, PnP/power lifecycles, queues, cancellation, kernel memory, callbacks, or version-pinned r
SKILL.mdskills/kernel-dev/SKILL.md
GitHubKernel development workflow
Activation
Use when the task involves kernel-mode driver development, kernel shellcode, manual mapping, injection, rootkit techniques, or any ring-0 code.
Project scaffolding
Default stack: C or C++ with the WDK. Prefer KMDF for new PnP device and queue work; use WDM when a required contract is genuinely below the framework. Keep private-layout research isolated from the supported driver path.
<project>/
├── src/
│ ├── driver.c # DriverEntry / EvtDriverDeviceAdd
│ ├── device.c # device creation, ACL, interfaces
│ ├── queue.c # dispatch, cancellation, completion
│ ├── ioctl.c # versioned boundary validation
│ ├── pnp-power.c # start/stop/remove and D-state ownership
│ ├── trace.c # WPP/IFR diagnostics
│ └── util.c # checked arithmetic and owned helpers
├── include/ # public request ABI + internal contracts
├── tests/ # malformed IOCTL and lifecycle harnesses
├── research/ # optional, build-pinned private-layout work
├── package/ # INF, catalog, signing inputs
├── <project>.vcxproj
└── README.md # support matrix and teardown contract
Coding conventions
- Link documented WDK imports normally. Use
MmGetSystemRoutineAddressonly for optional exported APIs with an explicit OS-version fallback; pattern-scanned or unexported routines are unsupported, version-pinned research dependencies. - Give every allocation a unique, searchable
POOL_TAG; never reuse another driver's tag to disguise ownership. - Compile out
DbgPrintin release builds, but retain WPP/IFR diagnostics suitable for crash correlation. - Prefer
ExAllocatePool2with NXPOOL_FLAG_NON_PAGEDorPOOL_FLAG_PAGED; executable pool requires a documented design reason and a compatible HVCI policy. - Annotate IRQL and ownership with SAL (
_IRQL_requires_max_,_Must_inspect_result_,_Post_writable_byte_size_) and build with/W4 /WXplus Code Analysis for Drivers. - Teardown every queue, callback registration, work item, timer, allocation, interface, symbolic link, and handle through the same ownership path that created it.
Correctness before stealth
A supported driver must survive malformed requests, concurrent removal, low-resource injection, Driver Verifier, and HVCI before any version-pinned research technique is considered. Prefer KMDF for new device/queue code; choose WDM only when the required contract is not represented by the framework.
IOCTL boundary contract
Define access deliberately; FILE_ANY_ACCESS is not a harmless default:
#define IOCTL_LAB_QUERY CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, \
METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA)
Secure the device with an INF security descriptor, IoCreateDeviceSecure, or WdfDeviceInitAssignSDDLString. Validate the caller's granted handle access as well as request contents.
| Transfer method | Buffer source | Required handling |
|---|---|---|
METHOD_BUFFERED |
Irp->AssociatedIrp.SystemBuffer |
Check both stack lengths, initialize every output byte, set IoStatus.Information to bytes actually written. |
METHOD_IN_DIRECT / METHOD_OUT_DIRECT |
Small input in SystemBuffer, second buffer described by Irp->MdlAddress |
Validate direction and length; map with MmGetSystemAddressForMdlSafe(..., NormalPagePriority | MdlMappingNoExecute) and handle NULL. |
METHOD_NEITHER |
Type3InputBuffer / Irp->UserBuffer |
Only probe inside __try/__except in the original requestor context at allowed IRQL, copy into owned kernel memory immediately, and never queue raw user pointers. |
In KMDF use WdfRequestRetrieveInputBuffer, WdfRequestRetrieveOutputBuffer, and WdfRequestCompleteWithInformation; the framework still does not validate semantic fields, integer arithmetic, nested offsets, or versioned request headers. Gate lengths before pointer addition and use checked arithmetic such as RtlULongLongAdd.
IRQL and resource ownership
| Context | Allowed work | Forbidden shortcuts |
|---|---|---|
PASSIVE_LEVEL |
pageable code, registry/file Zw calls, waits, device setup/teardown | holding spin locks across calls or attaching indefinitely |
APC_LEVEL or below |
documented memory-manager operations that state this limit | touching pageable user buffers after context changes |
DISPATCH_LEVEL |
DPC-safe nonpaged state, spin locks, completion enqueue | blocking, pageable code/data, registry/file I/O, user probing |
| ISR/DIRQL | acknowledge hardware, capture minimal state, queue DPC | allocation, complex parsing, waits, or request completion policy |
Pair every reference with a release in the same state machine: ObReferenceObject/ObDereferenceObject, MDL lock/unlock/free, remove-lock acquire/release, WDF object parentage, rundown acquire/release, and IRP ownership. Use EX_RUNDOWN_REF for callbacks that race teardown and IO_REMOVE_LOCK in WDM PnP paths. Use work items for PASSIVE-only work; a DPC is not a generic worker thread.
Driver lifecycle
WDM path
DriverEntryinitializes immutable state, dispatch entries (IRP_MJ_CREATE,IRP_MJ_CLOSE,IRP_MJ_CLEANUP,IRP_MJ_DEVICE_CONTROL,IRP_MJ_PNP, andIRP_MJ_POWER),DriverUnloadwhere legal, andAddDevicefor PnP drivers.AddDevicecreates the FDO, attaches withIoAttachDeviceToDeviceStackSafe, initializesIO_REMOVE_LOCK, clearsDO_DEVICE_INITIALIZING, and unwinds every partial failure in reverse order.IRP_MN_START_DEVICEacquires translated resources only after the lower stack completes. Do not accept I/O until start succeeds.- Every dispatch validates
IO_STACK_LOCATION, acquires the remove lock, sets a cancel-safe ownership state, forwards or completes the IRP exactly once, and releases the lock on the matching completion path. - Use
IoCsqInitializeor a framework queue for cancellable IRPs. The cancel routine, worker, timeout, and cleanup path must have one atomic winner; fixed sleeps are not synchronization. - Handle query-stop/remove, stop, surprise-removal, and remove distinctly. On remove, reject new I/O, drain with
IoReleaseRemoveLockAndWait, detach, delete links/interfaces, then delete the device. - Forward power/PnP IRPs according to the WDK contract; never complete an IRP both locally and in a completion routine.
KMDF path
DriverEntry -> WdfDriverCreate -> EvtDriverDeviceAdd -> WdfDeviceCreate -> WdfIoQueueCreate. Put hardware transitions in EvtDevicePrepareHardware/EvtDeviceReleaseHardware, power transitions in D0 callbacks, request work in typed EvtIo* callbacks, cancellation in EvtRequestCancel, and per-object teardown in EvtCleanupCallback/EvtDestroyCallback. Once a request is forwarded or completed, the driver no longer owns it unless the documented API returns ownership.
Build, signing, and test matrix
msbuild .\driver.sln /m /p:Configuration=Release /p:Platform=x64
InfVerif.exe /w .\package\driver.inf
signtool.exe verify /kp /v .\package\driver.sys
pnputil.exe /add-driver .\package\driver.inf /install
verifier.exe /standard /driver driver.sys
verifier.exe /querysettings
Run only in a disposable KD-enabled VM. Include checked/debug versus release, x64 versus ARM64 when supported, HVCI off/on, normal versus low-resource/special-pool verifier, start/stop/remove/surprise-remove, malformed IOCTL corpus, cancellation at each ownership edge, and repeated load/unload. Test-signing is a lab boot-policy choice; it is not production signing.
On a crash preserve the dump, exact SYS/PDB identity, verifier settings, and these KD views before changing code:
!analyze -v
!verifier 3 driver.sys
!irp <address>
!locks
!pool <address>
!wdfkd.wdfdevicequeues <WDFDEVICE>
Version-pinned research techniques
Research classification
These topics preserve the skill's ring-0 research coverage, but they are not supported driver architecture. Pin the exact Windows build, module hashes, PDB identity, offsets, VBS/HVCI state, and rollback snapshot. Treat every unexported routine, pattern-derived address, or internal layout as invalid after an update until re-proven.
APC delivery and thread context
MmAllocateContiguousMemoryreturns kernel virtual memory backed by contiguous physical pages; it does not allocate target-process user memory.- A documented
ZwAllocateVirtualMemorypath requires a process handle with the right access and strict requestor/context handling. Prefer a cooperative user-mode component for legitimate instrumentation; never retain an attach across waits or calls into unknown code. PsSuspendThread,PsGetContextThread, andPsResumeThreadare not a supported WDK thread-hijack contract. A lab that studies them must resolve and validate each build independently, account for WOW64/CET state, restore context on every exit, and cannot ship this path as production code.- Internal
KAPClayout and queue behavior are version-sensitive. Prove target-thread lifetime, APC environment, delivery conditions, cancellation, and allocation ownership before claiming a result.
VAD, callbacks, DKOM, and SSDT
MMVAD,EPROCESS,ETHREAD, token fast references, callback storage, and service tables are private layouts. Direct edits can violate reference counts, locks, PatchGuard, HVCI, and concurrent enumerators even when one debugger observation looks correct.- A driver unregisters only registrations it owns, using the matching cookie/handle and documented API:
PsSetCreateProcessNotifyRoutineEx(..., TRUE),ObUnRegisterCallbacks,CmUnRegisterCallback,FltUnregisterFilter, and corresponding thread/image APIs. Do not clear callback arrays or unlink filter globals. - DKOM unlinking and token replacement are forensic experiments, not lifecycle mechanisms. Record every invariant broken and restore the original list links/fast-reference semantics from a snapshot rather than trusting unload.
- SSDT pointer edits,
CR0.WPmanipulation, and NXE changes are unsupported on x64 and conflict with PatchGuard/HVCI. Use documented filter/callback interfaces for drivers; route a genuine VMM/EPT experiment tohypervisor-devorhyper-v-offensive.
Dynamic resolution
UNICODE_STRING name = RTL_CONSTANT_STRING(L"ExAllocatePool2");
PVOID optional = MmGetSystemRoutineAddress(&name);
Use this only for an exported API with a documented older-build fallback. Pattern scanning an unexported function requires static uniqueness, runtime boundary checks, fail-closed behavior, and a build-specific test matrix.
Routing
- Batch A:
bof-coff-development,windows-rpc-com-attack,windows-telemetry-etw, andhyper-v-offensive. - Batch B:
linux-kernel-exploitation,c2-implant-engineering,ebpf-offensive, andlinux-host-post-exploitation. - Use
windows-internalsfor build-specific object/I/O context,driver-commfor IOCTL protocol design,kernel-callbacksfor callback ownership, andwindows-driver-0dayfor a confirmed driver vulnerability. - Use
byovdfor third-party vulnerable-driver operations,exploit-devfor a proved primitive, andhypervisor-devfor a custom VMM boundary.
Verification checklist
- Documented versus unexported/version-pinned dependencies are explicit
- Device ACL and every IOCTL method/access/length contract are tested
- PnP, power, cancellation, removal, and partial-failure unwind paths are covered
- IRQL, request, reference, MDL, allocation, and callback ownership are balanced
- NX pool, SAL,
/W4 /WX, Code Analysis, signing, and HVCI results are recorded - Driver Verifier passes malformed-I/O and repeated lifecycle tests
- KD crash triage and exact SYS/PDB/build evidence are preserved