EDR Internals: Telemetry Architecture and Evasion Boundaries
Published on 53 min read
Updated on
A source-based analysis of Windows Endpoint Detection and Response (EDR) telemetry paths on endpoints, meaning monitored workstations and servers, their documented semantics, enforcement limits, and red-team validation methodology.
Opening: What an EDR Actually Sees
Consider one short sequence: an interpreter starts; during execution, threads and mapped images appear; it opens a handle to another process, modifies a registry key, writes a file, and establishes a network connection. From an operator’s perspective, this may be one action. Windows exposes it as separate state transitions, observed at different layers, at different times, and with different guarantees. An EDR does not receive an “attack” from the operating system. It reconstructs one from incomplete events.
A process is a running instance of a program; a thread is one execution path inside that process. An image here means an executable file or library mapped into memory, which means Windows associates its pages with virtual addresses in the process. A Windows object is an entity managed by the system, such as a process, thread, or file; a handle is the reference granted to a process so it can manipulate that object with specific access rights.
That distinction is the central thread of this article. A callback is a function that a component registers so Windows calls it automatically when a specific operation occurs. A hook instead intercepts an existing execution path to observe or modify the call before returning control to the original code. The useful question is therefore what each mechanism can know at that instant, what it can change, what can be lost before analysis, and which independent signals remain if it is bypassed.
Endpoint Detection and Response products combine multiple observation and enforcement mechanisms. An event schema defines fields, their types, and their meaning; buffering temporarily retains events when their producer and reader run at different speeds. No single architecture describes every product: sensor placement, these schemas and buffers, cloud dependencies, and response capabilities remain implementation-specific. The Windows interfaces on which many products rely are nevertheless documented well enough to establish a precise baseline.
Telemetry is the technical data produced during execution, such as a process creation, file access, or network connection. A sensor collects part of that data. The EDR agent is the set of components installed on the endpoint; its local service and the backend, meaning the analysis engine behind the agent, normalize and connect events to produce detections.
Before following this sequence sensor by sensor, the places where it occurs, becomes observable, and is finally interpreted must be distinguished. A product commonly combines a user-mode service with one or more kernel-mode drivers, then sends some of their events to a local or remote analysis engine. This is a recurring design model, not a shared specification for every vendor.
This model first crosses two execution domains whose privilege levels determine what a sensor can observe or modify.
Two execution domains.
User mode hosts ordinary applications and most services. Each process has its own address space, the isolated set of virtual-memory addresses it can use, and cannot directly access kernel memory. A user-mode sensor can observe functions traversed by a process, inspect selected memory regions, or receive events published by Windows. Its visibility still depends on which process is instrumented and which execution path is actually taken.
Kernel mode hosts the Windows core and device drivers. A driver is a privileged module that lets the kernel manage a device or extend an operating-system function. An EDR driver can register with documented facilities to observe process creation, object access, file-system requests, or parts of network processing. This position is closer to operating-system state, but it provides neither automatic knowledge of intent nor universal authority to block every operation.
The two domains communicate in part through system calls, which are controlled transitions that let a program request a kernel service. Input/output (I/O) requests represent exchanges with files, devices, or network streams. Kernel-controlled shared objects and event queues complete those exchanges. The two domains are therefore not complete copies of the same reality: each layer exposes different context and guarantees.
Separating user mode from kernel mode locates privilege, but it does not yet explain the purpose of each EDR building block. These domains must now be connected to the components that collect, transport, and interpret events.
Component roles.
An application programming interface (API) is an entry point with a contract through which one program requests a service from another component. An API path is the sequence of functions actually traversed to satisfy that request.
A kernel callback is a function registered by a driver that Windows invokes while processing an operation in the kernel, such as process creation. A filter is a component that observes, permits, blocks, or modifies a category of operations. A minifilter is a filter specialized in file operations; it registers with Filter Manager, the Windows component that organizes these filters and delivers requested operations to them.
An altitude is a numeric identifier written as text that positions certain filters or callbacks in their processing chain. A pre-operation runs before the request reaches the file system; a post-operation observes its result on the return path. For a minifilter, Windows invokes pre-operations from higher to lower altitudes, then post-operations in the opposite order.
A synchronous decision is made before the observed operation completes, which can sometimes allow immediate blocking. An asynchronous action occurs later, after queuing or correlation, such as when an EDR terminates a process that has already started.
| Component | What it represents | Technical purpose | Important limitation |
|---|---|---|---|
| Observed application | The process in which behavior runs: a browser, script host, administration tool, or native binary | Produces the calls, image mappings, memory accesses, files, and connections that may become signals | An application does not describe operator intent by itself |
| User-mode sensor | A module loaded or injected into selected processes, or a component consuming their interfaces | Instruments API paths, captures call context, and may inspect content or memory | A call that does not cross the instrumented path can evade that specific sensor |
| Local EDR service | The durable process coordinating the endpoint agent | Receives events, normalizes fields, enriches identities, applies local policy, maintains queues, and communicates with the backend | A driver notification is not necessarily retained, enriched, or transmitted |
| EDR driver | A privileged module loaded into the Windows kernel | Registers callbacks, file filters, or network components and can make selected documented synchronous decisions | A driver observes only the facilities it registered with and the operations those facilities expose |
| Event Tracing for Windows (ETW) | The telemetry infrastructure built into Windows | Lets the kernel and applications publish structured events that an EDR can collect to reconstruct a timeline without modifying the observed code | A registered source is not necessarily enabled; an active collection channel does not guarantee retention of every event |
| Antimalware Scan Interface (AMSI) | An interface that selected applications, including script engines, use to present content to antimalware software | Allows a script or document to be scanned after decryption or reconstruction, at a point chosen by the host application | AMSI depends on application integration and does not universally observe every process |
| File-system minifilter | A driver specialized in monitoring file operations | Registers with Filter Manager, the Windows component that organizes these filters, then receives callbacks before or after open, read, write, modify, or delete operations | It receives only registered operations, and its presence does not ensure that an EDR retains an event |
| Windows Filtering Platform (WFP) | The filtering infrastructure built into Windows network processing | Evaluates traffic at several control points and lets an EDR permit, block, or enrich it with application identity | Available information and blocking authority depend on the selected control point |
| Analysis backend | A local or remote engine receiving normalized events | Correlates sensors, maintains state, enriches reputation, and produces detections or responses | Transport, latency, retention, and policy can change what an analyst ultimately sees |
ETW, AMSI, kernel callbacks, minifilters, and WFP are therefore not synonyms for “EDR.” They are sources and control points that a product may combine. The local service and backend turn those observations into entities, relationships, alerts, and actions.
The component list describes isolated responsibilities. Their relationship becomes clearer when one activity is followed across several sensors and into the backend.
A concrete event path.
Return to the interpreter from the opening sequence. Windows can notify the driver of process and thread creation, report mapped images, dispatch handle and registry operations to registered callbacks, and present file operations to the minifilter. If the interpreter integrates AMSI, script content may also be submitted through that interface. ETW can publish additional context, while the connection reaches one or more WFP layers.
These notifications do not necessarily arrive in the same format, with the same clock, or in the order later displayed by the backend. The local service must associate them with a process identity, copy fields while they remain valid, resolve what can be enriched without blocking the system, and decide what to retain or transmit. The backend can finally connect the script content, mapped image, affected file, and network destination to one entity.
The kernel driver is therefore one sensor among several, not the universal owner of endpoint telemetry. Callback delivery also does not prove that an event is retained or transmitted. Queue pressure, source configuration, sampling, exclusions, local policy, and backend ingestion, the entry of events into the analysis platform, can each change the final evidence set.
Act I: Birth and Execution
The architecture now becomes concrete with the first event in the central sequence: the process appears. Kernel callbacks expose that birth, while threads and mapped images show how an identity becomes an environment capable of executing code. None of these signals yet explains what the process will do.
The execution graph begins when a process appears: this transition supplies the initial identity to which later events can be attached.
Process creation.
PsSetCreateProcessNotifyRoutineEx is a registration function exposed by Process Manager, the Windows process manager, not the path traversed by every creation. Its code is exported by the ntoskrnl.exe kernel module. The Windows Driver Kit (WDK), the documentation, headers, and tools used to develop Windows drivers, provides its prototype in the ntddk.h header. The driver then references that entry point through NtosKrnl.lib, an import library used by the linker; when the .sys is loaded, the kernel loader resolves that import to the address of the export in ntoskrnl.exe.
The first argument is not the name of a Windows function: it is the address of a PCREATE_PROCESS_NOTIFY_ROUTINE_EX callback implemented in the EDR driver’s .sys image. With Remove set to FALSE, Process Manager adds that address to its process-notification state; with Remove set to TRUE, it removes the registration and waits for in-flight invocations to finish. The contract documents a list of registered routines, but its private symbol, memory representation, and address are not stable across builds. On creation, the callback receives a PEPROCESS pointer, which identifies the kernel object representing the process, along with a process identifier and a PS_CREATE_NOTIFY_INFO structure containing the documented information available at that instant.
Hereafter, process identifier (PID) and thread identifier (TID) are used as event-correlation keys. Their values can be reused after the corresponding object terminates.
The registration call and the later event-delivery path are therefore distinct. The driver calls the ntoskrnl.exe export once, after which Windows invokes the callback address inside the driver for each corresponding notification. Private symbols representing the internal container can help with build-specific debugging, but they should not appear as stable architectural interfaces.
The Ex callback runs at PASSIVE_LEVEL in the context of the thread creating the process. The interrupt request level (IRQL) is the kernel’s execution-priority level; as it rises, the set of permitted operations becomes more restricted. PASSIVE_LEVEL is the ordinary and least restrictive level. The callback is not purely observational: setting CreationStatus to an error can prevent creation. Whether an EDR uses this enforcement capability is a product-policy question. EPROCESS is the internal structure through which the kernel represents a process; a PEPROCESS pointer refers to it but is not a serialized copy of all its fields. No field should be described as collected unless the sensor explicitly reads and records it.
Detection relevance. Parent-child relationships, creator identity, image provenance, and command-line semantics can support process-chain analytics. A token is the Windows object carrying the security identity and privileges of a process or thread; a broker is an intermediary process that performs an operation for another process. None of these properties is independently conclusive. Spoofed parentage, brokered creation, renamed images, and incomplete command lines require correlation with token, handle, image, and execution telemetry.
The following WDK excerpt is intentionally limited to the documented callback contract. ProcessNotify, StartProcessProbe, and StopProcessProbe are sample functions compiled into the .sys, not Windows exports. UNREFERENCED_PARAMETER is a compile-time macro supplied by WDK headers to mark a parameter as intentionally unused; no function code is resolved from a runtime module. The excerpt distinguishes an exit notification (CreateInfo == NULL) from a creation notification and treats optional strings as optional. In this kernel context, DbgPrintEx is declared in wdm.h, linked through NtosKrnl.lib, and executed from ntoskrnl.exe; it writes to debugger output and is suitable for a probe in a controlled lab, not for production telemetry transport.
#include <ntddk.h>
static VOID ProcessNotify( PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo){ UNREFERENCED_PARAMETER(Process);
// A NULL CreateInfo denotes process exit. if (CreateInfo == NULL) { return; }
// ImageFileName is documented as optional. if (CreateInfo->ImageFileName != NULL) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "create pid=%p image=%wZ\n", ProcessId, CreateInfo->ImageFileName); }}
NTSTATUS StartProcessProbe(VOID){ return PsSetCreateProcessNotifyRoutineEx(ProcessNotify, FALSE);}
VOID StopProcessProbe(VOID){ (VOID)PsSetCreateProcessNotifyRoutineEx(ProcessNotify, TRUE);}Process creation establishes identity and lineage, but it does not describe the execution paths that later appear inside the process. Thread notifications add those execution units to the graph without determining, by themselves, what the threads execute.
Thread creation and exit.
PsSetCreateThreadNotifyRoutine is exported by ntoskrnl.exe, declared in ntddk.h, and referenced by a driver through NtosKrnl.lib. It receives the address of a callback located in the calling .sys; Thread Manager retains that registration in kernel state whose layout is undocumented. The classic callback receives only ProcessId, ThreadId, and a Create Boolean. ETHREAD is the internal structure representing a thread in the kernel, but the classic callback signature supplies no argument of that type. On thread creation, the routine executes in the context of the thread that created the new thread, at an IRQL no higher than APC_LEVEL. An asynchronous procedure call (APC) is a function queued to run in the context of a thread; APC_LEVEL therefore imposes more restrictions than PASSIVE_LEVEL.
The event identifies the target process and new thread but does not by itself establish remote-thread injection, which causes a thread to execute selected code inside another process. A reliable conclusion requires other evidence, such as the creator context, preceding process-handle access, virtual-memory operations, start-address provenance, or the call stack, the sequence of nested functions that led to the operation.
PsSetCreateThreadNotifyRoutineEx is the extended variant shown in brackets in the diagram. It also resides in ntoskrnl.exe, with its declaration in ntddk.h and import through NtosKrnl.lib, but its notification-type parameter changes the contract and, for selected types, the callback execution context. Removal uses PsRemoveCreateThreadNotifyRoutine, exported by the same module, declared in the same header, and imported through the same library. The exact API and notification type must therefore be recorded during testing rather than inferred from the generic label “thread callback.”
The existence of a thread does not reveal which executable images are introduced into its process. Image-mapping notifications add that code provenance while remaining distinct from evidence that the code actually executed.
Image mapping.
PsSetLoadImageNotifyRoutine is exported by ntoskrnl.exe, declared in ntddk.h, and imported by the driver through NtosKrnl.lib. It receives the address of a PLOAD_IMAGE_NOTIFY_ROUTINE located in the driver’s .sys; Process Manager retains the registration in kernel state without exposing the internal container as a contract. The driver must remove it before unloading through PsRemoveLoadImageNotifyRoutine, supplied by the same module, header, and import library.
Mapping creates an image section and then maps a view of it into the process virtual address space. A memory page is a fixed-size unit that the memory manager associates with a virtual address. The phrase “pages from a file” means virtual pages whose contents are backed by bytes from the EXE or DLL; it does not mean that the file is inherently divided into memory pages or copied into RAM in full. Windows can load a page’s contents on demand when that page is first accessed. Mapping therefore prepares access to code and data without meaning that the code has already executed.
Windows invokes the routine after mapping and before the entry point, the first address to which the loader transfers execution. The callback receives an optional full image name, the target process identifier, and IMAGE_INFO data such as the image base and size. In the diagram, LoadLibrary denotes the LoadLibraryA and LoadLibraryW family: these user-mode APIs are declared in libloaderapi.h, linked through Kernel32.lib, and exported by Kernel32.dll. They request that a module be loaded but do not call the EDR callback directly; the loader creates the mapping, after which Windows separately dispatches the kernel notification.
The routine returns VOID, meaning no value, so it cannot directly return an allow-or-deny decision. A digital signature associates a file with a publisher and verifies that it has not changed since signing. A hash is a fingerprint calculated from content; prevalence indicates how often the file has been observed across the fleet. Those properties, along with signature catalogs and reputation, are not intrinsic callback fields. A sensor may add them through other facilities.
The first part of the path established the process identity, its execution units, and the images introduced into its address space. It still does not show what that process requests from other Windows objects or which traces it leaves on the system.
Act II: Interactions with the System
The central sequence therefore moves outside the process. Registry callbacks describe configuration changes that can survive it, object callbacks reveal rights requested over other processes or threads, and minifilters follow effects on storage.
Registry operations.
The registry is Windows’ hierarchical configuration database. Its kernel subsystem, the Configuration Manager, lets a filter driver register through CmRegisterCallbackEx. This function is exported by ntoskrnl.exe, declared in wdm.h, and imported by the driver through NtosKrnl.lib. It receives, among other values, the address of the callback inside the .sys, the filter altitude, the driver object, and an optional context. Configuration Manager retains the registration and returns an opaque cookie, an identifier that the driver must pass to CmUnRegisterCallback to unregister. That removal function also belongs to ntoskrnl.exe and the wdm.h/NtosKrnl.lib contract. The kernel container associated with the cookie remains an implementation detail.
Operations are identified by REG_NOTIFY_CLASS. In the diagram, Reg* and Zw* are family labels rather than two individual functions. RegSetValueExW is one user-mode example: winreg.h declares it, Advapi32.lib supplies its import, and Advapi32.dll exports its entry point. ZwSetValueKey is the corresponding kernel example for a driver: it is declared in wdm.h, imported through NtosKrnl.lib, and exported by ntoskrnl.exe. These callers reach Configuration Manager; they do not invoke the EDR callback themselves, because the manager dispatches it according to active registrations.
Most operation classes expose pre-notifications and post-notifications with operation-specific structures. A pre-callback can inspect, block, or in supported cases modify an operation; a post-callback observes the resulting status and output. The available fields depend on the notification class. A generic claim that every registry event contains a full path, process identifier, thread identifier, value data, and permissions is therefore too broad. Name resolution and caller attribution may require additional work by the sensor.
Detection relevance. Persistence keys, security-provider configuration, service definitions, and policy changes become meaningful when correlated with the initiating identity, process lineage, image trust, and temporal sequence. Registry access alone does not identify intent.
The registry exposes configuration changes, but not the rights one process requests over another process or thread. Object callbacks add this interprocess relationship and can reduce supported access rights before the handle is created.
Process and thread object operations.
In this mechanism, each handle carries an access mask for the targeted Windows object. ObRegisterCallbacks is exported by ntoskrnl.exe, declared in wdm.h, and imported through NtosKrnl.lib. The driver passes it a registration structure containing the altitude, object types, and addresses of the pre-operation and post-operation routines located in its own .sys. ObjectPreCallback and ObjectPostCallback name those callback roles and signatures rather than two functions implemented by ntoskrnl.exe on the driver’s behalf. Object Manager retains the registered set and returns an opaque registration handle. ObUnRegisterCallbacks, supplied by the same module and compilation contract, consumes that handle to remove the callbacks before the driver unloads. The kernel structure behind the handle is not public.
OB_OPERATION_HANDLE_CREATE and OB_OPERATION_HANDLE_DUPLICATE distinguish new and duplicated handles. The OpenProcess and DuplicateHandle functions shown in the diagram are user-mode APIs exported by Kernel32.dll and imported through Kernel32.lib; their declarations reside in processthreadsapi.h and handleapi.h, respectively. They produce requests on Windows objects. They do not directly call the EDR callback: Object Manager receives the kernel operation and dispatches the pre/post callbacks that were already registered.
This mechanism supports protection of sensitive processes by constraining rights such as process-memory access, thread creation, context modification, and handle duplication. Kernel handles and operations originating from trusted or protected components require careful interpretation. A blocked or reduced handle also does not demonstrate credential-dumping intent without surrounding behavior.
The policy core below shows the supported enforcement model. ProcessHandlePreOperation is the callback compiled into the driver’s .sys, while IsProtectedTarget is a sample predicate belonging to that same driver rather than a Windows function. PsProcessType is not a function: it is a data symbol declared by the WDK and exported by ntoskrnl.exe, whose value identifies the process object type to Object Manager. Registration, altitude selection, target lifetime handling, and policy synchronization are omitted, but the access-mask operation uses the documented pre-operation structures. The target predicate must be narrow; applying this mask to every process would break legitimate software.
static OB_PREOP_CALLBACK_STATUS ProcessHandlePreOperation( PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION Info){ UNREFERENCED_PARAMETER(RegistrationContext);
if (Info->ObjectType != *PsProcessType || !IsProtectedTarget((PEPROCESS)Info->Object)) { return OB_PREOP_SUCCESS; }
ACCESS_MASK *desired = NULL; if (Info->Operation == OB_OPERATION_HANDLE_CREATE) { desired = &Info->Parameters->CreateHandleInformation.DesiredAccess; } else if (Info->Operation == OB_OPERATION_HANDLE_DUPLICATE) { desired = &Info->Parameters->DuplicateHandleInformation.DesiredAccess; }
if (desired != NULL) { *desired &= ~(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE); } return OB_PREOP_SUCCESS;}Object callbacks describe the rights one process requests over another object, but they do not cover effects on storage. To observe file creation, writes, renames, or deletion and then correlate those operations with their initiating process in the EDR pipeline, the sensor uses another kernel layer: file-system minifilters.
Effects on the file system.
A minifilter is a driver specialized in monitoring file operations. It registers with Filter Manager (FltMgr.sys), the Windows component that organizes these filters, then receives callbacks before or after the operations it declared. A volume is the logical target carrying a file system, such as a mounted partition; one minifilter attachment to one volume is an instance.
That registration uses FltRegisterFilter, a function exported by FltMgr.sys, declared in fltkernel.h, and imported through FltMgr.lib. The minifilter calls it from its DriverEntry, the entry function compiled into its own .sys that Windows invokes when loading the driver. It supplies an FLT_REGISTRATION structure containing, among other data, the addresses of its callbacks, which also reside in that .sys. FltMgr adds the filter to its global list and returns an opaque PFLT_FILTER pointer. FltStartFiltering, supplied by the same module, header, and import library, then permits volume attachment and I/O delivery. FltUnregisterFilter, from the same source, removes the registration during unload. Internal structures behind the opaque pointer are not part of the contract.
In the Windows driver model, many of these operations are carried by input/output request packets (IRP), structures describing the requested action and its state to the kernel. The assigned altitude positions the instance in the stack; a product name does not establish a fixed EDR-before-antivirus order.
For a given operation, pre-operation callbacks run from the highest altitude toward the file system. Completion returns through post-operation callbacks from the lowest altitude upward. A pre-callback can pass the request, request a post-callback, pend it, synchronize it, or complete it with a final status. Post-operation availability and execution context depend on the pre-operation result and operation type.
Detection relevance. Create, write, rename, disposition, meaning a request to delete a file, section synchronization, and metadata operations can support ransomware, staging, the preparation or grouping of data before transfer, tampering, and collection analytics. Entropy here measures the distribution of byte values and can indicate highly transformed or encrypted content without proving it by itself. Path, content, entropy, volume context, and process attribution are product enrichments rather than a universal minifilter event schema.
A minifilter declares the operations it wishes to receive. This static table creates no fixed relationship with another vendor’s filter; FltMgr combines the table with the instance altitude at runtime. In the excerpt, PreCreate, PostCreate, PreWrite, PostWrite, PreSetInformation, and PostSetInformation are functions supplied by the minifilter and compiled into its .sys, not exports from FltMgr.sys.
CONST FLT_OPERATION_REGISTRATION Operations[] = { { IRP_MJ_CREATE, 0, PreCreate, PostCreate }, { IRP_MJ_WRITE, 0, PreWrite, PostWrite }, { IRP_MJ_SET_INFORMATION, 0, PreSetInformation, PostSetInformation },
{ IRP_MJ_OPERATION_END }};Registry, object, and file interactions show what the process touches locally. They are still distributed across several contracts and do not describe the complete execution timeline or, by themselves, the network connection that ends the sequence.
Act III: Telemetry Transport and Reconstruction
ETW adds structured events to the timeline, WFP exposes network control points, and user-mode instrumentation retains the context of API paths that were actually traversed. These sources complete the graph, after which the EDR service must still determine which observations concern the same entity and in what order they occurred.
Event Tracing for Windows (ETW) is the telemetry infrastructure built into Windows: the kernel and applications publish structured events describing their activity. An EDR can collect and correlate those events to reconstruct a timeline, such as a process creation or Dynamic Link Library (DLL) load, meaning a reusable code library loaded by a process, without directly modifying the observed code.
A provider is the component that produces events. A controller starts a session, meaning the temporary channel and its memory buffers, then chooses which providers to enable. A consumer reads session events in real time or from an Event Trace Log (ETL) file. An EDR service can be both controller and consumer, but the controller does not relay every event between provider and consumer.
For a conventional user-mode provider, EventRegister registers the provider identity and returns a registration handle, after which EventWrite publishes an event with that handle. Both public functions are declared in evntprov.h, imported through Advapi32.lib, and exported by Advapi32.dll. Generated ETW frameworks may wrap them, but the provider functions remain in the provider’s EXE or DLL; ETW receives data through these entry points rather than copying provider code into the system.
Event records can include a provider identifier, event identifier, version, a level representing severity, a keyword used as an enablement category, a timestamp, process and thread identifiers, activity identifiers, and a payload, meaning data specific to that event. Its schema comes from a manifest, self-describing TraceLogging metadata, Managed Object Format (MOF), or a Trace Message Format (TMF) file, which tell the consumer how to decode the fields. ETW does not guarantee that a provider is enabled, that a consumer retains every event, or that the resulting event is security-relevant.
PowerShell, .NET, kernel, and Microsoft-Windows-Threat-Intelligence events are distinct provider families with different enablement and access conditions. .NET is Microsoft’s platform for managed code, meaning code whose execution and memory are supervised by a dedicated runtime environment. AMSI is a separate content-inspection interface. Its signals may be correlated with ETW, but it is not an ETW provider category.
ETW contributes execution context and a timeline, but its collection role does not provide arbitration at specific network layers. WFP exposes those control points and can associate a decision with the flow, application, or user according to the selected layer.
Network telemetry with WFP.
Windows Filtering Platform (WFP) is the filtering infrastructure built into the Windows networking stack. The networking stack is the sequence of components that turns application data into packets and performs the reverse path on receipt. WFP exposes layers, meaning control points at specific stages of that processing.
At each layer, a WFP filter is a rule made of conditions and an action. The filter engine evaluates those rules; it can permit or block traffic directly, or invoke a callout, a function supplied by a driver for specialized processing. The Base Filtering Engine (BFE) manages platform configuration. Application Layer Enforcement (ALE) layers can add application or user identity to the decision.
The BFE coordinates policy and persistent configuration in user mode; classification, meaning comparison of traffic with applicable filters, occurs in kernel mode at network and transport layers. A flow groups exchanges belonging to one communication. At ALE layers, its metadata can include application, user, protocol, and local or remote addresses and ports. Availability varies by layer and event type.
An EDR callout is therefore not placed before WFP. It is invoked because a matching filter at a layer selects that callout. Its classify function is a callback compiled into the EDR driver’s .sys, not a generic function whose code resides inside WFP. The WFP kernel engine invokes that registered address with layer-specific metadata; the callback can inspect it and contribute an action under WFP arbitration, the priority rules that determine the final action when several filters match. The network payload is data carried by packets; stream reassembly reconstructs a continuous sequence from those packets. Inspection and final blocking semantics depend on the selected layer, callout implementation, and rights granted to the action.
WFP describes a communication and its application identity, but not necessarily the API path that caused the process to request it. User-mode instrumentation completes this view by observing calls that actually traverse the instrumented functions.
User-mode API instrumentation.
Some EDR products inject or load a user-mode sensor and detour selected exports, the functions a module makes callable by other components, in libraries such as ntdll.dll. An inline hook modifies a function’s first instructions, called its prologue, to branch execution to sensor code. A trampoline is a small code block that replays the displaced instructions and returns execution to the remainder of the original function.
The diagram uses NtCreateThreadEx as a concrete example. On observed modern Windows builds, this Native API entry point is exported by ntdll.dll; its user-mode stub prepares the transition to the corresponding kernel service. It is not treated here as a stable documented Win32 API, and both its stub and system-call identifier must be verified on the examined build. At a higher public layer, CreateRemoteThreadEx is declared in processthreadsapi.h, imported through Kernel32.lib, and exported by Kernel32.dll. These levels must not be conflated: the Win32 API supplies an application contract, while the ntdll.dll export represents the native transition shown to explain the detour.
System-call identifiers and stubs are architecture- and build-dependent. A stub is the short machine-code sequence that prepares an operation identifier and performs the transition into the kernel. Hard-coding one identifier as a general Windows technique is technically unsound. Microsoft Visual C++ also does not support inline assembly for the 64-bit x86 architecture (x64) in the form commonly shown in simplified examples. Research tooling must resolve and validate the target build rather than assume a fixed stub.
From a Local Signal to a Correlatable Event
Accumulating events from several sensors is not enough to reconstruct behavior. The pipeline must first determine which observations describe the same entity, when they occurred, and whether missing data follows from an API contract or a collection failure.
The first difficulty is maintaining a stable identity despite Windows identifier reuse and the limited lifetime of kernel objects.
Windows identifiers are convenient correlation keys but are not permanent identities. Process and thread identifiers can be reused after object termination. An epoch here means one execution period bounded by a Windows or sensor restart. A robust event model therefore combines the identifier with creation time and that epoch, then preferably with a product-generated entity identifier. Correlating a late network or file event to the current owner of a reused PID can otherwise create a false lineage.
Kernel callbacks also expose objects at different lifecycle stages. A process-create callback runs before the initial thread begins executing, while an image callback occurs after an image mapping has been created but before its entry point executes. A post-operation object callback observes a completed handle operation. These events describe different state transitions and should not be forced into a single generic timestamp such as “process started.”
Object pointers are valid only under the contract and lifetime rules of the callback receiving them. Retaining a raw pointer for asynchronous processing, meaning work performed later, without taking an appropriate reference is unsafe. Production sensors generally copy stable fields into a bounded record and defer expensive enrichment to a worker, a background task or thread, or to the user-mode service.
A stable entity identity indicates which events may be related, but it does not establish their causal order. Generation time and the clock domain of each source must also be preserved.
Event time and causal order.
Callback invocation order is not equivalent to backend arrival order. A buffer is temporary memory that absorbs a speed difference between a producer and reader; batching groups several events for transport together. Events can traverse per-central-processing-unit (CPU) buffers, kernel-to-user queues, local persistence, compression, batching, transport, and cloud ingestion before becoming queryable. Two records generated in a known kernel order may be observed in reverse order after independent buffering paths.
Several clocks can coexist: system time, interrupt time, a high-resolution performance counter, ETW timestamps, local service receipt time, and backend ingestion time. A clock domain is the time source and origin used to create a timestamp. A defensible correlation model preserves that original domain and records conversion uncertainty. Backend time should not replace event-generation time when reconstructing sub-second process, thread, handle, or network sequences.
Even a correctly ordered timeline is misleading if an optional field is treated as evidence of tampering. The semantics of each callback must therefore be established before conclusions are drawn from missing data.
Optional fields, name resolution, and races.
Documented optionality has analytical consequences. ImageFileName and CommandLine in PS_CREATE_NOTIFY_INFO can be absent. FullImageName in an image-load callback can be null. Registry callbacks receive operation-specific structures rather than a universal fully resolved path. Network metadata differs across WFP layers. Missing data is therefore not proof of tampering.
Names are mutable views of objects. A race occurs when an object changes between the event and later resolution. A reparse point can redirect path resolution, while a hard link gives the same file more than one name. A file can therefore be renamed or resolved differently after creation. Sensors that enrich asynchronously must distinguish event-time values from values resolved later.
Optionality explains why a valid event may lack one field; it does not explain the disappearance of entire records. At system scale, throughput, queues, and buffers introduce a second source of uncertainty.
Throughput, backpressure, and event loss.
Backpressure occurs when a producer generates events faster than the next component can process them. Every collection path therefore has a performance budget: kernel callbacks execute in constrained contexts, minifilters sit on latency-sensitive I/O paths, WFP callouts can affect network throughput, and ETW sessions use finite buffers. A sensor may sample, aggregate, suppress repeated events, or drop low-priority records. In fail-open mode, it lets an operation continue during failure or saturation to preserve availability, at the cost of reduced visibility or control.
These constraints determine what the product can know with confidence, but not yet what it can prevent. Each source’s observation capability, possible synchronous influence, and later product response must be separated.
Act IV: From Observation to Decision
The central sequence is now correlated: the product can connect the process, its images, object access, files, and network connection. That knowledge does not yet establish when an action can be prevented. The native interface determines what a callback can do synchronously; product response features can add later actions, but they should not be attributed to the collection API itself.
| Mechanism | Native observation | Native synchronous influence | Important boundary |
|---|---|---|---|
| Process notify Ex | Creation and exit; documented creation metadata | CreationStatus can fail creation | Product may use the callback only for telemetry |
| Thread notify | Target PID, TID, create/delete state | No documented allow/deny return | Injection requires correlation beyond the callback |
| Image-load notify | Image mapping metadata | No allow/deny return; callback is VOID | Mapping already exists when notification is delivered |
| Registry callback | Typed pre/post registry operation data | Pre-callback can block or modify supported operations | Fields and permitted changes depend on REG_NOTIFY_CLASS |
| Object callback | Process/thread handle create or duplicate | Supported access bits can be removed | Pre-callback returns success; it is not a generic deny hook |
| Minifilter pre-op | Registered file-system operations | Pass, pend, modify, synchronize, or complete | Ordering is determined by altitude and operation registration |
| ETW consumer | Events emitted to an enabled session | None through the consumer role | Provider enablement and retention are separate control decisions |
| WFP callout | Layer-specific flow, stream, packet, or ALE metadata | Classification can contribute permit/block behavior | Rights, arbitration, and available data vary by layer |
| User-mode detour | Arguments, caller context, return path, product-defined data | Hook code can alter or deny the user-mode call | Only calls traversing the instrumented path are observed |
Asynchronous response must be modeled separately. A sensor may observe an image mapping and terminate the process milliseconds later, or record a network flow and isolate the host after backend correlation. Such behavior is preventive at the product level but is not synchronous prevention by the original image or network event source.
An API may provide an enforcement capability and still be unusable when the component is not sufficiently trusted, protected, or kept active. Platform trust and self-protection therefore determine whether that capability remains available during a tampering attempt.
Platform trust, protected execution, and self-protection.
Kernel registration APIs impose trust conditions of their own. For example, process Ex callbacks can fail registration when the callback image lacks the required integrity characteristic, and object callback registration can be denied when routines are not located in a signed kernel image. These checks raise the cost of arbitrary registration but do not establish that every signed driver is secure.
Protected Process Light (PPL) is a Windows protection level that reserves selected process operations for components with a sufficient signing level. It can limit user-mode inspection or handle acquisition by an EDR component that lacks a sufficient trust level. Some security products use protected services and protected antimalware-light processes; exact configuration remains vendor- and deployment-specific.
A hypervisor is the software layer that creates and isolates execution environments beneath the main operating system. Virtualization-based security (VBS) uses that isolation to protect selected decisions and data. Code integrity is the Windows mechanism that verifies whether a binary meets signature and policy requirements before execution. Hypervisor-protected code integrity (HVCI) uses the isolated environment to strengthen this verification for code permitted to execute in the kernel. Microsoft’s vulnerable-driver blocklist rejects known vulnerable drivers, while App Control applies a policy defining which executables and drivers may run. These mechanisms reduce exposure to known or untrusted drivers but do not convert undocumented kernel data into a supported security boundary.
Kernel Patch Protection, commonly called PatchGuard, periodically verifies selected critical kernel structures and code on supported 64-bit systems. It should not be described as a complete, immediate, or publicly specified detector for every callback or ETW manipulation. Its coverage and timing are implementation details. A crash after an unsupported modification is evidence of platform instability, not a reliable EDR detection result.
Product self-protection adds another layer. An access control list (ACL) states which identities may act on a service or driver device. A watchdog checks that a component remains active and attempts to restart it or report failure. Protected services, callback-health checks, module integrity, signed configuration, and backend health can also contribute. Anti-tamper, which aims to prevent or detect product modification, remains distinct from behavioral detection.
Act V: What Disappears and What Remains Visible After One Sensor Is Bypassed
Now suppose that one of the sensors encountered in the sequence is bypassed. Determining what actually changes requires following the signal beyond collection and checking whether later stages still receive independent observations capable of describing the activity.
An EDR pipeline is the sequence of stages that turns raw endpoint observations into usable records and, potentially, a detection or response action. Collection is only its first stage; detection quality also depends on normalization, enrichment, state, and correlation.
| Stage | Typical function | Principal failure modes |
|---|---|---|
| Collection | Receive callbacks, events, I/O, flow, and user-mode signals | Sensor disabled, callback absent, provider not enabled, event loss |
| Normalization | Convert product-specific records into a stable internal schema | Missing fields, ambiguous identity, clock skew, schema drift |
| Enrichment | Resolve signer, hash, reputation, token, path, ancestry, and asset context | Network dependency, cache staleness, path races, inaccessible objects |
| Correlation | Relate events across processes, threads, sessions, and hosts | Identifier reuse, incomplete windows, sampling, backend delay |
| Decision | Score, alert, block, isolate, or collect evidence | Policy differences, model threshold, suppression, response latency |
The presence of a callback should not be equated with full visibility. Conversely, bypassing one callback or user-mode detour should not be equated with loss of detection. The relevant unit of analysis is the complete behavior graph and the set of independent sensors that can describe it.
The pipeline shows that collection, normalization, correlation, and decision can fail independently. Evaluating evasion therefore means locating the altered stage precisely, then measuring the evidence and actions that remain in the other layers.
Reading evasion as a loss of visibility.
Evasion techniques should be described by the exact visibility path they affect. Terms such as “disable EDR” or “become invisible” are too broad unless the product, version, policy, sensor state, and measured outcome are specified.
The first case concerns sensors closest to user-mode code: bypassing their detour changes one observation path without removing state transitions that Windows exposes elsewhere.
User-mode unhooking and direct system calls.
These techniques can remove or bypass selected user-mode detours. Residual evidence can include process and thread callbacks, handle operations, image mappings, ETW, WFP, minifilter activity, remote-memory state, and backend correlations. Hook restoration can itself create integrity or memory-protection anomalies.
After the detour is bypassed, ETW remains a separate path for enabled providers. Tampering claims must therefore identify the affected process, publication function, session, and provider family.
ETW tampering.
Microsoft describes EtwEventWrite in ntetw.h as an internal operating-system function that can change between Windows releases. On observed modern builds, its user-mode entry point is exported by ntdll.dll; that location describes the examined implementation rather than a stable application contract. A compatible provider should prefer the public EventWrite API already located above in Advapi32.dll. When its publication path reaches ntdll.dll!EtwEventWrite, patching that copy in one process affects only calls that traverse it in that process. It does not disable kernel providers, other processes, other ETW entry points, or non-ETW sensors. Modifying executable pages can also become an observable integrity event. Kernel ETW state is implementation-specific and must not be represented as a stable offset or structure layout.
Altering one user-mode ETW path does not modify callbacks registered separately in the kernel. Acting on those callbacks therefore moves the problem to a privileged primitive and build-specific structures.
Kernel callback manipulation.
Removing a callback through undocumented internal structures requires a kernel-write primitive, meaning the ability to modify a chosen address in kernel memory, and build-specific discovery. An input/output control (IOCTL) code is a structured command sent by a process to a driver; Windows provides no generic “remove callback” IOCTL. A vulnerable driver exposes its own device protocol and primitives, so exploitation must implement that driver’s read/write semantics and resolve target kernel state for the exact build. Integrity mechanisms, product self-protection, callback re-registration, crashes, and other sensors all change the outcome.
A kernel primitive can, for example, be obtained by abusing a legitimately signed but vulnerable driver. The question then moves from bypassing a sensor to the trust chain that permits that driver to load and exposes access to its device.
Vulnerable signed drivers.
A valid signature establishes a trust relationship for loading; it does not establish that the driver is free of exploitable defects. Modern controls include HVCI, the Microsoft vulnerable-driver blocklist, App Control policies, and the Attack Surface Reduction (ASR) rule for abused vulnerable signed drivers. Coverage is not absolute, and blocklist state must be verified on the tested endpoint.
Epilogue: Measuring Coverage in a Lab
The sequence has shown how one activity becomes several observations and why the disappearance of one source is meaningful only when compared with the evidence that remains. A lab provides the setting in which to measure those differences without conflating a Windows contract, one build’s implementation, and a product’s behavior.
Establishing the Scope of Each Form of Evidence
Three evidence classes must remain separate:
- Documented contracts include public Windows application programming interfaces (API), which programs call, and device driver interfaces (DDI), which define how a driver communicates with the kernel, the central privileged part of the operating system. Callback signatures and ordering rules are also part of those contracts. These are the strongest basis for architectural claims.
- Observed implementation details include private symbols, internal arrays, structure offsets, and product routines obtained through reverse engineering, meaning analysis of a binary and its execution when source code is unavailable. A debug symbol maps a machine address to a function, variable, or type name; private here means it is not a supported public interface. An offset is the distance of a field from the start of a structure, while its layout is the complete arrangement of fields in memory. A build is one precise compiled version of the software. These details are valid only for the examined Windows and product builds.
- Product behavior includes the telemetry actually retained, enriched, transmitted, and evaluated by a specific EDR version and policy. It must be measured rather than inferred from callback availability.
The sixth edition of Windows Internals describes Windows 7 and Windows Server 2008 R2. It remains useful for architectural concepts, but it is not a current implementation contract. The Windows Driver Kit (WDK) provides the documentation, headers, and tools used to develop Windows drivers; its current documentation and the seventh edition of Windows Internals take precedence for supported interfaces. WinDbg is Microsoft’s debugger for observing Windows state and execution. Private symbols, offsets, and debugger observations remain specific to the examined build.
Kernel debugging and reverse engineering are necessary when the research question concerns actual implementation rather than the documented contract. They require a stricter evidence format.
Event Tracing for Windows Threat Intelligence (ETW-TI) is a security-oriented Windows provider that publishes selected sensitive operations; it remains one ETW source with its own access and enablement conditions. Disassembly translates a binary’s machine code into processor instructions; pseudocode is a higher-level, more readable representation reconstructed by the analysis tool. Notification routines, callback registrations, minifilter nodes, and encoded entries can thus be studied with private symbols and disassembly. A primitive exposed by a vulnerable driver may sometimes allow kernel-memory modification. These techniques show only that the examined build can be inspected or altered; they do not make internal symbol names, array sizes, pointer encodings, list layouts, or field offsets portable.
For each internal claim, a research record should include:
- the exact kernel and driver file versions, hashes, timestamps, and loaded base addresses;
- the symbol server, the repository supplying debugging information, the identity of the Program Database (PDB) file associated with the binary, and symbol load state; these symbols map machine addresses to names, functions, and types;
- the disassembly or pseudocode location establishing how the internal object is referenced;
- the relevant structure layout derived from symbols or code, not copied from another build;
- the synchronization and lifetime assumptions required before reading or modifying the object;
- the before-and-after state, including callback re-registration, sensor health, system stability, and residual telemetry.
Security-driver ownership should be established by resolving each routine address to the loaded module and then examining the registration call or routine implementation. Labels such as WdFilter.sys, MsSecFlt.sys, or a third-party driver are observations from one system, not mandatory members of a Windows callback list. Load order, product version, enabled features, and platform security components change the population.
Removal experiments are particularly easy to overstate. Clearing an entry, unlinking a node, disabling an enable flag, or restoring bytes can produce a temporary state while leaving cached pointers, rundown references, which prevent an in-use object from being destroyed, worker queues, watchdogs, alternative callbacks, and backend evidence intact. A successful write is not equivalent to a clean or durable sensor bypass.
These evidence requirements also provide a framework for identifying recurring analytical errors. When an analysis skips a stage between registration, delivery, enrichment, and decision, it turns a local observation into an unsupported general conclusion.
Common analytical errors.
- Combining registration with event flow. A setup API called during driver initialization should not be drawn as a step traversed by every event.
- Presenting private symbols as public architecture. Internal names and offsets require an explicit build qualifier and evidence source.
- Inventing universal event fields. Callback arguments, product enrichment, and backend schema are separate layers.
- Equating notification with prevention.
VOIDcallbacks cannot return block decisions; asynchronous product response should be described separately. - Equating one bypass with EDR disablement. The affected hook, provider, callback, callout, or queue must be named precisely.
- Using an alert as the only ground truth. Raw telemetry, prevention outcome, sensor health, and backend ingestion must also be inspected.
- Ignoring negative controls. A test is incomplete without a comparable baseline, meaning a reference capture, and a benign operation of the same API shape.
- Ignoring recovery behavior. Watchdogs, callback re-registration, an operating-system restart, policy refresh, and updates can reverse a transient change.
- Assuming a fixed system-call number. Architecture and Windows build determine the stub; examples with identifiers fixed directly in the code age immediately.
- Treating a signed driver as trusted behavior. Signature, vulnerability state, blocklist coverage, and runtime policy are distinct properties.
Comparing Residual Evidence
These analytical errors are corrected by a protocol that separates the generated behavior, the tested collection path, and the observed result. An exhaustive test plan must vary those dimensions independently; the table below describes minimum evidence, not a product-specific expected alert.
| Test family | Primary source under test | Independent residual sources | Required outcome evidence |
|---|---|---|---|
| Process creation | Process Ex callback | Image map, ETW, handle lineage, service audit | Creation status, raw event, entity lineage, policy action |
| Remote thread behavior | Thread callback or user hook | Object callbacks, memory telemetry, ETW-TI, target state | Source/target identity, rights, start address, thread result |
| Image introduction | Image-load callback | Minifilter, code integrity, memory scan, ETW | Mapping state, signer/hash enrichment, execution outcome |
| Registry persistence | Registry callback | Process lineage, service/task telemetry, later activation | Pre/post status, resolved key/value, initiating identity |
| Sensitive-process access | Object callback | Audit events, memory reads, thread behavior, protection state | Original/reduced access, granted handle, follow-on operation |
| File transformation | Minifilter | Process/thread, entropy/content analysis, volume telemetry | Pre/post result, bytes/path identity, rate and scope |
| Script or managed code | ETW/AMSI path | Process, image, file, network, memory | Provider enablement, content availability, downstream behavior |
| Network connection | WFP/ALE | Domain Name System (DNS), process lineage, proxy, Transport Layer Security (TLS), remote-side logs | Layer, direction, endpoints, app identity, action |
| User-hook bypass | Instrumented export | Kernel callbacks, ETW, WFP, minifilter, memory | Hook integrity, actual call path, residual event graph |
| Driver load or abuse | Code integrity and driver policy | Image callback, service registry, minifilter, kernel telemetry | Load decision, policy state, driver hash/version, device access |
The strongest conclusion is rarely binary. Coverage can be complete, partial, delayed, sampled, enriched only in the backend, or sufficient for detection despite a missing primary event. Each dimension should be recorded separately.
The matrix states which sources to compare and which evidence to retain. It does not make the experiment reproducible without a protocol that pins the environment, controls variables, and separates result layers.
Reproducible research protocol.
A defensible EDR-internals experiment records enough state for another researcher to reproduce or challenge the conclusion.
- Pin the environment. Record the Windows edition, build and servicing level, virtualization and VBS state, EDR product and sensor versions, policy identifier, and network connectivity.
- Define the behavior. Specify the process, thread, image, registry, object, file, ETW, or network operation being generated. Avoid using an alert name as the behavior definition.
- Inventory the expected sensors. Record documented callbacks, loaded drivers, minifilter instances and altitudes, ETW sessions and enabled providers, WFP filters and callouts, and user-mode modules.
- Capture a baseline. Execute a benign control with the same API shape and collect local and backend records before introducing any evasion condition.
- Change one variable. Disable, bypass, or alter one collection path while holding the behavior and policy constant.
- Measure residual visibility. Compare raw events, normalized records, alerts, prevention, backend arrival time, and endpoint side effects.
- Repeat after reboot and update. Callback state, provider sessions, blocklists, and self-protection can be restored or changed by servicing and sensor health checks.
Results should distinguish at least four outcomes: the targeted event disappeared; the event remained but lost fields; telemetry remained while the alert disappeared; or prevention changed while telemetry remained. These outcomes describe different layers of the pipeline and should not be collapsed into a single “bypass succeeded” statement.
The following read-only inventory provides a reproducible starting point on a Windows lab host. It does not identify every EDR sensor, but it captures three frequently misunderstood surfaces before a test begins.
$EvidenceRoot = Join-Path $env:TEMP ("edr-surface-" + (Get-Date -Format "yyyyMMdd-HHmmss"))New-Item -ItemType Directory -Path $EvidenceRoot | Out-Null
fltmc filters | Out-File (Join-Path $EvidenceRoot "minifilters.txt")logman query -ets | Out-File (Join-Path $EvidenceRoot "etw-sessions.txt")logman query providers | Out-File (Join-Path $EvidenceRoot "etw-providers.txt")
$WfpState = Join-Path $EvidenceRoot "wfp-state.xml"netsh wfp show state "file=$WfpState"
Get-CimInstance Win32_SystemDriver | Select-Object Name, State, StartMode, PathName | Export-Csv (Join-Path $EvidenceRoot "drivers.csv") -NoTypeInformationThe snapshot should be paired with operating system (OS) and sensor versions, policy state, timestamps, and backend evidence. fltmc confirms minifilter instances and altitudes, logman distinguishes registered providers from active sessions, and netsh wfp exports the current WFP and Internet Protocol Security (IPsec) state.
Returning to the Central Sequence
Return to the opening sequence. Process creation establishes identity and lineage. Image notifications describe executable mappings, but do not decide whether those mappings are malicious. An object callback can observe or reduce requested access to another process, while a later thread notification says only that a thread appeared. File and WFP telemetry add effects outside the process. ETW may contribute runtime context. The analytical value emerges from their temporal relationship, not from any single event.
A claim about EDR visibility can therefore be tested with four questions:
- Which Windows state transition occurred? A behavior should be described independently of an alert or tool name.
- Which documented mechanism can observe or influence it? Registration, delivery, normalization, detection, and response are different stages.
- Which fields and ordering guarantees actually exist? Optional data, PID reuse, asynchronous transport, and event loss constrain every conclusion.
- Which independent evidence survives if that mechanism is absent? Residual process, handle, image, file, ETW, or network signals determine whether an evasion removes one event or genuinely breaks the analytical chain.
This mental model is useful only when each claim retains the evidence level that supports it. The final step is therefore to distinguish public contracts, build-specific observations, and measured product behavior.
Source hierarchy and limitations.
The public WDK defines supported interfaces but not every internal dispatch structure used by a particular Windows build. Windows Internals explains implementation concepts but explicitly warns that undocumented internals can change. Kernel-debugger symbols and reverse engineering can establish what a specific build does; they cannot establish a forward-compatible contract. Vendor behavior requires direct observation because the existence of an OS facility does not prove how a product configures or consumes it.
An experiment based on WinDbg symbols and a read or write primitive exposed by a vulnerable driver can establish behavior for the examined system. It does not prove that offsets, internal layouts, callback ownership, or provider state are identical on another build.
Understanding Where the Cited Windows Functions Live
A function cited in this article can involve three different locations. Its runtime module contains the loaded code or the stub that transfers control to the next stage. Its header provides the compiler with the prototype and types. Its .lib import library lets the linker record the dependency in the Portable Executable (PE) image, but it does not contain the code used at runtime. The loader resolves that dependency and writes the resulting address into the application’s or driver’s Import Address Table (IAT).
| Cited functions | Runtime module | Declaration and import | Role in the sequence |
|---|---|---|---|
PsSetCreateProcessNotifyRoutineEx, thread and image notification functions, CmRegisterCallbackEx, ObRegisterCallbacks, DbgPrintEx, ZwSetValueKey | ntoskrnl.exe | ntddk.h or wdm.h, then NtosKrnl.lib | Registration, removal, or a service supplied by the kernel |
FltRegisterFilter, FltStartFiltering, FltUnregisterFilter | FltMgr.sys | fltkernel.h, then FltMgr.lib | Minifilter registration and activation |
EventRegister, EventWrite, RegSetValueExW | Advapi32.dll | evntprov.h or winreg.h, then Advapi32.lib | ETW publication or user-mode registry access |
LoadLibraryA/W, OpenProcess, DuplicateHandle, CreateRemoteThreadEx | Kernel32.dll | The corresponding Win32 header, then Kernel32.lib | Public contract called by an application |
NtCreateThreadEx, and EtwEventWrite on observed builds | ntdll.dll | Native API or internal interface that must be qualified for the build | User-mode stub close to the native transition; it must not be presented as a stable Win32 contract |
The ProcessNotify, ProcessHandlePreOperation, PreCreate, PostCreate, and other callbacks supplied by the EDR do not reside in any of these Windows modules. Their code is compiled into the product’s .sys, EXE, or DLL. The Windows API receives their address during registration, after which the relevant manager retains a registration through which it can invoke them. The contract can guarantee that invocation without publishing the kernel array, list, or layout that represents the registration on a particular build.
This distinction makes each function easier to revisit without interrupting the main narrative: the module shows where the Windows code resides, the header and library explain how the component references it, and the callback address shows where the sensor’s own code actually begins.
This article establishes sensor contracts and their correlation limits. EDR Neutralization continues with a narrower case: deciding when a signed driver’s IOCTL interface actually grants an unauthorized termination primitive, without conflating a kernel import, reachability, and vulnerability.
References
- Microsoft, Driver Libraries and Headers and PE Format, Imports, and IAT.
- Microsoft, PsSetCreateProcessNotifyRoutineEx, PS_CREATE_NOTIFY_INFO, and DbgPrintEx.
- Microsoft, PsSetCreateThreadNotifyRoutine, PsSetCreateThreadNotifyRoutineEx, and PCREATE_THREAD_NOTIFY_ROUTINE.
- Microsoft, PsSetLoadImageNotifyRoutine, PLOAD_IMAGE_NOTIFY_ROUTINE, File Mapping, and LoadLibraryW.
- Microsoft, CmRegisterCallbackEx, REG_NOTIFY_CLASS, RegSetValueExW, and ZwSetValueKey.
- Microsoft, ObRegisterCallbacks, OB_OPERATION_REGISTRATION, OpenProcess, and DuplicateHandle.
- Microsoft, Object Handles and Managing Hardware Priorities and IRQL.
- Microsoft, FltRegisterFilter, FltStartFiltering, Minifilter Pre/Post Callback Ordering, and Load Order Groups and Altitudes.
- Microsoft, About Event Tracing, EventRegister, EventWrite, and EtwEventWrite, Internal Function.
- Microsoft, Antimalware Scan Interface.
- Microsoft,
logman query. - Microsoft, Windows Filtering Platform Architecture, WFP Components and Base Filtering Engine, Application Layer Enforcement, and WFP Operation.
- Microsoft,
netsh wfp. - Microsoft, Detours: Using Detours and CreateRemoteThreadEx.
- Microsoft, Device Input and Output Control with IOCTL and Protecting Anti-Malware Services.
- Microsoft, Recommended Driver Block Rules.
- Microsoft, Memory Integrity, VBS, and HVCI.
- Yosifovich, Ionescu, Russinovich, and Solomon, Windows Internals, Seventh Edition, Part 1, Microsoft Press, 2017.
- Russinovich, Solomon, and Ionescu, Windows Internals, Sixth Edition, Part 1, Microsoft Press, 2012. Consulted for historical architecture only.
- Altered Security, Evasion Lab Manual, learning objectives 9 to 11, 2025. Offline course material consulted for lab-specific debugging methodology.