EDRs generally contain the following components:
Quick Primer: Kernel Callbacks
EDRs also utilize kernel callbacks as exposed by the windows NT kernel, including:
These callbacks may be used by kernel drivers such that when an event happens (process creation, registry modifications, handle creations, etc) the kernel driver is notified (pre or post op) and may interfere with the operation or result.
A common usage of this is for EDRs to be notified of process creations and inject their own userland DLLs (usually to hook NTDLL) in the newly created processes before they execute.
Additionally EDRs may intercept handle creation events and block those that occur on their protected processes (for example, in self-protection mode they may prevent other processes from obtaining handles to their processes).
Quick Primer: Disassembling Callbacks
Callbacks can be enumerated and disassembled on Windows via Kernel Debugging (or in-kernel disassembling e.g. by compiling a kernel driver with disassembly functionality such as via Capstone).
If using KD/Windbg, we can leverage public symbols to first disassemble the function
PsSetCreateProcessNotifyRoutine with the command
We then follow any initial JMP (depending on the version of ntoskrnl.exe) to the main implementation of the function (e.g.
Continue disassembling the function and look for a
LEA instruction on the callback array symbol. Callbacks are stored in arrays of an undocumented
EX_CALLBACK structure from which we can discover the function pointer that points to the actual callback function registered for a particular driver.
As shown above, the callback array used in the
LEA instruction on the last line (loaded into
R13) also has the symbol
Next, we dump the contents of the callback array:
Here the command
dq nt!PspCreateProcessNotifyRoutine was used to dump the contents of the callback array symbol as quadwords.
We can resolve the callback function registered for each of these callback entries by changing the last byte of an entry from
8, this will contain a pointer to the function registered to the callback:
Above, we chose the first entry
ffff998ae70d3b8f, then we change the last byte such that the value becomes
ffff998ae70d3b88 then we disassembled it as instructions using the command
u poi(ffff998ae70d3b88) discovering that this function is the callback function with the symbol
Hooking techniques are commonly used by EDRs to intercept userland functions for API monitoring or blocking. The following demonstrates a common use for hooking where an EDR registers for process callback notifications and injects a DLL into each newly created process, this DLL then hooks ntdll.dll functions to block/alert/monitor malicious behaviour (e.g. blocking calls to
NtReadVirtualMemory where the target process handle represents the
EDRs may also leverage sandbox, emulation or virtualization to run a binary in isolation and log API usage.
The following list represents common weaknesses identified in multiple EDR solutions
Scanning and emulation of a binary may be used to detect malicious behaviour, however many EDRs (and Ads) have file size limitations on the file to analyse.
As a result, by appending junk to the end of a binary until it is roughly 100mb in size may be enough to prevent the EDR/AV from analysing it (and due to the PE32/PE32+ format, junk appended at the end of an executable will not affect its execution).
This is effective against products that heavily rely on an emulation & scanning layer to detect threats.
Typical APIs used for malicious activity (e.g. combinations of VirtualAllocEx, WriteProcessMemory & CreateRemoteThread) may be alerted on by EDRs for process injection.
However, performing the same or similar actions with different sets of APIs may evade EDRs and go unnoticed.
For example, in the case of dumping sensitive process memory (like that from the
lsass process) EDRs may not alert on handle creation of the target process, but may instead alert when an api like
ReadProcessMemory is called on the target.
However, if we clone the target process with
PssCaptureSnapshot and dump the memory of the cloned
lsass process instead, we may bypass such detections. This stems from the following main factors:
Simple handle creations on a target process are permitted;
lsassis permitted; and
Dumping memory of non-sensitive processes are permitted
lsass, the cloned
lsass process doesn’t get the same protections by the EDR as the original
lsass process, thereby permitting dumping of the
This can be performed using the Windows APIs, or by using tools like
ProcDump.exe with the
Another example is DLL injection via Windows hooks (e.g. leveraging
SetWindowsHookEx api), this method of process injection does not rely on the typical Windows injection methods of opening a process, writing into the process memory and then spawning a new thread, and can bypass typical process injection detections.
Breaking Process Trees
EDRs leverage process trees for detecting malicious behaviour (e.g. alerting if
cmd.exe), however we can leverage COM objects such as
C08AFD90-F2A1-11D1-8455-00A0C91F3880 that exposes the
ShellExecute function to spawn arbitrary processes under the
explorer.exe process, even from within VBScript running under
There are other techniques too (e.g. leveraging RPC) that may also be applicable to break process-tree based detections.
EDR weaknesses also include certain design flaws that make them susceptible to subversion.
For example, as shown above, userland hooking may be key to an EDR’s detection capabilities (such that without it, the product may be rendered useless).
EDRs that hook userland APIs via hooking ntdll.dll may be subverted by loading a fresh copy of ntdll.dll into the process and redirecting (via hooks) our API calls to the newly loaded (and unhooked by EDR) ntdll.
This technique along for bypassing EDR hooks may be enough to then perform malicious actions (like
lsass dumping) without any alerts or detections.
EDRs also expose a lot of attack surface due to their massive codebase (drivers, IPC, support for various file formats) that may make them susceptible to a range of 0-day vulnerabilities, as such proper testing of these products should be a priority.