EDR Primer
EDRs generally contain the following components:
- Self-Protection
- Hooking Engine
- Virtualization/Sandbox/Emulation
- Log/Alert Generation
- Network Comms
Quick Primer: Kernel Callbacks
EDRs also utilize kernel callbacks as exposed by the windows NT kernel, including:
- PsSetCreateProcessNotifyRoutine
- PsSetLoadImageNotifyRoutine
- PsSetThreadCreateNotifyRoutine
- ObRegisterCallbacks
- CmRegisterCallbacks
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 u nt!PsSetCreateProcessNotifyRoutine
We then follow any initial JMP (depending on the version of ntoskrnl.exe) to the main implementation of the function (e.g. nt!PspSetCreateProcessNotifyRoutine
)
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 nt!PspCreateProcessNotifyRoutine
).
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 F
to 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 nt!ViCreateProcessCallback
.
Hooking
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 lsass
process).
EDRs may also leverage sandbox, emulation or virtualization to run a binary in isolation and log API usage.
Common Weaknesses
The following list represents common weaknesses identified in multiple EDR solutions
Binary Padding
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.
Unmonitored APIs
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 MiniDumpWriteDump
or 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;
- Cloning
lsass
is permitted; and - Dumping memory of non-sensitive processes are permitted
By cloning lsass
, the cloned lsass
process doesn’t get the same protections by the EDR as the original lsass
process, thereby permitting dumping of the lsass
clone.
This can be performed using the Windows APIs, or by using tools like ProcDump.exe
with the -r
flag.
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 word.exe
spawns 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 word.exe
.
There are other techniques too (e.g. leveraging RPC) that may also be applicable to break process-tree based detections.
Attacking EDRs
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.