Why Kernel-Mode EDR
Modern adversaries have largely stopped writing malware to disk. Reflective DLL injection, process hollowing, manual mapping, module stomping, and raw shellcode staged in private memory all share one defining trait: they never produce a file for a scanner to catch.
That shift has forced defenders to move their sensors deeper into the operating system - into a place where user-mode tricks can't reach. Kernel-mode EDR drivers run inside the Windows executive itself, observing process creation, image loads, and thread starts from what we call the source context: a vantage point above the reach of an attacker holding nothing more than administrator credentials.
The answer, as this post documents, is partially - and the gap is architectural, not a bug waiting on better engineering.
Driver Foundations & Lifecycle
A kernel-mode driver is a living extension of the operating system, and its lifecycle determines whether it improves or destabilizes the host. Inside DriverEntry, the architect performs four duties: assign the DriverUnload routine, register kernel callbacks, initialize synchronization primitives, and create device objects for user-mode communication.
The RegistryPath parameter is more than a formality - it's a stable, file-system-independent place to persist configuration, policy, and audit logs, which matters when the EDR itself may be the thing inspecting the file system.
Three WDK headers are foundational: ntddk.h for basic kernel-mode declarations, ntifs.h for file/section object structures, and ntimage.h for the PE structure definitions used throughout header parsing. For development-time visibility, route telemetry through DbgPrintEx on the DPFLTR_IHVDRIVER_ID channel - conventionally reserved for hardware vendor drivers, so it stays quiet under normal OS noise and supports per-component filtering in DebugView.
Process Creation & Static PE Inspection
PsSetCreateProcessNotifyRoutineEx is where meaningful detection starts. Use the Ex variant only - the legacy routine doesn't deliver enough context (parent PID, command line, underlying file object) for modern detection logic.
CommandLine is arguably the single most valuable field for catching Living-off-the-Land attacks, where adversaries abuse signed binaries like cmd.exe or powershell.exe - the binary is legitimate, the arguments are malicious. CreationStatus can veto process creation outright, but production deployments typically disable veto by default: a bad policy that blocks legitimate children of svchost.exe renders the host unusable in a way that looks a lot like malware to an end user.
Process names lie, though - which is why static inspection matters. Opening a file from kernel mode means always setting OBJ_KERNEL_HANDLE and using ZwCreateFile rather than NtCreateFile, which correctly sets the previous-mode field to KernelMode.
MZ-header triage isn't just "look for the bytes M and Z" - it's three checks, in order: confirm the read returned at least sizeof(IMAGE_DOS_HEADER) bytes, confirm the magic bytes, and confirm e_lfanew falls within the bytes actually read. Skip any one and the next parsing stage inherits a buffer overrun - which in kernel mode is a Bug Check, not a graceful segfault.
Section Tables & the Import RVA Trap
Two of the sharpest static signals available to a kernel EDR - and one of the most common mistakes made while building it.
A section's Characteristics bitmask can mark it both writable and executable at once - a violation of the principle that memory should be one or the other, never both. Legitimate compiled code almost never does this; packers, self-decrypting loaders, and shellcode stagers do it constantly, because they need to write their real payload into memory before executing it.
Section names add a second tell: .text, .data, .rdata are standard. .UPX0, .aspack, .vmp0 signal packing - not inherently malicious, but a strong prior that static content no longer represents runtime behavior.
In a normally compiled binary, AddressOfEntryPoint falls within .text, near its start, in a section marked executable but not writable. Three deviations are suspicious: an entry point inside .data or .rsrc; an entry point in a section that's both writable and executable; or an entry point that falls within no section at all.
PE import tables reference everything by relative virtual address (RVA) - offsets relative to the image base after the loader maps it. On disk, sections sit at file offsets that don't correspond to those RVAs at all. Treat a disk-buffer offset as an in-memory offset and you'll dereference into kernel pool guard pages and crash the system.
The architectural fix: don't parse imports from disk. Register PsSetLoadImageNotifyRoutine instead and inspect imports from the mapped image, where RVAs are already valid. This also unlocks comparing the static import list against the dynamic load list - a DLL present at runtime but never statically declared means something bypassed the loader's normal binding machinery entirely, which is one of the most reliable signals a kernel EDR has.
Synchronization & Target-Mode Filtering
Kernel callbacks fire on whatever thread happens to be inside the executive at that moment - on a busy multi-core host, that means callbacks can run simultaneously across every core. Any shared state is subject to concurrent mutation from an effectively unbounded number of writers.
For the typical EDR critical section, FAST_MUTEX is the right primitive. But a naive implementation - acquire the mutex, do all the work including PE parsing, release - breaks two ways: PE parsing calls ZwCreateFile/ZwReadFile, which can take milliseconds and serialize every process creation on the host; and parsing may trigger another callback in the same driver, producing a recursive deadlock that Bug Checks the system.
Acquire FAST_MUTEX, touch only the shared process table, set a DoParse flag if deeper inspection is warranted, release immediately.
If DoParse is true, run the heavyweight parsing routine with no lock held - any writes are reserved during the locked phase and committed in a brief second acquisition.
Bounded lock hold time, no recursive deadlock, and clean composition with deferring heavy work to a system worker thread.
Global-mode telemetry doesn't scale well either: a typical desktop creates 2,000-4,000 processes a day, each with 40-60 image loads, producing hundreds of thousands of events per host - a volume where the marginal value of more data approaches zero. Target mode keeps every callback registered but filters which ones generate telemetry via a predicate consulting image name, parent image, command line, or user SID - concentrating analyst attention on a coherent intent graph while accepting that anything outside the target set is invisible.
Event-Driven Memory Scanning
Everything above builds a high-fidelity picture of what processes exist and what they loaded - none of it observes the runtime mutation of process memory, which is exactly where in-memory execution lives.
Periodic scanning - walking every process on a fixed timer - is a poor fit: average detection latency is half the scan interval, far too slow for a payload that executes and exits in milliseconds. Event-driven scanning triggers off thread creation instead, since a new thread is created every time injected code is about to run, whether via CreateRemoteThread, NtCreateThreadEx, or a queued APC.
The high-confidence signal is a Private memory region transitioning from writable to executable - PAGE_READWRITE to PAGE_EXECUTE_READ. Legitimate code lives in Image regions, backed by a mapped file section. Private regions are normally data. After excluding allowlisted JIT compilers, the residual population of RW→RX transitions in private memory is dominated by reflective loaders, manual mappers, and shellcode stagers.
Section X - Event-Based Memory ScanningImplementation matters: the scan must run without holding any FAST_MUTEX, since walking a VAD tree may touch operations other callbacks need. The natural design defers the scan from the thread-creation callback to a system worker thread via IoQueueWorkItem, so the callback itself just queues the work and returns immediately - keeping thread creation responsive.
Three Architectural Ceilings
This is the part vendors are least eager to say out loud: no purely kernel-resident sensor can fully detect in-memory execution. Not because of a bug - because of the architecture itself.
The PE parser is the sharpest tool available against malicious files - and completely blind to shellcode delivered over the network and decoded straight into a private memory region of an already-running process. It's not a flaw in the parser; it's a property of the threat model itself.
Process creation fires once. Image load fires once per image. Thread creation fires once per thread. Between those events, an attacker can freely allocate, write, decrypt, relocate, and re-protect memory without generating a single callback. A protection transition that happens and reverts between scans - protection laundering - is invisible by construction, and adversaries have built payloads that deliberately race the thread-creation scanner.
Every scan has a cost, and on busy server workloads that cost shows up as latency operators notice. Scan everything in full detail and the sensor eventually gets configured down or uninstalled. Any scan that's skipped is a scan an adversary can exploit - the architect is always choosing where to spend a finite budget, and every choice leaves some path uncovered.
Reading about these ceilings is one thing. Knowing whether your specific EDR deployment actually has them - and whether your SOC would catch an attacker exploiting them - is another question entirely, and it's not one a vendor datasheet can answer.
Want to find out? Talk to ISECURION's Red Team practice →Closing the Gap: Beyond the Kernel Callback
What made this architecture instructive is that none of its three ceilings can be closed by writing a smarter kernel scanner. The gap is layered across sources the kernel callback set was never designed to cover.
The ETW Threat-Intelligence provider, introduced in Windows 10, exposes high-fidelity events for memory operations that no callback surfaces - subscribing to it turns the EDR from a snapshot scanner into a streaming observer of memory mutation, eliminating the protection-laundering window. AMSI covers a different blind spot: script-based execution staged as text rather than bytes, seen just before interpretation. Minimal user-mode telemetry stubs add operation-level granularity that kernel callbacks don't carry - and while the stub itself is bypassable by an attacker with code execution in the same process, that bypass is itself detectable when corroborated against the kernel's view that the stub's own memory was modified.
All of this has to operate under two hard constraints. PatchGuard kills any EDR that achieves visibility by inline-hooking syscall handlers or patching the SSDT, within minutes. HVCI goes further - no page can be both writable and executable, no dynamic code generation, no unsigned drivers. Every pattern in this post is compatible with those constraints by design; anything relying on shadow page tables or runtime code synthesis is not.
Key Takeaway
A well-built kernel EDR covers a genuinely wide range of malicious behavior - and it still isn't the whole picture.
Code that never touches disk is invisible to the parser. Protection transitions that revert between scans are invisible to the scanner. The gap between thread creation and first execution is a window some adversaries already know how to race.
These are architectural limits of kernel-resident sensing, not bugs to be patched. The practical path forward isn't a smarter kernel scanner - it's a layered sensor, with kernel callbacks as the spine and ETW-TI, AMSI, and minimal user-mode stubs closing the memory and script blind spots no single layer can cover alone.
Building or Auditing a Kernel-Mode EDR?
ISECURION works with security teams and vendors on Windows kernel driver security, EDR architecture review, and detection-engineering assessments across India and internationally.
Talk to Our Research Team More InsightsFrequently Asked Questions: Kernel-Mode EDR & In-Memory Detection
Questions security architects and detection engineers ask when designing or evaluating kernel-resident EDR sensors.
Find Out What Your EDR Actually Catches
Don't take detection coverage on faith. ISECURION's Red Team Assessments emulate the exact evasion techniques covered in this post - reflective loading, process hollowing, manual mapping - against your live environment.
Request a Red Team AssessmentSuspect an Active Intrusion?
Fileless and in-memory attacks are hard to spot and easy to lose evidence for. ISECURION's DFIR team provides rapid triage, forensic investigation, and containment - available for urgent engagement.
Engage Incident Response