Filter-Mute Operation: Investigating EDR Internal Communication
For our annual internal hacker conference dubbed SenseCon in 2023, I decided to take a look at communication between a Windows driver and its user-mode process. Here are some details about that journey.
TL;DR
Attackers could use Windows kernel R/W exploit primitive to avoid communication between EDR_Driver.sys and its EDR_process.exe. As a result some EDR detection mechanisms will be disabled and make it (partially) blind to malicious payloads. This blogpost describes an alternative approach which doesn’t remove kernel callbacks and gives some recommendations for protecting against this “filter-mute” attack.
For our annual internal hacker conference dubbed SenseCon in 2023, I decided to take a look at communication between a Windows driver and its user-mode process. Here are some details about that journey.
TL;DR
Attackers could use Windows kernel R/W exploit primitive to avoid communication between EDR_Driver.sys and its EDR_process.exe. As a result some EDR detection mechanisms will be disabled and make it (partially) blind to malicious payloads. This blogpost describes an alternative approach which doesn’t remove kernel callbacks and gives some recommendations for protecting against this “filter-mute” attack.
Overview of EDR_process.exe and EDR_Driver.sys roles
The first question that comes to mind is how does a EDR application (EDR_Process.exe) communicate with its EDR driver (EDR_Driver.sys)?
Before doing research we must know some EDR basics; how does an EDR agent hook / inject its own DLL during process creation?
The Process Injection via Callbacks schemas taken from EDR Observations made by Christopher Vella is a good summary.
I have added some comments on what’s happening:
- EDR_Driver.sys can subscribe to several kinds of kernel notifications. You can imagine those notifications are like “newsletters” you subscribe to on the Internet and receive by email from a website. For example EDR_Driver.sys could subscribe to the “new process creation” notification service using the Windows API named
PsSetCreateProcessNotifyRoutinewhich then, for each process created by the system, the driver will receive information about it (parent PID, command line, etc)
- The user double-clicks on malware.exe
- Windows calls the CreateProcessW API in order to load malware.exe into memory
- EDR_Driver.sys is notified that malware.exe ****will be**** spawned.
- EDR_Driver.sys sends a log to EDR_Process.exe saying “Hey! A new process called malware.exe will be started soon.”
- EDR_process.exe can choose to take action (or not): “Ok I will monitor this process by creating hooks in its ntdll.dll”
- When malware.exe runs, it calls the Windows API. Thanks to the hooks in place, EDR_Process.exe knows which APIs are called and can deduce what this malware.exe is doing
We could take the piece of code below from ired.team as an example of malware.exe.
Once hooks are in place, the EDR agent (EDR_process.exe) can monitor / analyse malware.exe. Here is an example of actions it could take:
1. EDR_Process.exe sees the following Windows API calls that are called by malware.exe:
- OpenProcess
- VirtualAllocEx
- WriteProcessMemory
- CreateRemoteThread
2. EDR_Process.exe classifies this API call sequence as “malicious” and blocks (kills) the process.
3. EDR_Process.exe sends a log to the EDR_C2 (security console) saying “Hey, malware.exe process spawned and is classified as malicious”.
Note: this is a common EDR flow and not the only way it could work, for example EDR_Process.exe may only send telemetry data and let the EDR_C2 decide if it’s malicious and the action to be applied (block or not).
If the EDR vendor or the security team operators (aka blueteam) configured a “block if malicious” rule in the EDR Security Console then the malware.exe process is killed by EDR_Process.exe (or EDR_Driver.sys). Other countermeasures are also available, for example:
- the Windows host could be remotely isolated from the network
- malware.exe file or memory dump could be downloaded for analysis / reversing
- security analyst could run commands on the Windows host (from the security console) for investigation purpose
- …
This point is an important one; the more experienced the blueteam is in creating custom rules, the more difficult for attackers to evade or move laterally into the network without being caught!
Now before digging into internal communication, I want to take a step back and simplify the EDR behaviour. Internal communication (blue arrows) and external communication (yellow arrows) of the EDR_Process.exe could be visualised with a simple overview:
Digging into EDR internal communication
From Windows kernel memory space, EDR_Driver.sys could use several Windows Kernel API’s (callbacks) to monitor and then block the malicious system activities. For example the API PsSetCreateProcessNotifyRoutine routine could be used to generate the following “monitoring logs” messages thanks to the kernel callback mechanism:
– Log = new process created (PID 5376) with cmd Line C:\notepad.exe
From usermode memory space, EDR_Process.exe could send action requests to the driver and receive information from it. For example an “Action request” coming from EDR security console could be:
– Action = denylist C:\notepad.exe
In the figure below I tried to map common Windows Kernel callbacks used for monitoring purposes.
The question which comes to mind after making this summary was how to avoid communication between EDR_process.exe and EDR_driver.sys?
Blinding EDR using known techniques
The most common techniques for blinding EDR sensors are:
- Removing the DLL hooks (userland)
- Removing the kernel callbacks (kernel land)
Because we only focus on kernel part of EDR, here is a visualisation on what happens when you remove kernel callbacks:
BEFORE zero out of the EDR callback address:
AFTER zero out of the EDR callback address:
We won’t go into details on this topic, it’s covered in the blogpost Blinding EDR On Windows from Zach Stein.
But you may notice in the figure below that each time you zero out the EDR callback address it means no more notifications (no “newsletter”) will be sent from Windows to EDR_Driver.sys. In the end, no event log will be sent to EDR_Process.exe (and security analyst console) anymore!
Blinding EDR using an alternative approach
During my research on this topic I was wondering how to avoid communication between EDR_process.exe and EDR_driver.sys without any callback modification ? Could we prevent EDR_process.exe and EDR_Driver.sys from exchanging “messages”?
Like I said before, we want to stay on the kernel side of the story. We could imagine this other approach using this graphical representation:
While I was trying to investigate using Windbg, Yarden Shafir wrote an awesome blog on Investigating Filter Communication Ports which really helped. I discovered some Windows data structures being manipulated during communication setup between an application and a driver.
The data structure named FLT_SERVER_PORT_OBJECT drew my attention because it seemed to contain interesting fields, see if you agree:
When I saw this, the first question which came to my mind was what could happen if we set MaxConnections to zero?
This data structure is initialised using the Windows Drivers API named FltCreateCommunicationPort:
NTSTATUS FLTAPI FltCreateCommunicationPort(
[in] PFLT_FILTER Filter,
[out] PFLT_PORT *ServerPort,
[in] POBJECT_ATTRIBUTES ObjectAttributes,
[in] PFLT_CONNECT_NOTIFY ConnectNotifyCallback,
[in] PFLT_DISCONNECT_NOTIFY DisconnectNotifyCallback,
[in, optional] PFLT_MESSAGE_NOTIFY MessageNotifyCallback,
[in] LONG MaxConnections
Microsoft documentation gives the following information:
What could we deduce? If we are able to reset MaxConnections to zero, it will only prevent new connections from happening. Let’s go for the following attack plan:
- Step 1: reset MaxConnections value
[...]