Back to Blog

Analyzing Windows Kernel Callback Manipulation for EDR Evasion

Analyzing Windows Kernel Callback Manipulation for EDR Evasion

In the modern endpoint security landscape, the battleground has shifted from user-mode API hooking to the kernel. As Endpoint Detection and Response (EDR) solutions have matured, they have moved deeper into the Windows kernel to gain unmaskable visibility. By leveraging kernel callbacks, EDR drivers can intercept critical system events-such as process creation, thread injection, and registry modifications-before they even reach user-mode space.

For the sophisticated adversary, this creates a fundamental problem: how do you perform malicious activity when the very foundation of the OS is reporting your every move to a security agent? The answer lies in the manipulation of these callback mechanisms. This post explores the technical intricacies of kernel callback manipulation, the mechanics of how EHDs (Endpoint Detection Hooks) function, and the methods used to subvert them.

The EDR Visibility Engine: Understanding Callbacks

Windows provides several legitimate, documented APIs that allow drivers to register for notifications regarding system events. These are the "eyes and ears" of an EDR.

1. Process and Thread Notifications

Functions such as `PsSetCreateProcessNotifyRoutine` and `PsSetCreateThreadNotifyRoutine` allow a driver to register a callback that the kernel executes every time a new process or thread is instantiated. This is the primary mechanism used by EDRs to perform "on-access" scanning of new processes.

2. Object Callbacks (The `ObRegisterCallbacks` Mechanism)

This is perhaps the most critical component for modern EDRs. Using `ObRegisterCallbacks`, a driver can register for notifications on specific object types, most notably `PsProcessType` and `PsThreadType`. This allows the EDR to intercept `OpenProcess` or `OpenThread` requests. By inspecting the `DesiredAccess` mask, the E2DR can strip away dangerous permissions (like `PROCESS_TERMINATE` or `PROCESS_VM_WRITE`) from a handle, effectively preventing even an administrative user from killing the EDR's protected process.

3. Registry Callbacks

Through `CmRegisterCallbackEx`, drivers can intercept registry operations. This enables the EDR to monitor for persistence mechanisms, such as the modification of "Run" keys or the creation of new services, in real-time.

The Mechanics of Manipulation

To evade detection, an attacker must either prevent the callback from firing or manipulate the data being passed to the callback. Because these callbacks reside in kernel memory, manipulation requires kernel-mode execution (e.g., via a vulnerable driver exploit).

Technique 1: Callback Array Nullification

The kernel maintains internal, unexported arrays of function pointers for process and thread notifications. For example, the `PspCreateProcessNotifyRoutine` array holds the pointers to all registered `PsSetCreateProcessNotifyRoutine` callbacks.

While the API to register a callback is exported, the underlying array is not. An adversary can use pattern scanning (signature searching) to locate the base address of these unexported arrays in kernel memory. Once located, the attacker can iterate through the array and zero out the entries belonging to the EDR driver.

Conceptual Workflow:

  1. Locate `PspCreateProcessNotifyRoutine`: Use a known pattern or scan for the `PsSetCreateProcessNotifyRoutine` export to find the internal array.
  2. Identify EDR Entry: Iterate through the array and compare the function pointers against the known address range of the EDR driver.
  3. Overwrite: Disable the Write Protect (WP) bit in the `CR0` register to allow writing to read-only kernel memory, then nullify the pointer.

Technique 2: Stripping the Callback List (Object Callbacks)

`ObRegisterCallbacks` is slightly more complex because it doesn't just use a simple array; it uses a linked list of `OB_CALLBACK_REGISTRATION` structures. Each structure contains a `CallbackList` head.

An attacker can traverse this linked list. By identifying the node that corresponds to the EDR's driver, the attacker can perform a "list unlinking" operation (similar to how one might remove a node from a doubly-linked list in C). Once the EDR's node is unlinked, the kernel no longer traverses the EDR's callback during handle creation, rendering the EDR blind to handle-based attacks like `OpenProcess`.

Technique 3: Inline Hooking (The Interceptor)

If unlinking or nullification is too risky (due to PatchGuard), an attacker may opt for inline hooking. Instead of removing the callback, the attacker overwrites the first few bytes of the EDR's callback function with a `JMP` instruction.

The execution flow becomes:

`Kernel Event` $\rightarrow$ `EDR Callback` $\rightarrow$ `Attacker's Trampoline` $\rightarrow$ `Original EDR Logic (with modified parameters)`.

This allows the attacker to "sanitize" the telemetry. For example, if the EDR is looking for a specific suspicious command line, the hook can modify the buffer in memory to remove the malicious argument before the EDR's logic processes it.

The PatchGuard Barrier: Kernel Patch Protection (KPP)

The primary obstacle to these techniques is Kernel Patch Protection (KPP), commonly known as PatchGuard. KPP periodically checks the integrity of critical kernel structures, including the GDT (Global Descriptor Table), IDT (Interrupt Descriptor Table), and the system call tables.

If KPP detects that a protected structure or an unexported array like `PspCreateProcessNotifyRoutine` has been tampered with, it will immediately trigger a Bug Check (BSOD) `CRITICAL_STRUCTURE

Conclusion

As shown across "The EDR Visibility Engine: Understanding Callbacks", "The Mechanics of Manipulation", "The PatchGuard Barrier: Kernel Patch Protection (KPP)", a secure implementation for analyzing windows kernel callback manipulation for edr evasion depends on execution discipline as much as design.

The practical hardening path is to enforce host hardening baselines with tamper-resistant telemetry, behavior-chain detection across process, memory, identity, and network telemetry, and continuous control validation against adversarial test cases. This combination reduces both exploitability and attacker dwell time by forcing failures across multiple independent control layers.

Operational confidence should be measured, not assumed: track time from suspicious execution chain to host containment and reduction in reachable unsafe states under fuzzed malformed input, then use those results to tune preventive policy, detection fidelity, and response runbooks on a fixed review cadence.

Related Articles

Explore related cybersecurity topics:

Recommended Next Steps

If this topic is relevant to your organisation, use one of these paths: