Recently I’ve had the opportunity to assess multiple EDR & AV solutions including: CrowdStrike, Defender ATP, McAfee, Carbon Black Defence, and multiple others.
As a result of this research I wanted to discuss areas of weakness in these products and question why they miss a lot of techniques and chains of attacks, and question the design decisions of some of the products. Note that the weaknesses I talk about will not point out the corresponding product that falls to this issue, however a lot of the issues found are shared between the products.
Firstly lets go over the general capabilities of EDR products.
Generally EDRs will have the following components:
- Hooking engine
- Log/Alert generation
- Network comms
Self-Protection is self-explanatory, this may include marking processes as PPLs and preventing process handle creation via kernel callbacks.
Quick Primer: Kernel Callbacks
The kernel ntoskrnl.exe exports multiple callback functions, e.g:
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 and may interfere with the operation.
An example use of this by EDRs are to log process creations and inject their own DLLs (including their hooks) into newly created processes.
Additionally EDRs may intercept handle creation events and block any handle creations that occur on their processes (an example of their self-protection capabilities).
Quick Primer: Disassembling Callbacks
Callbacks can be enumerated and disassembled on windows via kernel debugging.
First, start the kernel debugging session (via kd.exe or windbag.exe), it does not matter if its a remote or local debugging session.
Next, disassemble a chosen callback function (e.g. “u Nt!PsSetCreateProcessNotifyRoutine”).
Follow any initial JMP to the main implementation (PsSetCreateProcessNotifyRoutine may be a stub for the actual implementation in other function).
Continue disassembling the function and look for a “LEA” commands on the callback array. Callbacks are stored in arrays of an undocumented “EX_CALLBACK” structure from which we can discover the function pointer that points to the function registered to be called when the callback is triggered.
Next, dump the contents of the callback array.
Finally, if we set the last byte of an entry in the array from “f” to “8”, this will contain a pointer to the function registered to the callback from that particular entry, we can test this with one of the callback entries and we will see the callback function:
EDR Primer cont.
Hooking engine relates to the EDRs visibility into operations on the system. For API call monitoring some EDRs rely on user land hooks in system dlls (e.g. NTDLL.dll) which redirect into the EDRs own DLL to monitor/log API calls, check parameters and potentially deny the operation. The design decision made to hook in user-land stems from PatchGuard preventing reliable hooking of the API calls in kernel-land.
As above, the hooking is typically driven by the process creation callbacks in kernel-land from the EDR’s driver, following by the EDR’s process opening the target process and injecting/patching the process.
The virtualisation/sandbox/emulation component of EDRs are another way for EDRs to monitor operations performed by an executable. EDRs may emulate the windows environment or run the target in a sandbox to detect API usage, registry and disk modifications, and other behaviours that may trigger an alert.
Log/alert generation is another self-explanatory component, certain EDRs are designed to be entirely in-memory with their alerts (downside being alerts are lost on reboot) and will frequently transfer the alert data over the network to their server component.
Other EDRs may write these logs to disk and frequently read and send them to their server components over the network (One recent issue I found in a prominent EDR was weak permissions on these alert files, allowing them to be overwritten by a low-privileged user!).
Now I’ll get to listing multiple weaknesses discovered during testing these security products, with a focus on post-exploitation and other host-based malicious activity.
One method which surprisingly worked on multiple vendors was binary padding. By simple enlarging a binary (e.g. appending junk to the binary until its large, at ~100mb) would prevent the binary from being analysed in multiple products sandbox/virtualization environment.
What does this mean? It means I could run an otherwise unmodified mimikatz on the host and dump LSASS credentials without generating any alerts!
Additionally, as the file is analysed on execute, an attacker may send a small executable to the target, append junk to the file to enlarge it, then execute it to bypass alerting for these products.
The reliance on the virutalization/emulation layer by the affected products and their inability to analyse large files created a rather generic method to bypass their alerting for certain actions, which was surprising.
Typical APIs used for malicious activity (e.g. combination of VirtualAllocEx, WriteProcessMemory & CreateRemoteThread) will be alerted on by these products for process injection, or credential dumping (in the case of MiniDumpWriteDump targeting LSASS, etc).
However, performing the same or similar actions with different API calls can lead to malicious actions being performed with no alerts generated.
For example, in the case of dumping LSASS, lots of EDRs don’t alert when a process handle is created on LSASS, they only alert once they detect memory scraping on the process (via MiniDumpWriteDump or multiple ReadProcessMemory calls).
To get around this, simply cloning LSASS with PssCaptureSnapshot then dumping the memory of the cloned process (e.g. via MiniDumpWriteDump) will bypass a lot of these products, this stems from two main factors:
- Operations other than dumping are permitted on LSASS
- Dumping memory of other, non-LSASS processes are permitted
This functionality is easy to code up yourself, additionally ProcDump.exe supports this with the “-r” flag.
For the example of DLL or process injection, most security products do not detect injection via SetWindowsHookEx, this can be used to inject an arbitrary DLL into any process that has a window.
Additionally, replacing “VirtualAlloc” and “WriteProcessMemory” with “NtCreateSection” and then mapping the section into your own process memory with READWRITE permissions to fill the buffer with shell code, then map the section into the target process with PAGE_EXECUTE permissions (leveraging “NtMapViewOfSection”) acts as a commonly undetected replacement for “VirtualAlloc” and “WriteProcessMemory”.
Doing the above then using “RtlCreateUserThread” or similar against the target process, pointing at the newly mapped section can result in process injection that is undetected by a lot of these security products.
While these techniques aren’t really new, they still work without alerting simply because they don’t follow the more common API patterns.
Another feature of these security products is detecting malicious process creations stemming from phishing documents (e.g. word macros). They look for suspicious parent-child process relationships (e.g. winword.exe spawning cmd.exe).
However, thanks to COM, we can craft a word macro that will spawn a process under the context of explorer.exe (which blends in with normal user activity).
The COM object “C08AFD90-F2A1-11D1-8455-00A0C91F3880” exposes the “ShellExecute” function and can be used to execute arbitrary processes with arbitrary command lines from the explorer.exe process, and may be driven via a word macro.
This evades the process-tree based detection security products rely on and have been used during phishing exercises to evade EDRs.
The last area I want to touch on relates to weaknesses in the designs of certain EDRs.
Certain EDRs rely solely on user-land hooks to detect a lot of malicious actions, however by simply loading up a fresh copy of NTDLL.dll from disk and repatching the current process, or by using inline syscalls we can bypass their hooks.
Unfortunately (for certain EDRs) this technique alone is enough to then perform LSASS dumping and process injection techniques without any alerting at all, even via your usual “VirtualAllocEx”, “WriteProcessMemory” and “CreateRemoteThread” APIs.
Other products have weak permissions on their own drivers or registry service entries, allowing an administrator user to disable, rename or manipulate these items causing the EDR to either die, or not start on reboot.
A very recent vulnerability I discovered with one of these products allows a low-privileged user to completely prevent alerting due to a weak file permission, and another vulnerability I discovered allows a low-privileged user to escalate privileged to Local System and proceed to disable the security product.
The scariest part of this was the relative ease of finding these issues with a bit of research time, while these products do provide a lot of functionality on top of your basic AVs, they still have a long way to go. Especially when some of the identified issues are old and rather basic.