# ETW: How Windows 2000's Performance Hack Became the EDR Substrate

> Event Tracing for Windows is the kernel-buffered observability bus every modern Windows EDR consumes. This is the architecture, the attacks, and the one provider that survives them.

*Published: 2026-05-11*
*Canonical: https://paragmali.com/blog/etw-how-windows-2000s-performance-hack-became-the-edr-substr*
*License: CC BY 4.0 - https://creativecommons.org/licenses/by/4.0/*

---
<TLDR>
Event Tracing for Windows is the high-rate, kernel-buffered observability bus that every modern Windows EDR consumes. A 2007-era architectural decision -- letting eight sessions read the same provider concurrently -- is what makes multi-vendor coexistence possible on a single host. Microsoft's `Microsoft-Windows-Threat-Intelligence` provider, gated behind Protected Process Light and an ELAM-signed Antimalware certificate since the Windows 10 RS-era, fires from the kernel side of memory-modifying syscalls and survives the user-mode `EtwEventWrite` patch class that defined red-team tradecraft from 2020 to 2022. The remaining attack surface -- BYOVD-driven kernel tampering -- is structurally narrowed by the Vulnerable Driver Blocklist enabled by default since Windows 11 22H2, with the residual sub-microsecond-payload gap remaining as ETW's irreducible "observation, not enforcement" limit.
</TLDR>

## 1. Why didn't the patch silence Defender?

A red-team operator drops onto a 2026 [Defender](https://paragmali.com/blog/the-defenders-dilemma-how-microsoft-won-the-antivirus-war-it/)-protected box and runs the move that worked five years ago. They locate `ntdll!EtwEventWrite` in the calling process, write the byte `0xC3` over the function prologue, and the calling process now silently fails to emit user-mode ETW events. The .NET CLR provider goes dark. `Invoke-Mimikatz` loads from `execute-assembly` without lighting up `Microsoft-Windows-DotNETRuntime`. Defender catches the [credential dump](https://paragmali.com/blog/the-empty-hash-credential-guard-the-lsaiso-trustlet-and-the-/) anyway, four seconds later, and the operator is on a SOC analyst's screen before the shellcode finishes running.

The patch worked. The .NET tracing provider in that process is mute. Attach a debugger and disassemble the function prologue: the first byte is now `0xC3`, the [near-return opcode](https://www.felixcloutier.com/x86/ret) [@felixcloutier-ret], and any caller falls straight back to its return address before producing a single event. The technique is the one [Adam Chester documented in March 2020](https://blog.xpnsec.com/hiding-your-dotnet-etw/) [@xpn-hiding-dotnet], and to a generation of red teamers it has functioned as a near-universal ETW evasion ever since.

So why did Defender still fire?

Because Defender does not consume `Microsoft-Windows-DotNETRuntime` to detect a credential dump. It consumes [`Microsoft-Windows-Threat-Intelligence`](https://fluxsec.red/event-tracing-for-windows-threat-intelligence-rust-consumer) [@fluxsec-eti] -- a provider whose GUID is `{f4e1897c-bb5d-5668-f1d8-040f4d8dd344}`, whose events fire from inside the kernel side of memory-modifying syscalls, and whose producer the user-mode patcher cannot reach. The patch operated on a `ntdll` trampoline. The signal Defender used was emitted from a different layer entirely.

> **Key idea:** Modern Windows EDR is layered on ETW, and the layers fail under different attacks.

That single asymmetry -- one provider goes dark to a one-byte patch, another fires from a place the patcher cannot touch -- is the spine of this article. Around it sits a 26-year story of one Microsoft team accidentally building the substrate of every modern Windows endpoint security product.

<Definition term="ETW (Event Tracing for Windows)">
A high-rate, kernel-buffered tracing facility built into Windows since 2000. Components called *providers* emit events tagged with a GUID; *controllers* configure trace sessions; *consumers* subscribe to live event streams or read recorded `.etl` files. ETW was designed for low-overhead developer diagnostics; it was retrofitted into the security-telemetry substrate that all modern Windows EDR products consume.
</Definition>

<Definition term="EDR (Endpoint Detection and Response)">
A class of endpoint security product that ingests behavioural telemetry (process creation, image load, memory allocation, network connection, registry change), correlates it against detection logic, and produces alerts and response actions. On Windows, the dominant EDRs (Microsoft Defender for Endpoint, CrowdStrike Falcon, SentinelOne, Elastic Defend, Wazuh, Sysmon-plus-SIEM) all build on ETW or on the same kernel callbacks ETW exposes to the user-mode tier.
</Definition>

To understand why a one-byte patch silences one provider but not another, we have to go back to a Windows 2000 design decision about per-CPU ring buffers.

## 2. ETW in Windows 2000: the performance problem that started it all

Imagine a 1999 network-driver author. A customer's NT4 production server is corrupting packets under load and the only available instrumentation is `DbgPrint`. Each call serialises through a kernel debug port, costs measurable percentage points of CPU on a busy box, and ships data to whoever happens to have the kernel debugger attached. The customer says no. The bug reproduces only at production traffic levels. You cannot ship enough printf-debugging through a debug port to find it.

That is the engineering pain Insung Park and Ricky Buch's team was solving when ETW shipped with Windows 2000. Their design moves -- recorded years later in the [definitive April 2007 MSDN Magazine article on the Vista upgrade](https://learn.microsoft.com/en-us/archive/msdn-magazine/2007/april/event-tracing-improve-debugging-and-performance-tuning-with-etw) [@ms-park-buch-2007] -- still define the architecture two and a half decades later.

The first move was per-CPU ring buffers. A producer on CPU 7 writes to CPU 7's buffer with no lock contention against producers on other CPUs. Hot-path tracing on a 64-core machine does not serialise. The kernel allocates [at least two buffers per logical processor](https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-event_trace_properties) [@ms-event-trace-props] so a producer can keep writing while a writer thread drains the previous buffer.

The second move was an asynchronous writer thread. The producer never blocks on disk I/O. It writes to its CPU's buffer and returns. A separate kernel thread drains buffers to file or hands them to a real-time consumer. ETW pushes the latency tax onto the consumer and the storage path, never onto the producer's hot loop.

The third move was dynamic enable and disable. Park and Buch describe the resulting capability in one sentence:

<PullQuote>
"ETW gives you the ability to enable and disable logging dynamically, making it easy to perform detailed tracing in production environments without requiring reboots or application restarts." -- Park & Buch, *MSDN Magazine*, April 2007 [@ms-park-buch-2007]
</PullQuote>

That sentence is the entire reason ETW could later become the EDR substrate. A producer compiles its trace points into shipping code at low cost; a controller flips them on at runtime when somebody actually wants the data. Without that property, you cannot build a security product that ships universal kernel tracing on a billion endpoints.

The fourth move was the trichotomy of [providers, controllers, and consumers](https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/event-tracing-for-windows--etw-) [@ms-etw-wdk]. Microsoft did not write ETW as an internal-only facility. From the start, third parties could write providers (driver authors instrumenting their own code), controllers (performance tools starting and stopping sessions), and consumers (analyzers reading event streams). The architecture is open by design.

<Definition term="Provider">
A component that emits ETW events, identified by a GUID. A provider is registered with the system at runtime via the `EventRegister` API (or its predecessor `RegisterTraceGuids` for classic providers) and emits events via `EventWrite` (or `TraceEvent`). Providers ship inside Windows itself, inside Microsoft applications, and inside any third-party binary that wants to expose tracing.
</Definition>

<Definition term="Controller">
A component that creates, configures, enables, and stops trace sessions. Controllers select which providers a session subscribes to and at which level and keyword bitmask. The Windows Performance Recorder, `logman`, `xperf`, and every EDR's session-management code are controllers.
</Definition>

<Definition term="Consumer">
A component that reads events from a session in real time or from an `.etl` file on disk. Consumers register a callback that the system invokes once per delivered event. The Windows Performance Analyzer, the krabsetw library, SilkETW, and every EDR's sensor process are consumers.
</Definition>

<Mermaid caption="The ETW core architecture introduced in Windows 2000: a controller starts a session, providers register and emit events into per-CPU ring buffers, and a writer thread asynchronously drains buffers to file or to a real-time consumer.">
flowchart LR
    Ctl[Controller<br/>StartTrace + EnableTrace] --> Sess[Trace Session<br/>per-session buffer pool]
    P1[Provider on CPU 0] --> CPU0[CPU 0 buffer]
    P2[Provider on CPU 1] --> CPU1[CPU 1 buffer]
    P3[Provider on CPU N] --> CPUN[CPU N buffer]
    CPU0 --> WT[Writer thread<br/>asynchronous drain]
    CPU1 --> WT
    CPUN --> WT
    Sess -.governs.-> CPU0
    Sess -.governs.-> CPU1
    Sess -.governs.-> CPUN
    WT --> File[(.etl file)]
    WT --> RT[Real-time consumer<br/>OpenTrace + ProcessTrace]
</Mermaid>

The original Windows 2000 implementation [supported 32 trace sessions running simultaneously](https://learn.microsoft.com/en-us/windows/win32/etw/event-tracing-sessions) [@ms-etw-sessions], a number Microsoft later raised to 64 globally. ETW was framed as a developer-diagnostics facility -- the [Windows Driver Kit primary still describes it that way](https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/event-tracing-for-windows--etw-) [@ms-etw-wdk] -- and the security-telemetry use case did not exist for almost a decade.

But the design choices that made ETW good for low-overhead production diagnostics turn out to be exactly the design choices a security telemetry bus needs. Per-CPU buffers solve the multi-core throughput problem. Asynchronous writes solve the producer-latency problem. Dynamic enable solves the always-shipping-but-mostly-off problem. The trichotomy solves the third-party-extensibility problem. Twenty-five years later, every modern Windows EDR consumes telemetry through the same four primitives.

<Sidenote>
Windows 2000's 32-session global cap [@ms-etw-sessions] is preserved verbatim on the modern Microsoft Learn page: "Windows 2000: Supports only 32 event tracing sessions." The cap doubled to 64 in later releases and has stayed there ever since.
</Sidenote>

The 2000-era design carried one limit, however, that turned out to matter for security: only one trace session could enable a classic provider at a time. The next ten years would be defined by the consequences.

## 3. The MOF era: one session, one steal, one decade of coexistence pain

In 2005, a third-party performance monitor that registered a classic provider could find itself silently disabled the moment Microsoft's `wprui.exe` started its own session against the same provider GUID. The first session got no error. It just stopped receiving events. That second-consumer-steals-first behavior is the architectural fact of the entire 2000-2007 era.

Microsoft Learn still documents the rule in one sentence:

> **Note:** "Up to eight trace sessions can enable and receive events from the same manifest-based provider. However, only one trace session can enable a classic provider. If more than one trace session tries to enable a classic provider, the first session would stop receiving events when the second session enables the provider." -- [Microsoft Learn, Configuring and Starting an Event Tracing Session](https://learn.microsoft.com/en-us/windows/win32/etw/configuring-and-starting-an-event-tracing-session) [@ms-etw-config]

That single rule made multi-EDR coexistence on classic providers structurally impossible. If Defender's predecessor and a third-party HIPS both wanted real-time process events from the same classic provider, they had to fight for it. The loser got silence with no notification.

The provider class involved was *MOF-based*, named after the schema language that described its events.

<Definition term="MOF (Managed Object Format)">
The schema description language inherited from WBEM (Web-Based Enterprise Management). For ETW, MOF files describe each event a classic provider can emit -- field names, types, tasks, opcodes -- and are compiled into the WMI repository at install time using `mofcomp`. Consumers decode events by querying the WMI repository for the matching MOF schema.
</Definition>

<Definition term="Classic provider">
A synonym for *MOF provider*. The original ETW provider class introduced in Windows 2000. Registered with `RegisterTraceGuids`, emits events via `TraceEvent`, decoded against a MOF schema in the WMI repository. Capped at one trace session per provider.
</Definition>

The MOF model was workable for a single-consumer world. A performance-tuning team running an in-house tool could enable the provider, capture, and disable. As the substrate of a security stack with multiple agents on the same host, it could not work. The mid-2000s had not yet produced a "multiple agents on the same host" world, so the limit did not bite immediately. By 2007 it would.

| Class | Era | Schema location | Sessions/provider | Adoption in 2026 |
|-------|-----|------------------|-------------------|-------------------|
| MOF / classic | 2000 | WMI repository | 1 | Niche; mostly NT Kernel Logger |
| WPP | 2002 | `.pdb` (TMF) | 1 | Pervasive inside Windows internals |
| Manifest-based | 2007 (Vista) | XML manifest | 8 | Dominant for security telemetry |
| TraceLogging | 2015 (Win10) | Inline (TLV) | 8 | Rising for new app/service code |

A handful of classic providers survived the 2007 transition and are still significant. The most important is the [NT Kernel Logger](https://learn.microsoft.com/en-us/windows/win32/etw/event-tracing-sessions) [@ms-etw-sessions], the special-purpose system session that captures high-throughput kernel events: file I/O, disk I/O, registry operations, network packets. On most consumer SKUs it remains the only path to those event streams at line rate. Sysmon and most kernel-level diagnostics tools use the NT Kernel Logger or its modern descendants.

<Sidenote>
The NT Kernel Logger is a system reserved logger. There is exactly one of it on a host, and the kernel itself owns the buffers. Tools that want kernel disk, file, registry, or network events at high throughput typically subscribe through it rather than through manifest providers. This is why a host can have eight `Microsoft-Windows-Kernel-File` consumers but cannot easily have two simultaneous full-fidelity disk I/O traces.
</Sidenote>

By 2007 Microsoft knew the one-session limit had to go. The fix shipped with Windows Vista in January 2007, and it was the central architectural decision of the entire ETW-as-EDR-substrate story.

## 4. Vista's eight sessions: the architectural decision that made the modern EDR endpoint possible

Park and Buch open their April 2007 MSDN Magazine article with the line that frames every later development:

<PullQuote>
"On Windows Vista, ETW has gone through a major upgrade, and one of the most significant changes is the introduction of the unified event provider model and APIs." -- Park & Buch, *MSDN Magazine*, April 2007 [@ms-park-buch-2007]
</PullQuote>

The new model raised the per-provider session cap from one to eight. That single number is why Defender, CrowdStrike Falcon, SentinelOne, Sysmon, and a researcher's SilkETW tap can all read [`Microsoft-Windows-Kernel-Process`](https://www.fireeye.com/blog/threat-research/2019/03/silketw-because-free-telemetry-is-free.html) [@fireeye-silketw-launch] from the same host today without one of them stealing events from the others.

The Vista model also unified two things that had been separate. ETW providers wrote to per-CPU ring buffers; the Win32 Event Log was a different facility with its own writer, its own format, and its own consumers. Park and Buch describe the unification verbatim:

<PullQuote>
"The new unified APIs combine logging traces and writing to the Event Viewer into one consistent, easy-to-use mechanism for event providers." -- Park & Buch, *MSDN Magazine*, April 2007 [@ms-park-buch-2007]
</PullQuote>

After Vista, a single `EventWrite` call from a manifest-based provider lands both in the per-CPU ring buffer for ETW consumers *and* in the `evtx` channel for `wevtutil` and Group Policy audit consumers, depending on how the manifest's channel mappings are configured. The "Event Viewer" the user sees is now a consumer of ETW.

<Definition term="Manifest-based provider">
The Vista-era ETW provider class. The provider author writes an XML manifest enumerating events, fields, tasks, opcodes, levels, keywords, and channels. The `mc.exe` message compiler turns the manifest into a binary resource embedded in the provider binary; `wevtutil im` registers the manifest with the system at install time. At runtime the provider calls `EventRegister` once per provider GUID and `EventWrite` per event. Capped at eight trace sessions per provider.
</Definition>

<Definition term="Channel">
A logical destination for an event, declared in a manifest. The four standard channels are *Admin* (operational events for administrators), *Operational* (verbose events for operators), *Analytical* (high-volume events for diagnostics), and *Debug* (developer-only events). When the provider's `EventWrite` fires, the kernel demultiplexes by channel: events with channels enabled in the `evtx` configuration land in the corresponding channel log, while subscribed real-time consumers receive them through their session.
</Definition>

The deployment pipeline for a manifest-based provider is heavier than for a classic provider. The author writes a manifest, compiles it, embeds the resource, and runs `wevtutil im` at install time. Microsoft Learn calls out the [distinction between provider registration and manifest installation](https://learn.microsoft.com/en-us/windows/win32/api/evntprov/nf-evntprov-eventregister) [@ms-eventregister] explicitly, and notes that [each process can register up to 1,024 providers](https://learn.microsoft.com/en-us/windows/win32/api/evntprov/nf-evntprov-eventregister) [@ms-eventregister]. In practice few processes come close.

<Mermaid caption="The Vista unified provider model: a manifest XML compiled by mc.exe into a binary resource registers with the system at install time; at runtime EventWrite calls land both in ETW sessions and in evtx log channels.">
flowchart TD
    A[Author writes manifest.xml] --> B[mc.exe compiles to binary resource]
    B --> C[Resource embedded in provider .dll/.exe]
    C --> D[Installer runs wevtutil im manifest.xml]
    D --> E[System-wide manifest registry]
    F[Provider process at runtime] --> G[EventRegister GUID]
    G --> H[EventWrite per event]
    H --> I[Per-CPU ring buffer<br/>for ETW sessions]
    H --> J[Channel demux<br/>Admin / Operational / Analytical / Debug]
    J --> K[(.evtx log files)]
    I --> L[Real-time consumers]
    E -.decode metadata.-> L
    E -.decode metadata.-> K
</Mermaid>

The cap rules now read like this: [eight trace sessions can enable a manifest-based provider concurrently](https://learn.microsoft.com/en-us/windows/win32/etw/about-event-tracing) [@ms-about-etw]; [up to 64 sessions can run on the system at once](https://learn.microsoft.com/en-us/windows/win32/etw/event-tracing-sessions) [@ms-etw-sessions]; [`EnableTraceEx2` returns `ERROR_NO_SYSTEM_RESOURCES` when the per-provider cap binds](https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-enabletraceex2) [@ms-enabletraceex2]. The 8-session number was chosen for ergonomics, not for security planning, but it is the load-bearing number in modern Windows endpoint security.

> **Key idea:** The eight-session cap on manifest-based providers is the single architectural decision that made multi-EDR coexistence on the same Windows host possible. Without it, the second EDR to subscribe to `Microsoft-Windows-Kernel-Process` would silently steal events from the first.

A 2007-era driver author shipping the inaugural `Microsoft-Windows-Kernel-Process` provider, GUID `{22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716}`, authored a manifest declaring `ProcessStart` (event ID 1), `ProcessStop` (event ID 2), `ImageLoad` (event ID 5), and so on. Defender's `MsMpEng.exe` could subscribe; the future CrowdStrike Falcon could subscribe; the future Sysmon could subscribe; the future SilkETW researchers could subscribe. None starves another. The Vista unification is the architectural enabler of the modern multi-EDR Windows endpoint.

With multi-consumer concurrency solved, the next problems were authoring overhead and producer integrity. Two parallel paths branched off the Vista manifest model: TraceLogging for the first, the EtwTi PPL/ELAM gate for the second.

## 5. Two more provider classes: WPP for the kernel tree, TraceLogging for the app tier

Vista's manifest-based providers solved coexistence and decoding, but they were heavy to deploy. Microsoft shipped two more provider classes -- one older than Vista and one younger -- that traded manifest deployment for two different kinds of simplicity.

### WPP: the C-preprocessor approach

WPP -- Windows software trace PreProcessor -- predates Vista. Community references and the Park & Buch description of ETW being "abstracted into the Windows preprocessor (WPP) software tracing technology" [@ms-park-buch-2007] place its first WDK ship in the Windows XP era; no Microsoft primary pins a specific build. It became the standard tracing facility inside the Windows kernel tree itself for years. The [WDK page](https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/wpp-software-tracing) [@ms-wpp] frames its purpose:

> "WPP software tracing supplements and enhances WMI event tracing by adding ways to simplify tracing the operation of the trace provider. It is an efficient mechanism for the trace provider to log real-time binary messages."

A WPP provider is authored in C with macros that look like printf calls. The C preprocessor expands `DoTraceMessage(FlagId, "Frobnicating widget %d", widgetId)` into an `EventWrite` call against an auto-generated provider GUID. Format strings are extracted at build time into a *Trace Message Format* file embedded in the binary's `.pdb`. The producer cost is the smallest of any ETW provider class: emitting an event is a function call plus a few stores into a buffer. There is no manifest to deploy, no XML to author.

The corresponding decode cost is the highest. A WPP event arrives at the consumer as a binary payload referencing a TMF identifier. To turn that into a human-readable message the consumer needs the producer's `.pdb` file. If you do not have the symbols for the binary that emitted the event, you do not know what the event means.

That decode cost is why WPP did not become the EDR substrate. Sealighter's README puts the operational consequence verbatim:

<Definition term="WPP (Windows software trace PreProcessor)">
A C-preprocessor-based ETW authoring path inherited from the XP-era WDK. Format strings are extracted to a TMF resource that lives in the producer's `.pdb`. Producer cost is minimal; decode cost requires the producer's symbol files. WPP providers inherit the classic one-session-per-provider cap and are pervasively used inside Windows itself for in-tree dev-time tracing.
</Definition>

> "WPP traces compounds the issues, providing almost no easy-to-find data about provider and their events." -- [Sealighter README](https://github.com/pathtofile/Sealighter) [@gh-sealighter]

WPP providers also inherit the [classic one-session-per-provider cap](https://learn.microsoft.com/en-us/windows/win32/etw/about-event-tracing) [@ms-about-etw], which would have made them unworkable for multi-EDR consumption even if the decode problem were solved. So WPP became the kernel-tree internal tracing facility -- ubiquitous inside Microsoft's source tree, irrelevant outside it.

### TraceLogging: schema in the payload

Eight years after Vista, in Windows 10 (2015), Microsoft shipped a parallel path that solved a different problem. [TraceLogging](https://learn.microsoft.com/en-us/windows/win32/tracelogging/trace-logging-about) [@ms-tracelogging-about] keeps the eight-session cap of manifest providers but eliminates the manifest deployment burden:

> "TraceLogging is a system for logging events that can be decoded without a manifest." -- [Microsoft Learn, About TraceLogging](https://learn.microsoft.com/en-us/windows/win32/tracelogging/trace-logging-about) [@ms-tracelogging-about]

A TraceLogging event carries its own schema inline. The event payload is a sequence of typed-length-value triples: a one-byte type tag, a length, and the data. A consumer that has never seen the provider before can still decode the event because the names and types of every field are *in the event*. The provider author needs no XML manifest, no `mc.exe`, no `wevtutil im`.

The trade-off is per-event size. Inline schema strings cost bytes per event. For a high-volume provider emitting millions of events per minute, the per-event size matters and a manifest-based provider is correct. For a new component author who wants tracing without an install-time deployment dance, TraceLogging is the right answer.

<Definition term="TraceLogging">
A self-describing ETW provider class shipped in Windows 10. Schema is inline in each event payload as type-length-value triples; consumers decode without a manifest. Available from C/C++ via `TraceLoggingProvider.h`, from .NET via `EventSource` with `EtwSelfDescribingEventFormat`, and from WinRT via `LoggingChannel`. Inherits the eight-session cap from the manifest-based class.
</Definition>

TraceLogging is also the unified path across runtimes. The same self-describing payload format is emitted from native C/C++, from .NET (when an `EventSource` opts into `EtwSelfDescribingEventFormat`), and [from kernel-mode drivers](https://learn.microsoft.com/en-us/windows/win32/tracelogging/trace-logging-portal) [@ms-tracelogging-portal]. A consumer using TDH (the Trace Data Helper API) decodes them without distinguishing between the runtime that emitted them.

### Four classes, four trade-offs

| Class | First Shipped | Schema Location | Sessions/Provider | Decode without symbols/manifest? | Best for |
|-------|---------------|-----------------|-------------------|-----------------------------------|-----------|
| MOF / classic | 2000 | WMI repository (`mofcomp`) | 1 | Needs MOF | Legacy components; NT Kernel Logger |
| WPP | ~2002 | `.pdb` (TMF) | 1 | No -- needs producer PDB | In-tree Windows kernel dev-time tracing |
| Manifest-based | 2007 (Vista) | XML manifest, system-installed | 8 | Needs installed manifest | Shipping security telemetry |
| TraceLogging | 2015 (Win10) | Inline TLV in payload | 8 | Yes | New apps and services; cross-runtime |

Sources for the table: [@ms-about-etw, @ms-etw-config, @ms-tracelogging-about, @ms-wpp].

<Aside label="When to use which provider class">
For new shipping Windows components with a known event vocabulary and high volume, choose manifest-based: smallest per-event size, evtx integration, eight-consumer concurrency. For new cross-runtime open-source providers where deployment friction matters, choose TraceLogging: same eight-consumer concurrency, no XML to author, decodable everywhere. For in-source-tree dev-time tracing inside a binary you already have symbols for, WPP is fine. For new security-relevant providers, never choose classic: the one-session cap is structurally incompatible with multi-EDR coexistence.
</Aside>

Four provider classes, four trade-offs. But every one of them shares a structural weakness: the producer fires from inside the calling process, and any code in that process can patch the runtime entry-point and silence the provider for itself. That is the weakness Adam Chester made famous in 2020, and the one EtwTi was built to defeat.

## 6. Sessions, buffers, and the autologger registry: where the telemetry actually lives

Open `regedit` on a Windows host and navigate to `HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger`. You are looking at the persistence surface of every trace session that survives a reboot on this machine -- and the persistence surface every modern EDR uses to install itself.

A session is the unit ETW actually exposes to controllers. It owns a per-session pool of buffers, a writer thread, a destination (file or real-time consumer), and a list of providers it has subscribed to. The lifecycle is short. A controller fills out an [`EVENT_TRACE_PROPERTIES` structure](https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-event_trace_properties) [@ms-event-trace-props] with a session name, buffer size, logging mode, and destination, then calls `StartTrace`. The kernel allocates the buffers -- [at least two per logical processor](https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-event_trace_properties) [@ms-event-trace-props] -- and returns a session handle. The controller then calls [`EnableTraceEx2`](https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-enabletraceex2) [@ms-enabletraceex2] for each provider it wants to subscribe to, passing `EVENT_CONTROL_CODE_ENABLE_PROVIDER` along with the provider GUID, level, and keyword bitmask.

If the provider's per-class session cap is already saturated, `EnableTraceEx2` returns `ERROR_NO_SYSTEM_RESOURCES`. If the caller lacks the privilege to enable that provider, it returns `ERROR_ACCESS_DENIED`. We will see both error codes again later, on different paths.

<Sidenote>
The default buffer size sweet spot is small. The Microsoft Learn primary states it explicitly: "Trace sessions with large buffers (256KB or larger) should be used only for diagnostic investigations or testing, not for production tracing." [@ms-event-trace-props] Production session buffer sizes typically sit in the 32-64KB range.
</Sidenote>

There are three logging modes. *File mode* writes events to a sequential `.etl` file on disk; the writer thread drains buffers to disk and the file grows. *Circular mode* writes to a fixed-size file in a circular buffer; old events are overwritten when the file fills. *Real-time mode* delivers events to a real-time consumer process via a kernel callback. Defender, EDR sensors, and Sysmon all use real-time mode for their hot paths; they may also write to file as a forensic backup.

<Definition term="Real-time consumer">
A process that calls `OpenTrace` with `LogFileMode = EVENT_TRACE_REAL_TIME_MODE` and receives events live via a registered callback rather than from an `.etl` file on disk. Real-time consumers must keep up with producer rate or events are lost.
</Definition>

The autologger registry path is what makes a session survive a reboot. A subkey under `HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\<SessionName>` defines a session that the kernel starts at boot, before most user-mode services are running. Each subkey's values configure the session: `BufferSize`, `MaximumBuffers`, `LogFileMode`, `FileName`, plus a nested `<SessionName>\<ProviderGuid>` subkey for each provider to enable.

<Definition term="Autologger">
A registry-persisted boot-time ETW session. The kernel reads `HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\<SessionName>` at boot, creates the session, enables the configured providers, and begins capture before user-mode services start. Defender's Sense agent, CrowdStrike's Falcon sensor, and Sysmon's driver all install autologgers here.
</Definition>

Defender's `DiagTrack`, `Microsoft-Windows-Diagnosis-PCW`, the SQM kernel logger, the EventLog-Application channel autologger -- all live here (observable via `logman query -ets` on a stock Windows install). Third-party EDRs add their own. The Palantir CIRT taxonomy [@palantir-tampering-wayback] (about which more in section 11) frames this registry surface as the persistent-tampering target: an attacker who can write to this subtree can disable an EDR's boot-time tracing without ever interacting with the running EDR process. The events of interest never get captured because the session never starts.

There is a related concept worth naming: the *Global Logger*. This is a special autologger session whose configuration lives in `HKLM\SYSTEM\CurrentControlSet\Control\WMI\GlobalLogger`. It is the boot-time tracing path that comes online before any user-mode service, including before Sense and the EDR sensor. It exists to capture early-boot kernel events that no later session can record.

<Mermaid caption="The autologger registry persistence surface: each subkey under HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\ defines a session that the kernel starts at every boot, with provider GUIDs to enable, buffer sizes, and the destination .etl path.">
flowchart TD
    R[HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\] --> S1[DiagTrack-Listener]
    R --> S2[Defender-Listener]
    R --> S3[ThirdPartyEDR-Sensor]
    R --> SG[GlobalLogger]
    S2 --> S2P[Provider GUIDs subkeys]
    S2 --> S2C[BufferSize / MaximumBuffers / LogFileMode]
    S2 --> S2F[FileName=.etl path]
    S2P --> KS[Kernel reads at boot]
    S2C --> KS
    S2F --> KS
    KS --> Started[Session started before user-mode services]
</Mermaid>

> **Note:** `logman query -ets` enumerates every live trace session on the host. Cross-reference against the subkeys in `HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\` to find sessions configured to start at boot. Any unauthorised entry -- a session you do not recognise, an autologger pointed at a destination outside your EDR's data path, a provider GUID you cannot account for -- belongs in your incident response queue. We return to this in section 14.

<Sidenote>
`ERROR_NO_SYSTEM_RESOURCES` from `EnableTraceEx2` is the runtime symptom of the eight-session cap binding [@ms-enabletraceex2]. SOC engineers debugging multi-EDR coexistence problems should look for it in their sensor's diagnostic output. Eight subscribers per manifest provider is enough for the typical Defender + third-party EDR + Sysmon + research tap arrangement, but a host running multiple research-mode tracers can saturate it.
</Sidenote>

Persistence solved: a session the OS starts at every boot. But who reads it? That requires a consumer process, and consumers are where the architecture forks along the security spectrum.

## 7. Consumer architecture: from `OpenTrace` to KrabsETW to a 30-line process watcher

The consumer side of ETW is mechanically simple -- three calls to open a trace, register a callback, and process events -- but the choice of library tells you almost everything about what kind of EDR you are building.

The native pattern is three Win32 calls. `EnableTraceEx2` subscribes the session to a provider GUID with a level and keyword bitmask. `OpenTrace` returns a handle on the session for consumption. `ProcessTrace` blocks the calling thread, drains events from the kernel's per-CPU buffers, and dispatches each one to a registered callback. Each event arrives as an `EVENT_RECORD` containing a header (provider GUID, event ID, level, keyword, opcode, timestamp, process ID, thread ID) and a payload that the consumer decodes.

For manifest providers the consumer decodes via TDH (the Trace Data Helper API) against the system-installed manifest. For TraceLogging providers the consumer decodes from the inline TLV payload. For classic and WPP providers the consumer needs the MOF schema or the producer's PDB respectively.

<Definition term="TDH (Trace Data Helper)">
The Win32 decoder API that turns a raw `EVENT_RECORD` payload into typed fields, using the registered manifest as the schema source. `TdhGetEventInformation` returns a `TRACE_EVENT_INFO` structure with the field names, types, and offsets; `TdhFormatProperty` extracts each field. TDH is what makes manifest events self-describing at the consumer end, even though the schema lives out of band.
</Definition>

<Mermaid caption="The user-mode real-time consumer pattern: EnableTraceEx2 subscribes a session to a provider GUID, OpenTrace returns a handle, and ProcessTrace blocks the calling thread while dispatching events to a registered callback.">
sequenceDiagram
    participant C as Consumer process
    participant K as Kernel ETW subsystem
    participant P as Provider process
    C->>K: StartTrace(session)
    C->>K: EnableTraceEx2(session, providerGuid, level, keyword)
    K-->>P: Provider notified to begin emitting
    C->>K: OpenTrace(session)
    K-->>C: TraceHandle
    C->>K: ProcessTrace(handle) [blocking]
    P->>K: EventWrite(payload)
    K-->>C: callback(EVENT_RECORD)
    P->>K: EventWrite(payload)
    K-->>C: callback(EVENT_RECORD)
    Note over C,K: ProcessTrace returns only when session ends
</Mermaid>

In production almost no one writes the raw three-call pattern. The library universe settled into a small set of widely-used wrappers, and the choice of wrapper maps almost one-to-one onto the kind of EDR the engineering team is building.

[**krabsetw**](https://github.com/microsoft/krabsetw) [@gh-krabsetw] is a Microsoft-authored C++ library that simplifies session and provider management. Its README explicitly notes the production caller: a C++/CLI wrapper called `Microsoft.O365.Security.Native.ETW`, "used in production by the Office 365 Security team. It's affectionately referred to as Lobsters." If you are building an in-house EDR or a security analytics pipeline in C++ on Windows, krabsetw is the default choice.

[**Microsoft.Diagnostics.Tracing.TraceEvent**](https://www.nuget.org/packages/Microsoft.Windows.EventTracing.Processing.All) [@nuget-traceprocessing] is the .NET ETW library, distributed as a NuGet package and used heavily inside the .NET diagnostics community. It is the backing library for the [.NET TraceProcessing API](https://learn.microsoft.com/en-us/windows/win32/etw/event-tracing-portal) [@ms-etw-portal] which Microsoft uses internally to analyze ETW data from the Windows engineering system.

[**SilkETW**](https://github.com/mandiant/SilkETW) [@gh-silketw], originally [released by Ruben Boonen at FireEye in March 2019](https://www.fireeye.com/blog/threat-research/2019/03/silketw-because-free-telemetry-is-free.html) [@fireeye-silketw-launch] (now maintained by Mandiant), wraps `Microsoft.Diagnostics.Tracing.TraceEvent` to expose ETW telemetry to detection-engineering and threat-hunting workflows. SilkETW is the canonical "blue team research" consumer: the tool you reach for when you want to see what events a provider actually emits without writing C++.

[**Sealighter**](https://github.com/pathtofile/Sealighter) [@gh-sealighter], by `pathtofile`, is a krabsetw-wrapping C++ tool that makes multi-provider subscription and filtering tractable from a JSON config. The README states: "Sealighter leverages the feature-rich Krabs ETW Library to enable detailed filtering and triage of ETW and WPP Providers and Events." Sealighter is the canonical "red/blue team triage" consumer: more flexible than SilkETW, less code to write than raw krabsetw.

The pitfalls are universal across all four libraries. The krabsetw README spells two of them out:

> "The call to 'start' on the trace object is blocking so thread management may be necessary." -- [@gh-krabsetw]

> "Throwing exceptions in the event handler callback ... will cause the trace to stop processing events." -- [@gh-krabsetw]

Both have caused real production outages. An EDR that throws an unhandled exception in its event callback dies silently as an ETW consumer, and the next event the provider emits goes nowhere.

<Sidenote>
The "throwing in the callback stops the trace" pitfall is the gotcha that bites every team writing their first ETW consumer. The kernel does not catch the exception; the trace simply ends. A production-quality consumer wraps every callback in try/catch (or its language equivalent) and routes failures through a side channel, not through the trace itself.
</Sidenote>

To make the structure concrete, here is what a 30-line `Microsoft-Windows-Kernel-Process` real-time consumer looks like, written in TypeScript pseudocode that mirrors the structure a Sealighter or krabsetw user would write:

<RunnableCode lang="ts" title="Logical structure of a 30-line Microsoft-Windows-Kernel-Process real-time consumer">{`
// Pseudocode: the structure of a krabsetw / Sealighter consumer
// for the Microsoft-Windows-Kernel-Process provider.

const KERNEL_PROCESS_GUID = "{22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716}";

const session = new UserTraceSession("MyEdrSensor");

const provider = new Provider(KERNEL_PROCESS_GUID);
provider.level = TraceLevel.Information;
provider.anyKeyword = 0xFFFFFFFFFFFFFFFFn;

provider.onEvent = (event) => {
  try {
    switch (event.id) {
      case 1: // ProcessStart
        const pid = event.fields.ProcessID;
        const imageName = event.fields.ImageName;
        const cmdLine = event.fields.CommandLine;
        console.log(\`Process start pid=\${pid} image=\${imageName}\`);
        break;
      case 2: // ProcessStop
        console.log(\`Process stop pid=\${event.fields.ProcessID}\`);
        break;
      case 5: // ImageLoad
        console.log(\`Image load \${event.fields.ImageName} into pid=\${event.fields.ProcessID}\`);
        break;
    }
  } catch (e) {
    // never let an exception escape the callback
    sideChannelLog(e);
  }
};

session.enable(provider);
session.start();  // blocks until session.stop() is called
`}</RunnableCode>

That code, in production form, is a working EDR sensor's process watcher. Every commercial Windows EDR has something with the same structure inside it.

> **Note:** krabsetw wraps the C++ surface and is the default for production in-house EDRs. TraceEvent wraps .NET and is the default for diagnostics tooling. SilkETW exposes ETW to detection engineers without C++. Sealighter wraps krabsetw with a config file for triage. Pick the library that matches the team that will own the consumer, not the one that looks most powerful.

This is what Sysmon, Wazuh, and Elastic Defend look like under the hood -- a SYSTEM-privileged user-mode service consuming public providers. But there is one provider this code cannot subscribe to. Try it and `EnableTraceEx2` returns `ERROR_ACCESS_DENIED`. The next two sections are about the GUID that requires a passport.

## 8. The security provider catalogue: what EDRs actually read

There are roughly 1,300 manifest-based providers shipped on a 2026 Windows 11 24H2 install -- the [community-maintained jdu2600 inventory](https://github.com/jdu2600/Windows10EtwEvents) [@gh-jdu2600] tracks the count across builds, and the [repnz manifest archive](https://github.com/repnz/etw-providers-docs) [@gh-repnz] holds byte-stable copies of the manifests for cross-version diffing. Eight of those providers carry almost all the security telemetry the EDR vendors read. This is the catalogue.

### `Microsoft-Windows-Security-Auditing`

GUID `{54849625-5478-4994-A5BA-3E3B0328C30D}`. The audit-policy-driven Security event log producer. Event ID 4624 (logon), 4625 (failed logon), 4634 (logoff), [4688 (process create with command line)](https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4688) [@ms-event-4624], 4689 (process exit), and the broader subcategory audit policy events. This is the closure for the legacy Security event log: when an administrator turns on "audit logon events" in the local security policy, this is the provider that emits the events. EDRs that consume it are reading the same stream the Event Viewer's Security log shows.

### `Microsoft-Windows-Kernel-Process`

GUID `{22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716}`. The canonical real-time process telemetry source for non-PPL EDR. Event ID 1 fires on `ProcessStart` with PID, parent PID, image name, command line, and SID; event ID 2 on `ProcessStop`; event ID 3 on thread create; event ID 4 on thread exit; event ID 5 on `ImageLoad` with the loaded module name and base address. SilkETW's launch [post enumerates the event record format inline](https://www.fireeye.com/blog/threat-research/2019/03/silketw-because-free-telemetry-is-free.html) [@fireeye-silketw-launch]. This provider is widely cited in EDR community documentation as available since Windows 7, though no Microsoft primary pins the exact build.

### `Microsoft-Windows-Kernel-File`, `Microsoft-Windows-Kernel-Network`, `Microsoft-Windows-Kernel-Registry`

The per-subsystem siblings of `Kernel-Process`. `Kernel-File` surfaces file open / close / read / write / delete operations with the file path and the operating PID. `Kernel-Network` surfaces TCP and UDP send / receive with the local and remote endpoints. `Kernel-Registry` surfaces registry create / open / set value / delete with the key path and value name. All three use the manifest-based class and inherit the eight-session cap. EDRs that want full-fidelity per-syscall telemetry without writing kernel callbacks subscribe to these three.

### `Microsoft-Antimalware-Scan-Interface`

GUID `{2A576B87-09A7-520E-C21A-4942F0271D67}`, documented in the [Microsoft Learn AMSI portal](https://learn.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-portal) [@ms-amsi-portal] and surveyed in the [Palantir CIRT taxonomy](https://web.archive.org/web/2023/https://blog.palantir.com/tampering-with-windows-event-tracing-background-offense-and-defense-4be7ac62ac63) [@palantir-tampering-wayback]. This is the ETW provider that surfaces AMSI scan results: a script block submitted by PowerShell, JScript, VBA, an Office macro engine, or any other AMSI client comes through here *after deobfuscation*. Whatever string the script engine is about to execute, the registered antimalware engine sees in plaintext, and the result of the scan is published via this provider for any listener.

<Definition term="AMSI (Antimalware Scan Interface)">
A COM interface exposed by Windows since 2015 that script engines and runtime hosts can call into to submit content for malware scanning. The Microsoft Learn AMSI portal lists PowerShell, JScript and VBScript via Windows Script Host, Office VBA macros, and User Account Control as in-box integrators [@ms-amsi-portal]; the .NET CLR's assembly load path joined the list with .NET Framework 4.8, as documented in Adam Chester's CLR walk-through [@xpn-hiding-dotnet]. The scanned content is the post-deobfuscation form -- the actual code about to execute, not the obfuscated wrapper. Scan results surface via the `Microsoft-Antimalware-Scan-Interface` ETW provider.
</Definition>

<Sidenote>
The AMSI Operational event log channel typically appears empty by default. The [Palantir taxonomy](https://web.archive.org/web/2023/https://blog.palantir.com/tampering-with-windows-event-tracing-background-offense-and-defense-4be7ac62ac63) [@palantir-tampering-wayback] notes the keyword bitmask configured for the channel does not surface scan-result events. The events fire on the ETW bus and can be consumed in real time, but they do not land in the user-visible evtx log unless the consumer reconfigures the keyword mask.
</Sidenote>

### `Microsoft-Windows-PowerShell`

GUID `{a0c1853b-5c40-4b15-8766-3cf1c58f985a}`. Event ID 4104 is the script-block-logging event that records each PowerShell script block before execution; event ID 4103 records pipeline execution detail; event ID 4100 records errors. The [Microsoft Learn `about_Logging_Windows` reference](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_logging_windows) [@ms-powershell-logging] documents EID 4104 verbatim ("`EventId 4104 / 0x1008` ... `Channel Operational` ... `Task CommandStart`") and the script-block-logging configuration. Combined with AMSI the two providers give an EDR the executed PowerShell content twice: once at AMSI submission, once at script-block logging. Detection engineers use both as cross-checks.

### `Microsoft-Windows-DotNETRuntime`

GUID `{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}`, [verbatim in Adam Chester's PoC source](https://blog.xpnsec.com/hiding-your-dotnet-etw/) [@xpn-hiding-dotnet]. The .NET CLR provider. Surfaces assembly load events, JIT compilation, AppDomain creation, exception throws. Critical for detecting Cobalt Strike's `execute-assembly` style of in-memory .NET payload loading. This is the provider that goes dark in the section 1 hook scene after the operator's `EtwEventWrite` patch.

<Sidenote>
This is the provider Adam Chester targeted in the canonical March 17, 2020 ETW patching post [@xpn-hiding-dotnet]. The Cobalt Strike `execute-assembly` workflow produces a loud signal here -- "assembly X loaded into PID Y from in-memory source Z" -- so silencing it locally was a valuable evasion. The story comes back in section 11.
</Sidenote>

### `Microsoft-Windows-Sysmon`

GUID `{5770385F-C22A-43E0-BF4C-06F5698FFBD9}`, surfaced by `wevtutil gp Microsoft-Windows-Sysmon` and inventoried in [@gh-jdu2600]; the [Microsoft Learn Sysmon page by Russinovich and Garnier](https://learn.microsoft.com/en-us/sysinternals/downloads/sysmon) [@ms-sysmon] documents authorship, the protected-process status, and the `Microsoft-Windows-Sysmon/Operational` channel. This is the *publishing* side of Sysmon. Sysmon's kernel driver `SysmonDrv.sys` collects events through `PsSetCreateProcessNotifyRoutineEx` and friends; the user-mode service then republishes via this ETW provider so any consumer (a SIEM forwarder, a SOC dashboard, a custom analytic) can subscribe without writing its own kernel driver. Events also land in the `Microsoft-Windows-Sysmon/Operational` evtx channel.

### `Microsoft-Windows-Threat-Intelligence` (EtwTi)

GUID `{f4e1897c-bb5d-5668-f1d8-040f4d8dd344}`, [verbatim in the fluxsec.red walkthrough](https://fluxsec.red/event-tracing-for-windows-threat-intelligence-rust-consumer) [@fluxsec-eti]. The only ETW source in the catalogue that fires from inside the kernel for memory-modifying syscalls. Ten task IDs, all prefixed `KERNEL_THREATINT_TASK_`:

- `ALLOCVM` (`NtAllocateVirtualMemory` cross-process)
- `PROTECTVM` (`NtProtectVirtualMemory`)
- `MAPVIEW` (section mapping; cross-process and self)
- `QUEUEUSERAPC` (`NtQueueApcThread` cross-process)
- `SETTHREADCONTEXT` (`NtSetContextThread` cross-process)
- `READVM` (`NtReadVirtualMemory` cross-process)
- `WRITEVM` (`NtWriteVirtualMemory` cross-process)
- `SUSPENDRESUME_THREAD`
- `SUSPENDRESUME_PROCESS`
- `DRIVER_DEVICE`

Each task pairs with a 64-bit keyword bitmask that distinguishes `LOCAL` vs `REMOTE` (cross-process) and `KERNEL_CALLER` vs not. The [Elastic Security Labs walkthrough](https://www.elastic.co/security-labs/doubling-down-etw-callstacks) [@elastic-doubling-down] lists the named Win32/Nt syscalls that surface here:

> "The most notable addition to this visibility is the Microsoft-Windows-Threat-Intelligence Event Tracing for Windows (ETW) provider ... VirtualAlloc, VirtualProtect, MapViewOfFile, VirtualAllocEx, VirtualProtectEx, MapViewOfFile2, QueueUserAPC, SetThreadContext, WriteProcessMemory, ReadProcessMemory(lsass)" -- [Elastic Security Labs](https://www.elastic.co/security-labs/doubling-down-etw-callstacks) [@elastic-doubling-down]

<Definition term="Microsoft-Windows-Threat-Intelligence (EtwTi)">
The kernel-emitted ETW provider for memory-modifying syscalls. GUID `{f4e1897c-bb5d-5668-f1d8-040f4d8dd344}`. Events are emitted from the kernel side of the syscall path (not from a user-mode trampoline), which makes the provider unreachable from a user-mode patcher in the calling process. Consumption is gated behind Protected Process Light at the Antimalware signer level, paired with an Early Launch Antimalware driver. The provider first shipped in the Windows 10 RS-era; the precise build is not stated verbatim in any Microsoft primary located, with community references converging on no later than 1709.
</Definition>

The first-ship-build is hedged: the provider GUID and task inventory are well-documented in third-party reverse-engineering primaries, but no Microsoft primary located in the source verification stage pins the exact build. The community reference range is Windows 10 1607 (RS1) through 1709 (RS3). The dispositive practical evidence is [Yarden Shafir's 2023 Trail of Bits walkthrough](https://blog.trailofbits.com/2023/11/22/etw-internals-for-security-research-and-forensics/) [@trailofbits-shafir], which shows live-debugger output of `CSFalconService.exe` (CrowdStrike) holding `EtwConsumer` handles to multiple logger IDs simultaneously. By 2023 third-party EDRs were demonstrably consuming EtwTi at scale.

### The catalogue as a single screen

| Provider name | GUID | Surface | Gate | Primary source |
|---------------|------|---------|------|----------------|
| Microsoft-Windows-Security-Auditing | `{54849625-5478-4994-A5BA-3E3B0328C30D}` | Audit-policy events (4624/4625/4688/...) | None (Local Security Policy) | [@ms-event-4624] |
| Microsoft-Windows-Kernel-Process | `{22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716}` | Process / thread / image-load events | None (admin) | [@fireeye-silketw-launch], [@gh-jdu2600] |
| Microsoft-Windows-Kernel-File | (manifest archive) | File I/O syscalls | None (admin) | [@gh-jdu2600], [@gh-repnz] |
| Microsoft-Windows-Kernel-Network | (manifest archive) | TCP/UDP send/receive | None (admin) | [@gh-jdu2600], [@gh-repnz] |
| Microsoft-Windows-Kernel-Registry | (manifest archive) | Registry create/open/set/delete | None (admin) | [@gh-jdu2600], [@gh-repnz] |
| Microsoft-Antimalware-Scan-Interface | `{2A576B87-09A7-520E-C21A-4942F0271D67}` | Post-deobfuscation script content | None (admin) | [@ms-amsi-portal], [@palantir-tampering-wayback] |
| Microsoft-Windows-PowerShell | `{a0c1853b-5c40-4b15-8766-3cf1c58f985a}` | Script-block logging (4104), pipeline | None (admin) | [@gh-jdu2600] |
| Microsoft-Windows-DotNETRuntime | `{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}` | CLR assembly load, JIT, exceptions | None (admin) | [@xpn-hiding-dotnet] |
| Microsoft-Windows-Sysmon | `{5770385F-C22A-43E0-BF4C-06F5698FFBD9}` | Sysmon driver re-publication | None (admin) | [@gh-jdu2600], [@ms-sysmon] |
| Microsoft-Windows-Threat-Intelligence | `{f4e1897c-bb5d-5668-f1d8-040f4d8dd344}` | Memory-modifying syscalls (kernel-emitted) | **PPL + ELAM (Antimalware signer level)** | [@fluxsec-eti], [@elastic-doubling-down] |

<Aside label="What this catalogue is not">
This is the *security* catalogue. The full Windows manifest-based provider list is roughly 1,300 entries on a current Windows 11 build; performance-tuning, diagnostic, and developer-facing providers fill out the rest. The [jdu2600 inventory](https://github.com/jdu2600/Windows10EtwEvents) [@gh-jdu2600] tracks the full list across Win10 versions; the [repnz archive](https://github.com/repnz/etw-providers-docs) [@gh-repnz] preserves byte-stable manifest copies for cross-version diffing.
</Aside>

Nine of the ten rows in that table are accessible to any SYSTEM-privileged user-mode service. The tenth -- EtwTi -- requires a passport. The next section is about who issues the passport.

## 9. The PPL / ELAM gate: why EtwTi is not for everyone

To consume the one ETW provider that fires from the kernel for memory-modifying syscalls, your service must be (a) a [Protected Process Light](https://paragmali.com/blog/who-is-this-code----the-quiet-33-year-reinvention-of-app-ide/), (b) signed at the Antimalware signer level with EKU `1.3.6.1.4.1.311.61.4.1`, and (c) loaded from disk by an [Early Launch Antimalware](https://paragmali.com/blog/secure-boot-in-windows-the-chain-from-sector-zero-to-userini/) driver registered at boot. Two of those three were not possible for third parties until the Windows 10 RS-era.

[fluxsec.red](https://fluxsec.red/event-tracing-for-windows-threat-intelligence-rust-consumer) [@fluxsec-eti] gives the prerequisite list verbatim:

> "In order to start receiving ETW:TI signals, we need: 1. A service running as Protected Process Light, 2. An Early Launch Antimalware driver and certificate, 3. A logging mechanism." -- [@fluxsec-eti]

Each prerequisite has a story.

### Protected Process Light at the Antimalware signer level

Windows 8.1 introduced the *protected service* concept specifically for antimalware engines. The motivation was simple: a malicious process running as administrator should not be able to inject code into the antimalware service or attach a debugger to it. The [Microsoft Learn primary](https://learn.microsoft.com/en-us/windows/win32/services/protecting-anti-malware-services-) [@ms-protect-am] sets out the model:

> "Windows 8.1 introduced a new concept of protected services to protect anti-malware services... In addition to the existing ELAM driver certification requirements, the driver must have an embedded resource section containing the information of the certificates used to sign the user mode service binaries." -- [@ms-protect-am]

PPL is a process-protection level. A given process has a level on the PPL lattice; another process can open it for write or debug only if the requesting process's level is greater than or equal to the target's. Antimalware-PPL is a *signer level* on that lattice. The kernel admits a process to Antimalware-PPL when its image is signed with a certificate whose EKU includes `1.3.6.1.4.1.311.61.4.1` (Windows Antimalware) *and* whose certificate is enrolled in an ELAM driver's allow-list at boot.

<Definition term="PPL (Protected Process Light)">
A Windows process-protection model. Each process has a PPL level; another process may open it for write or debug only if the requestor is at an equal or higher level. Originally introduced for DRM, the lattice was extended in Windows 8.1 to host the Antimalware signer level for protecting antimalware services from administrative-rights attackers.
</Definition>

<Definition term="Antimalware-PPL">
A specific signer level on the PPL lattice. Reserved in Windows 8.1 for Microsoft Defender; opened to third-party EDR vendors via ELAM onboarding in the Windows 10 RS-era. Consumption of the `Microsoft-Windows-Threat-Intelligence` ETW provider is gated at the Antimalware signer level: an `EnableTraceEx2` call from a non-Antimalware-PPL caller against the EtwTi GUID returns `ERROR_ACCESS_DENIED` (the [`EnableTraceEx2`](https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-enabletraceex2) [@ms-enabletraceex2] page documents the error code for callers that lack the documented administrative groups; the per-provider PPL-signer-level check that triggers it for the EtwTi GUID specifically is described in the [@fluxsec-eti] prerequisite list).
</Definition>

### Early Launch Antimalware

ELAM is a driver class that loads before any other non-Microsoft boot driver. The [Microsoft Learn primary](https://learn.microsoft.com/en-us/windows-hardware/drivers/install/early-launch-antimalware) [@ms-elam] describes it:

> "Because an ELAM service runs as a PPL (Protected Process Light), you need to debug using a kernel debugger... AM drivers are initialized first and allowed to control the initialization of subsequent boot drivers, potentially not initializing unknown boot drivers." -- [@ms-elam]

The boot sequence runs like this. Winload loads the ELAM driver as part of the early-boot path. The ELAM driver registers a callback via `IoRegisterBootDriverCallback` and gets to inspect each subsequent boot driver, returning a verdict (initialize / do not initialize / unknown) based on the certificate inventory it carries in its embedded resource section. The kernel honours that verdict. After boot drivers settle, the SCM launches the paired user-mode antimalware service with the `LaunchProtected = SERVICE_LAUNCH_PROTECTED_ANTIMALWARE_LIGHT` flag, and the kernel admits that service to Antimalware-PPL because its signing certificate matches an entry in the ELAM driver's allow-list.

<Definition term="ELAM (Early Launch Antimalware)">
A driver class that loads before any non-Microsoft boot driver. The ELAM driver registers a boot-driver callback to inspect subsequent drivers and an embedded-resource certificate inventory of permitted user-mode antimalware service signatures. Together with PPL, ELAM gates which user-mode antimalware services can pass the Antimalware-PPL admission check.
</Definition>

### The 1709 onboarding

Microsoft Defender's `MsMpEng.exe` ran at the Antimalware signer level by default starting around the Windows 10 1709 timeframe (October 17, 2017), and the same release is widely cited in EDR-vendor documentation as the moment the Antimalware-PPL onboarding was extended to third-party EDR vendors. The Microsoft primary that pins the 1709 third-party onboarding date is not in the public ETW documentation; we treat the date as widely-cited rather than verified.

The dispositive practical evidence is the [Trail of Bits 2023 walkthrough by Yarden Shafir](https://blog.trailofbits.com/2023/11/22/etw-internals-for-security-research-and-forensics/) [@trailofbits-shafir]. Shafir's WinDbg JS scripts walk the live `_ETW_REALTIME_CONSUMER` data structures of a running Windows host and print:

> "Process CSFalconService.exe with ID 0x1e54 has handle 0x760 to Logger ID 3" -- [@trailofbits-shafir]

That is CrowdStrike's user-mode service, holding a real-time consumer handle to an EtwTi logger session. By 2023 the third-party Antimalware-PPL story is operationally complete.

<Mermaid caption="The PPL+ELAM gate for Microsoft-Windows-Threat-Intelligence: at boot the ELAM driver registers an allow-list of permitted user-mode binary signatures; the SCM launches the EDR service with the LaunchProtected flag; the service's EnableTraceEx2 call against the EtwTi GUID is admitted only because the kernel finds the caller is Antimalware-PPL signed.">
sequenceDiagram
    participant BL as Winload (boot)
    participant EL as ELAM Driver
    participant SCM as Service Control Manager
    participant SVC as EDR Service
    participant K as Kernel ETW
    BL->>EL: Load ELAM driver (early boot)
    EL->>EL: Register IoRegisterBootDriverCallback then read embedded cert inventory
    Note over EL: ELAM gates subsequent boot drivers
    SCM->>SVC: Start EDR service with PROTECTED_ANTIMALWARE_LIGHT flag
    K->>SVC: Verify signature against ELAM allow-list
    K-->>SVC: Admit to Antimalware-PPL
    SVC->>K: EnableTraceEx2(session, EtwTi GUID, ...)
    K->>K: Check caller signer level ge Antimalware
    K-->>SVC: SUCCESS
    Note over SVC,K: Non-PPL caller would receive ERROR_ACCESS_DENIED here
</Mermaid>

### Why this gate matters for the section 1 hook

The asymmetry that defines the entire generation is one sentence in the [fluxsec.red walkthrough](https://fluxsec.red/event-tracing-for-windows-threat-intelligence-rust-consumer) [@fluxsec-eti]:

<PullQuote>
"We cannot patch out the Threat Intelligence provider as this is emitted from within the kernel itself. To do so, you'd require kernelmode execution and then to patch out those signals so no ETW signals are emitted." -- [@fluxsec-eti]
</PullQuote>

That is the answer to the puzzle the section 1 hook posed. The Adam Chester 2020 patch operates on a user-mode trampoline in the calling process. `ntdll!EtwEventWrite` is a stub that calls down through `NtTraceEvent` into the kernel; rewriting its first byte to `0xC3` short-circuits the user-mode entry path and the calling process emits no events through that stub. But EtwTi does not fire from the user-mode entry path. EtwTi fires from inside the kernel implementation of `NtAllocateVirtualMemory` and friends, after the syscall has crossed the boundary, on a path the user-mode patcher cannot reach without first achieving kernel execution.

> **Key idea:** EtwTi is the only ETW provider in the catalogue whose producer fires from the kernel side of the syscall path -- and that is exactly why a user-mode patch in the calling process cannot silence it. The PPL+ELAM gate that controls *consumer* admission is paired with a *producer* location that no in-process attacker can reach.

The 2017 PPL+ELAM gate was a deliberate structural defense against the patch class that was only fully publicised three years later. By the time Chester wrote his March 2020 post, the load-bearing security signal was already structurally out of reach of his technique.

<Aside label="Why Microsoft chose this specific gate">
The combination of PPL and ELAM is not an arbitrary defense-in-depth stack. PPL gates *consumer identity* at signer level: only a binary signed with the Antimalware EKU and enrolled in an ELAM allow-list can subscribe. ELAM gates *load order*: the gate is set during early boot, before any code an attacker could load gets a chance to interfere. The signer-level check is hard because forging the signature requires breaking Microsoft's PKI; the load-order check is hard because subverting it requires compromising the boot path, which Secure Boot and the Vulnerable Driver Blocklist exist to defend.
</Aside>

That is the gate. Now we walk the consumers that pass through it.

## 10. Six vendors, three spectra: a map of the EDR consumer architecture

Defender, CrowdStrike, SentinelOne, Sysmon, Wazuh, Elastic Defend. They look interchangeable on a vendor comparison sheet. They are not, and the differences are entirely about which substrates each one consumes.

There are three axes that distinguish them.

### Axis 1: kernel callbacks vs ETW

Some EDRs consume process-creation events through ETW (subscribing to `Microsoft-Windows-Kernel-Process` from a SYSTEM-privileged user-mode service). Others register kernel callbacks directly through [`PsSetCreateProcessNotifyRoutineEx`](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreateprocessnotifyroutineex) [@ms-pssetprocnotify] and [`PsSetCreateThreadNotifyRoutine`](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreatethreadnotifyroutine) [@ms-pssetthreadnotify] from a kernel driver they ship.

The trade-off is sharp. Kernel callbacks are synchronous: the kernel calls into the driver before the operation completes, the driver runs at PASSIVE_LEVEL in the originating thread context with normal kernel APCs disabled, and the driver can deny the operation by writing a non-success status to `CreationStatus`. ETW is asynchronous: the event is emitted from the producer's hot path, drained from a per-CPU buffer by the writer thread, and delivered to the consumer's callback at some later point. ETW cannot deny anything; it can only observe.

<Definition term="Kernel notify routine">
The `PsSetCreate*NotifyRoutine` family of kernel APIs. A driver calls `PsSetCreateProcessNotifyRoutineEx` (process create/exit), `PsSetCreateThreadNotifyRoutine` (thread create/exit), or `PsSetLoadImageNotifyRoutine` (image load) at boot to register a callback. The kernel invokes the callback synchronously, in the originating thread context at PASSIVE_LEVEL with normal kernel APCs disabled. The `Ex` variant of the process callback receives a `CreationStatus` field the driver can write to deny the operation.
</Definition>

CrowdStrike, SentinelOne, Sysmon, and Elastic Defend ship kernel drivers and use callbacks for the latency-critical hot path. Defender uses both -- callbacks from `WdFilter.sys` and ETW consumption from `MsMpEng.exe` -- because as the in-box engine it has the institutional position to do so. Wazuh ships no kernel driver; it consumes ETW exclusively via SilkETW-class wrappers, which makes it less invasive but unable to deny.

### Axis 2: PPL adoption

Defender (`MsMpEng.exe` and `MsMpEngCP.exe`) runs at Antimalware-PPL by default. CrowdStrike's `CSFalconService.exe` runs at Antimalware-PPL, [demonstrably](https://blog.trailofbits.com/2023/11/22/etw-internals-for-security-research-and-forensics/) [@trailofbits-shafir]. SentinelOne's `SentinelAgent.exe` is widely reported to run at Antimalware-PPL via vendor documentation, although it does not appear in the Trail of Bits sample debugger output. Sysmon runs as a *protected process* but [not at the Antimalware signer level](https://learn.microsoft.com/en-us/sysinternals/downloads/sysmon) [@ms-sysmon] -- the Microsoft Learn page states "The service runs as a protected process, thus disallowing a wide range of user mode interactions" without naming Antimalware specifically.

Wazuh and Elastic Defend's user-mode services run as standard SYSTEM-privileged services without PPL.

### Axis 3: EtwTi consumption

This axis is determined by axis 2. Defender consumes EtwTi by design -- it is the in-box reason EtwTi exists. CrowdStrike and SentinelOne consume EtwTi (the Trail of Bits debugger output is the practical demonstration). Sysmon does not consume EtwTi: it is not Antimalware-PPL, so its `EnableTraceEx2` calls against the EtwTi GUID would receive `ERROR_ACCESS_DENIED`. Sysmon relies on its own `SysmonDrv.sys` callbacks for the in-memory threat surface that EtwTi covers for the others. Wazuh and Elastic Defend do not consume EtwTi for the same reason; Elastic Defend [ships its own kernel driver to compensate](https://www.elastic.co/security-labs/doubling-down-etw-callstacks) [@elastic-doubling-down], using Microsoft-blessed kernel-callback paths for memory events.

| Vendor | Process surface | PPL level | EtwTi? | Primary source |
|--------|------------------|-----------|--------|----------------|
| Microsoft Defender | Driver callbacks (`WdFilter.sys`) + ETW (`MsMpEng.exe`) | Antimalware-PPL | Yes | [@ms-protect-am] |
| CrowdStrike Falcon | Driver callbacks + ETW | Antimalware-PPL | Yes ([@trailofbits-shafir] live evidence) | [@trailofbits-shafir] |
| SentinelOne | Driver callbacks + ETW | Antimalware-PPL | Widely reported | -- (vendor docs; SentinelAgent.exe not in [@trailofbits-shafir] sample) |
| Sysmon | `SysmonDrv.sys` callbacks; publishes via own ETW provider | Protected (not Antimalware) | No | [@ms-sysmon] |
| Wazuh | ETW only (SilkETW-class) | Standard SYSTEM | No | -- |
| Elastic Defend | Own kernel driver + ETW | Standard SYSTEM | No | [@elastic-doubling-down] |

Sysmon is worth singling out as the canonical *callback-then-publish* reference architecture. Its kernel driver registers `PsSetCreate*NotifyRoutine` callbacks; its user-mode service consumes the events the driver delivers; and the service then publishes them via its own `Microsoft-Windows-Sysmon` ETW provider for any downstream consumer (a SIEM forwarder, a SOC dashboard, a custom analytic) to read. The result is that Sysmon's events are universally consumable -- which is why Wazuh and Splunk both ship Sysmon configurations as their default kernel-event source.

<Aside label="Sysmon as the calibration anchor">
Sysmon's design choice is the reference architecture for the callback-then-publish pattern, even though Sysmon is not itself an Antimalware-PPL EDR. By publishing through its own ETW provider rather than writing to a private channel, Sysmon makes its events consumable by any downstream pipeline. Wazuh and the Splunk Universal Forwarder can both ingest Sysmon events without any custom integration work. This is why Sysmon, despite being free, is the de facto kernel-event source for the open-source SIEM world.
</Aside>

<Mermaid caption="The kernel-callback-vs-ETW spectrum: synchronous callbacks (left) can deny but couple tightly to the kernel; asynchronous ETW (right) tolerates multiple consumers per provider but observes only.">
flowchart LR
    K[Kernel callbacks<br/>synchronous, can deny] --- L1[Sysmon driver]
    K --- L2[CrowdStrike driver]
    K --- L3[SentinelOne driver]
    K --- L4[Elastic driver]
    K --- L5[Defender WdFilter.sys]
    M[ETW providers<br/>asynchronous, observe-only<br/>up to 8 consumers per provider] --- M1[Defender MsMpEng]
    M --- M2[CrowdStrike service]
    M --- M3[SentinelOne service]
    M --- M4[Sysmon service]
    M --- M5[Wazuh ETW reader]
    M --- M6[Elastic Defend service]
    K -.latency-vs-coupling axis.-> M
</Mermaid>

<Sidenote>
The CrowdStrike July 2024 channel-file outage was a kernel-driver brittleness story, not an ETW story. The Falcon kernel driver's content-update parser dereferenced an out-of-bounds pointer when processing a malformed channel file, BSOD-ing roughly 8.5 million Windows hosts [@ms-crowdstrike-2024]. That story belongs to the [App Identity in Windows article](https://paragmali.com/blog/who-is-this-code----the-quiet-33-year-reinvention-of-app-ide/) in this series; it is mentioned here only to mark that the cost of the synchronous-kernel-driver path is a higher blast radius when the driver itself is buggy.
</Sidenote>

A note on Defender's cloud schema. The events that surface in Microsoft Defender for Endpoint's hunting tables -- `DeviceProcessEvents`, `DeviceFileEvents`, `DeviceNetworkEvents`, `DeviceImageLoadEvents`, `DeviceRegistryEvents` -- are the cloud-side abstraction over the kernel and ETW telemetry the Defender sensor collects locally. The full schema mapping from ETW provider to cloud column is out of scope here, but the substrate is the same.

Six vendors, three axes, one substrate. Now we walk the attack tradition that the substrate has to survive.

## 11. The attack tradition: five generations of trying to blind ETW

Every generation of ETW has been attacked. Some attacks broke a single provider; some broke every user-mode provider on a host; one would, if it worked at scale, break Defender. The defense story is on the same five-generation timeline.

### Gen 1 (2014-2018): autologger registry tampering

The dispositive taxonomy is Matt Graeber and Lee Christensen's December 24, 2018 [Palantir CIRT post](https://web.archive.org/web/2023/https://blog.palantir.com/tampering-with-windows-event-tracing-background-offense-and-defense-4be7ac62ac63) [@palantir-tampering-wayback], preserved in the Wayback Machine because the direct Medium URL has since returned HTTP 403 to non-browser fetchers. The opening framing is verbatim:

> "Event Tracing for Windows (ETW) is the mechanism Windows uses to trace and log system events. Attackers often clear event logs to cover their tracks. Though the act of clearing an event log itself generates an event, attackers who know ETW well may take advantage of tampering opportunities to cease the flow of logging temporarily or even permanently, without generating any event log entries in the process." -- [@palantir-tampering-wayback]

Graeber and Christensen split the technique into two classes. *Persistent tampering* writes to the autologger registry path described in section 6, disabling a session before it ever starts at next boot; the events of interest are never captured because the session is never running. *Ephemeral tampering* targets a live session: stopping the session via `ControlTrace`, removing a provider from a session via `EnableTraceEx2(EVENT_CONTROL_CODE_DISABLE_PROVIDER, ...)`, or directly clearing the session's buffers.

The defense is direct: monitor the autologger registry surface. Sysmon Event ID 13 [@ms-sysmon] surfaces registry value-set events in `HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\`; a SOC playbook that alerts on any unexpected write to that subtree catches the persistent class of attack reliably. Matt Graeber's authorship is cross-confirmed by the [palantir/exploitguard repository](https://github.com/palantir/exploitguard) [@gh-palantir-exploitguard], which credits him as the lead researcher on the ETW work.

### Gen 2 (2020): user-mode `EtwEventWrite` 0xC3 RET patch

The technique that made ETW patching a household tradecraft term is [Adam Chester's "Hiding your .NET - ETW", March 17, 2020](https://blog.xpnsec.com/hiding-your-dotnet-etw/) [@xpn-hiding-dotnet]. The mechanic is one byte:

1. Locate `ntdll!EtwEventWrite` (or in modern variants `ntdll!NtTraceEvent`) in the calling process's memory.
2. Use `VirtualProtect` to make the page writable.
3. Write the byte `0xC3` over the function's first byte.
4. Restore the page protection.

`0xC3` is the [near-return opcode](https://www.felixcloutier.com/x86/ret) [@felixcloutier-ret]: "C3 RET ZO Valid Valid Near return to calling procedure." Any caller into the function falls straight back to its return address before producing a single event. The calling process now silently fails to emit any user-mode ETW events for any provider that funnels through the patched stub -- including `Microsoft-Windows-DotNETRuntime`.

The technique has been re-implemented in every language that can call `VirtualProtect`. The [fluxsec.red Rust port](https://fluxsec.red/etw-patching-rust) [@fluxsec-etw-patching] explains the modern variant verbatim:

> "When a ETW Provider sends a notification, it will eventually reach into ntdll.dll for the function NtTraceEvent... we can simply patch the function address to return straight from byte 0. The opcode for a ret is C3, so we can swap out the opcode 4C with C3 to immediately return out of the stub." -- [@fluxsec-etw-patching]

Here is the structure of the patch in TypeScript pseudocode -- not actually runnable Win32, but mirroring exactly what a Windows binary would do:

<RunnableCode lang="ts" title="The Adam Chester 2020 EtwEventWrite patch, illustrated in pseudocode">{`
// Pseudocode: silence user-mode ETW for the calling process.
// This silences only the calling process and only user-mode providers
// that funnel through the patched stub.

// 1. Resolve the address of ntdll!EtwEventWrite in this process.
const ntdll = getModuleHandle("ntdll.dll");
const fn = getProcAddress(ntdll, "EtwEventWrite");

// 2. Make the function's first page writable.
const PAGE_EXECUTE_READWRITE = 0x40;
let oldProtect = 0;
virtualProtect(fn, 1, PAGE_EXECUTE_READWRITE, /* out */ ref(oldProtect));

// 3. Write 0xC3 (RET) over the first byte. Caller now returns immediately.
writeByte(fn, 0xC3);

// 4. Restore original page protection.
virtualProtect(fn, 1, oldProtect, /* out */ ref(oldProtect));

// Limits:
// - Silences only this process.
// - Silences only providers whose emit path funnels through this stub.
// - Cannot silence kernel-emitted providers like Microsoft-Windows-Threat-Intelligence.
`}</RunnableCode>

> **Note:** The patch operates on the calling process's user-mode trampoline. Other processes on the host are unaffected; their ETW emissions continue normally. Kernel-emitted providers like `Microsoft-Windows-Threat-Intelligence` are unaffected even in the patched process; they fire from the kernel side of the syscall path, after control has crossed the user/kernel boundary, on a code path the user-mode patcher cannot reach without first achieving kernel execution.

### Gen 3 (2021-2023): kernel-mode primitives

If a user-mode patch cannot reach EtwTi, can a kernel-mode patch? Yes -- but the attacker first needs kernel execution. The most common path is [BYOVD](https://paragmali.com/blog/wdac--hvci-code-integrity-at-every-layer-in-windows/): load a signed but vulnerable driver and use its primitive to read or write kernel memory. Once you can write kernel memory you can target ETW's internal data structures directly.

[Binarly's Black Hat Europe 2021 talk](https://www.binarly.io/posts/Design_issues_of_modern_EDRs_bypassing_ETW-based_solutions/index.html) [@binarly-edr] documents the surface verbatim:

<PullQuote>
"Many ways to disable ETW logging are publicly available from passing a TRUE boolean parameter into a `nt!EtwpStopTrace` function to finding an ETW specific structure and dynamically modifying it or patching `ntdll!ETWEventWrite` or `advapi32!EventWrite` to return immediately thus stopping the user-mode loggers." -- [@binarly-edr]
</PullQuote>

The kernel-side primitives Binarly enumerates target the `_ETW_GUID_ENTRY` structure for a provider, the `EtwpRegistration` linked list of registered providers, and the `EtwpEventTracingProhibited` flag the kernel checks before emitting events. [Yarden Shafir's 2023 Trail of Bits walkthrough](https://blog.trailofbits.com/2023/11/22/etw-internals-for-security-research-and-forensics/) [@trailofbits-shafir] provides the contemporary kernel-side data structure walk through `_ETW_REALTIME_CONSUMER` and `_ETW_SILODRIVERSTATE`, and notes:

> "Most recently, the Lazarus Group bypassed EDR detection by disabling ETW providers" -- [@trailofbits-shafir]

The architectural-level treatment is well-documented; the specific kernel offsets that change between Windows builds are a moving target. We treat the technique class as well-established and the per-build offset details as out of scope.

### Defense Gen 1 (2017): Antimalware-PPL + ELAM gate on EtwTi

Section 9 covered this in detail. The point to record here, in the attack-tradition timeline, is that the Antimalware-PPL gate predates the Adam Chester 2020 user-mode patch by three years. Microsoft did not respond to Chester's post; they had already put the load-bearing security signal structurally out of reach of any user-mode patch in the calling process. The user-mode patch class is generic against `Microsoft-Windows-DotNETRuntime` and the rest of the user-mode catalogue; it is structurally impotent against `Microsoft-Windows-Threat-Intelligence`.

### Defense Gen 2 (2022): Vulnerable Driver Blocklist on by default

The kernel-mode primitive class needs a kernel write. Without a vulnerability in the EDR's kernel driver, the realistic path is BYOVD: load a third-party signed driver that exposes a memory-write primitive. The structural defense is Microsoft's [Vulnerable Driver Blocklist](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules) [@ms-vdb]:

<PullQuote>
"Since the Windows 11 2022 update, the vulnerable driver blocklist is enabled by default for all devices, and can be turned on or off via the Windows Security app... the vulnerable driver blocklist is also enforced when either memory integrity, also known as hypervisor-protected code integrity (HVCI), Smart App Control, or S mode is active... The blocklist is updated quarterly." -- [@ms-vdb]
</PullQuote>

The blocklist enumerates known-vulnerable signed drivers by hash; the kernel refuses to load anything on the list. On a Windows 11 22H2-or-later host with the default settings, the BYOVD primitive against most known-vulnerable drivers is closed. With HVCI on, the closure is enforced even against attackers who would otherwise try to load drivers via legacy paths. The empirical bound is the LOLDrivers project's catalogue of known-vulnerable drivers; the blocklist tracks public discovery with a lag of approximately one quarter, which is the residual window an attacker can exploit before a freshly disclosed driver is added.

<Definition term="BYOVD (Bring Your Own Vulnerable Driver)">
The attack pattern of loading a known-vulnerable but signed driver to obtain a kernel-mode primitive (memory read, memory write, or arbitrary code execution). Used in real-world EDR-blinding attacks, including by the Lazarus Group as cited in Trail of Bits' 2023 ETW walk [@trailofbits-shafir].
</Definition>

<Definition term="Vulnerable Driver Blocklist">
The Microsoft-maintained blocklist of known-vulnerable signed drivers, by hash. Enabled by default on Windows 11 22H2 and later. Enforced more strictly when HVCI, Smart App Control, or S mode is active. Updated quarterly per the Microsoft Learn primary [@ms-vdb].
</Definition>

<Sidenote>
The [LOLDrivers project](https://www.loldrivers.io/) [@loldrivers] is the empirical anchor for the BYOVD lag story. It catalogues known-vulnerable signed drivers as a community resource; the Microsoft blocklist updates quarterly, so a freshly-disclosed driver lives in a 2-3 month exploitation window before its hash is added.
</Sidenote>

<Mermaid caption="The five-generation attack/defense parallel chain: each attack generation targets a different layer (registry, user-mode, kernel) and is countered by a structural defense (autologger monitoring, then PPL+ELAM gate on EtwTi, then VBL+HVCI to close the BYOVD primitive).">
flowchart LR
    subgraph Attacks
        A1["Gen 1 2014-2018: Autologger registry tampering -- Palantir CIRT taxonomy"]
        A2["Gen 2 2020: EtwEventWrite 0xC3 RET -- Adam Chester"]
        A3["Gen 3 2021-2023: Kernel _ETW_GUID_ENTRY -- EtwpRegistration EtwpStopTrace via BYOVD"]
    end
    subgraph Defenses
        D1["Sysmon Event ID 13 -- monitor Autologger subtree"]
        D2["Antimalware-PPL plus ELAM -- gate on EtwTi 2017"]
        D3["Vulnerable Driver Blocklist -- default-on Win11 22H2 plus HVCI"]
    end
    A1 --> D1
    A2 --> D2
    A3 --> D3
</Mermaid>

### The 2026 picture

User-mode patching cannot reach the kernel-mode provider that EDR cares about. The BYOVD primitive that could reach it is structurally narrowed by default on supported hardware. The remaining gap is the long tail of newly-disclosed vulnerable drivers between disclosure and blocklist update, plus any custom kernel zero-day an attacker discovers in an EDR's own driver. Both are real, both are exploited in the wild, neither is the universally-applicable evasion the 2020-era user-mode patch class was.

That is the operational story. But ETW has structural limits even when no attacker is patching anything.

## 12. Theoretical limits: what ETW cannot see, even with every defence engaged

Even on a perfectly-configured Windows 11 box -- [HVCI](https://paragmali.com/blog/wdac--hvci-code-integrity-at-every-layer-in-windows/) on, Vulnerable Driver Blocklist on, Antimalware-PPL Defender consuming EtwTi, third-party EDR ELAM-onboarded -- there are events ETW does not emit. Some are observed too late. Some are not observed at all.

There are three structural ceilings.

### Pre-ETW kernel paths

The Global Logger session is one of the earliest things to come up at boot, but it is not the first. Some early-init driver paths run before any ETW session exists; they cannot be traced via ETW. Measured Boot is the discipline that records this prefix into TPM PCRs, with attestation handled by the platform integrity layer rather than by ETW. The implication for EDR is that any malicious code executing during early boot, before the Global Logger session is up, is invisible to ETW.

### Incomplete EtwTi syscall coverage

The 10 `KERNEL_THREATINT_TASK_*` task IDs are the public surface. The underlying syscall set the kernel actually instruments is not exhaustively documented. The fluxsec.red [inventory](https://fluxsec.red/event-tracing-for-windows-threat-intelligence-rust-consumer) [@fluxsec-eti] is the public surface, not the private one. Some syscalls are clearly covered (`NtAllocateVirtualMemory` for cross-process allocation surfaces as `KERNEL_THREATINT_TASK_ALLOCVM`); some have partial coverage (`MAPVIEW_LOCAL` and `MAPVIEW_REMOTE` keywords cover some but not all of the section-mapping primitive set across `NtCreateSection`, `NtMapViewOfSection`, `NtMapViewOfSectionEx`, image-section vs file-section variants); some are not enumerated at all in the public manifest. Process-hollowing primitives that combine `NtUnmapViewOfSection` and `NtMapViewOfSection` may be partially covered depending on which path the attacker takes.

### The async-flush gap

ETW's per-CPU ring buffer is asynchronous. If a process allocates RWX memory, writes shellcode, executes it, and returns within one writer-thread flush interval, the event is *recorded* but the attacker's payload has *already executed*. The synchronous denial primitive on Windows belongs to kernel notify routines, not to ETW. The [Microsoft Learn primary on About Event Tracing](https://learn.microsoft.com/en-us/windows/win32/etw/about-event-tracing) [@ms-about-etw] is explicit that events can be lost:

> "Events can be lost if any of the following conditions occur ... The total event size is greater than 64K ... The disk is too slow to keep up with the rate at which events are being generated. ... For real-time logging, the real-time consumer is not consuming events fast enough." -- [@ms-about-etw]

No ETW-only EDR can prevent a syscall whose payload completes inside one writer flush. EDRs that ship a kernel driver and register synchronous callbacks (CrowdStrike, SentinelOne, Sysmon, Elastic Defend) can deny operations through the [`PsSetCreateProcessNotifyRoutineEx`](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreateprocessnotifyroutineex) [@ms-pssetprocnotify] `CreationStatus` field; ETW-only EDRs cannot. ETW is observation, not enforcement.

> **Key idea:** ETW is observation, not enforcement. The synchronous denial primitive on Windows belongs to kernel notify routines, not to ETW. Sub-microsecond payloads execute before the writer thread flushes; the layered defense stack of 2026 is an empirical bar, not a theoretical guarantee.

<Definition term="HVCI (Hypervisor-Protected Code Integrity)">
The VBS-backed code-integrity enforcement for kernel-mode code on Windows. With HVCI enabled, the hypervisor enforces that only signed kernel pages can execute. Closes the attack class that loads unsigned drivers; combined with the Vulnerable Driver Blocklist it closes most of the realistic BYOVD primitive surface as well.
</Definition>

<Sidenote>
The "events can be lost" enumeration in [@ms-about-etw] is the dispositive Microsoft acknowledgement of ETW's lossy substrate. SOC playbooks should treat ETW telemetry as best-effort, not as a guaranteed audit trail. Forensic claims that depend on completeness need an independent corroborating source.
</Sidenote>

> **Note:** A detection-only EDR can alert on a malicious operation, but only after the operation has happened. By the time the SOC sees the alert, the syscall has completed, the shellcode has executed, the credentials have been stolen. This is why the kernel-callback path (with its ability to deny via `CreationStatus`) coexists with ETW even though ETW is more flexible: a SOC playbook needs both the speed of denial and the breadth of observation.

The 2026 layered stack -- Antimalware-PPL + EtwTi + HVCI + VBL -- raises the empirical bar enormously. It does not close the architectural gap. Sub-microsecond payloads still execute before the writer thread flushes. The BYOVD primitive on a non-HVCI box still defeats the kernel-callback layer. There are still problems the substrate cannot solve in principle.

Those are the limits we can describe. The next section is about the limits we cannot yet measure.

## 13. Open problems: keyword drift, secure kernel ETW, and the BYOVD arms race

The 2026 state of the art has five active open problems. Each has a partial workaround; none has a complete solution.

### 1. EtwTi keyword inventory drift across builds

Microsoft has not published a complete, current `Microsoft-Windows-Threat-Intelligence` keyword inventory. The community-maintained references -- the [jdu2600 cross-build inventory](https://github.com/jdu2600/Windows10EtwEvents) [@gh-jdu2600] and the [repnz manifest archive](https://github.com/repnz/etw-providers-docs) [@gh-repnz] -- are partial coverage and lag Microsoft's quarterly servicing cadence. EDR vendors that hard-code keyword bitmasks against an old build can silently miss events on newer builds because the keyword definitions have shifted underneath them. Detection engineers writing rules against `KERNEL_THREATINT_TASK_*` IDs that move between builds can get false negatives.

<Aside label="Why Microsoft has not published a TI keyword inventory">
There are three plausible reasons, and Microsoft has not stated which (or which combination) is operative. *Operational secrecy*: a complete keyword inventory tells attackers exactly which syscall paths are observed and which are not, narrowing the search for evasion paths. *Documentation cost*: the inventory shifts every build, and maintaining a synchronised public reference is engineering work without an obvious internal champion. *Deliberate moving target*: keeping the public surface incomplete forces attackers to reverse-engineer per build, raising the cost of stable evasion. The community references partially defeat all three rationales; the absence remains.
</Aside>

### 2. Secure ETW (the `EtwSi*` family)

Windows VBS Trustlets run in the Secure Kernel (VTL1), insulated from the normal-world kernel (VTL0) by the hypervisor. The Secure Kernel exposes its own ETW family for VTL1 components; this is enumerated in fragments in Alex Ionescu's BlackHat 2015 deck on the Secure Kernel and in subsequent BlueHatIL talks. There is no public consumer-facing primary on `EtwSi*` in 2026. Cross-link: this article's [companion piece on VBS Trustlets](https://paragmali.com/blog/vbs-trustlets-what-actually-runs-in-the-secure-kernel/) [@paragmali-vbs-trustlets] covers the producer side of the story.

### 3. Forensic soundness of ETW telemetry

ETW is lossy by design (per the [@ms-about-etw] enumeration). Whether ETW-derived telemetry is *forensically sound* -- chain-of-custody complete, lossless under load, attestable as untampered between event emission and SIEM ingestion -- is an open question. Courts have not ruled. The current best partial result is to treat ETW as supporting evidence and require independent corroboration (file-system snapshots, network captures, OS state captures) for any claim that depends on completeness. Sysmon's Event ID 16 (Sysmon configuration changed) [@ms-sysmon] and the autologger registry write events on `HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\` are useful integrity signals: an attacker who silenced ETW typically leaves a footprint here.

### 4. The BYOVD arms race

The [Vulnerable Driver Blocklist](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules) [@ms-vdb] is hash-based and updated quarterly. The [LOLDrivers project](https://www.loldrivers.io/) [@loldrivers] documents the public catalogue of known-vulnerable signed drivers. The 2-3 month gap between disclosure and blocklist update is the residual exploitation window. The deeper structural issue is that the blocklist is hash-based; an attacker who finds a new vulnerability in a previously-trusted signed driver enjoys a fresh window every quarter. Closing this gap requires either a different trust model (allow-listing of known-good drivers, as Smart App Control does for executables) or behavioural detection of suspicious driver loads. Both are active areas of work.

### 5. Cross-process section-mapping coverage

EtwTi's `KERNEL_THREATINT_TASK_MAPVIEW` covers some but not all section-mapping primitives. The public fluxsec.red [@fluxsec-eti] inventory lists `MAPVIEW_LOCAL` and `MAPVIEW_REMOTE` keywords, but the underlying syscall set (`NtMapViewOfSection`, `NtMapViewOfSectionEx`, `NtCreateSection`, image-section vs file-section variants) is not exhaustively documented. Detection engineers who depend on full coverage of cross-process section mapping are working from an incomplete map.

### What would a v2 ETW look like?

A theoretical ideal: synchronous kernel-emitted events on every security-relevant syscall, with the consumer running in VTL1 (Secure Kernel) so even a kernel-mode attacker in VTL0 cannot tamper with the consumer. The `EtwSi*` family is the partial realisation. The full ideal is incompatible with x64 syscall performance: synchronous notification on every syscall would dominate the cost of the syscall itself. The pragmatic answer Microsoft has been building toward is *selective* synchronous notification (the kernel notify routines for high-value control points) layered with *broad* asynchronous observation (ETW for everything else), with the most security-critical of the broad observations promoted to PPL/ELAM-gated kernel-emitted producers (EtwTi). Two decades of layering, no single architectural endpoint.

<Sidenote>
For the producer side of the Secure Kernel ETW story (`EtwSi*`), see this article's [companion piece on VBS Trustlets](https://paragmali.com/blog/vbs-trustlets-what-actually-runs-in-the-secure-kernel/) [@paragmali-vbs-trustlets] in the same series. The Trustlet-side architecture is a separate topic large enough to need its own walkthrough.
</Sidenote>

Open problems are interesting but they are not actionable. The next section is about what an engineer can do on Monday morning.

## 14. Practical guide: five things to do Monday morning

You have read 12,000 words about ETW. Here are five concrete checks an engineer can run on a Windows host this morning.

> **Note:** `logman query providers` enumerates every registered provider on the host. Cross-reference the output against the section 8 catalogue and flag any security-relevant provider your EDR is not consuming. Pay specific attention to `Microsoft-Antimalware-Scan-Interface`, `Microsoft-Windows-PowerShell`, `Microsoft-Windows-DotNETRuntime`, and `Microsoft-Windows-Sysmon` if Sysmon is installed. Missing coverage of any of these on a host you are responsible for is a detection-coverage gap, not a configuration issue.

> **Note:** Run `wevtutil gp Microsoft-Windows-Threat-Intelligence` to confirm the provider is registered and inspect its keyword definitions. Then check whether your EDR is actually a consumer: walk the live-debugger handle enumeration in [Yarden Shafir's Trail of Bits post](https://blog.trailofbits.com/2023/11/22/etw-internals-for-security-research-and-forensics/) [@trailofbits-shafir] (the WinDbg JS scripts are linked from the post). If your EDR is supposed to be ELAM-onboarded but does not appear in the consumer enumeration for an EtwTi logger session, your installation may have lost the gate. This is the difference between a configured EDR and a functional EDR.

> **Note:** Enumerate `HKLM\SYSTEM\CurrentControlSet\Control\WMI\Autologger\` for unauthorised session entries. Per the [Palantir CIRT taxonomy](https://web.archive.org/web/2023/https://blog.palantir.com/tampering-with-windows-event-tracing-background-offense-and-defense-4be7ac62ac63) [@palantir-tampering-wayback], this is the persistent-tampering surface. A baseline audit should produce a known list of expected sessions (Defender, your EDR, Sysmon if installed, the standard Windows diagnostic listeners). Any subkey not on the baseline list is an investigation candidate. Sysmon Event ID 13 (registry value set) [@ms-sysmon] on this subtree is a high-signal alert in any SIEM.

> **Note:** Run `Get-CimInstance Win32_DeviceGuard | Select-Object SecurityServicesConfigured, SecurityServicesRunning, VirtualizationBasedSecurityStatus` to expose whether HVCI and the Vulnerable Driver Blocklist are active. Per the [Microsoft Learn primary](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules) [@ms-vdb], the BYOVD ceiling is your kernel-tampering integrity guarantee. If VBS is `Off` on a managed endpoint, your detection coverage is structurally weaker than it should be on supported hardware. Treat it as a hardening item, not a nice-to-have.

> **Note:** Write a hunting query for the pattern: "process X registers as ETW consumer for `Microsoft-Windows-Threat-Intelligence` and X is not on the EDR allow-list." The provider's PPL+ELAM gate makes this a high-signal alert: only a signed Antimalware-PPL service can pass the gate, so an unexpected process holding an `EtwConsumer` handle to the TI logger ID is either a misconfigured tool, a legitimate research session you forgot about, or an attacker chain that has acquired Antimalware-PPL trust on your fleet. The first two are quick to triage; the third is an incident.

The structure of the check in pseudocode -- mirroring the WinDbg JS approach in [@trailofbits-shafir]:

<RunnableCode lang="ts" title="Check provider availability and consumer fingerprints (PowerShell-equivalent logic)">{`
// Pseudocode: inventory providers and identify EtwTi consumers.

// 1. Enumerate registered providers and find Microsoft-Windows-Threat-Intelligence.
const providers = enumerateRegisteredProviders();
const tiProvider = providers.find(p => p.guid === "{f4e1897c-bb5d-5668-f1d8-040f4d8dd344}");
if (!tiProvider) {
  warn("EtwTi provider not registered on this host");
}

// 2. Enumerate live trace sessions and find any that subscribe to TI.
const sessions = enumerateLoggerSessions();  // logman query -ets equivalent
const tiSessions = sessions.filter(s =>
  s.providers.some(p => p.guid === tiProvider?.guid));

// 3. Walk EtwConsumer handles for each TI session; identify the consuming processes.
const expectedConsumers = ["MsMpEng.exe", "CSFalconService.exe", "SentinelAgent.exe"];
for (const session of tiSessions) {
  const consumers = enumerateEtwConsumers(session.loggerId);  // Shafir WinDbg JS
  for (const consumer of consumers) {
    if (!expectedConsumers.includes(consumer.processName)) {
      alert(\`Unexpected EtwTi consumer: \${consumer.processName} (PID \${consumer.pid})\`);
    }
  }
}

// 4. Audit autologger persistence entries against a known baseline.
const baseline = loadAutologgerBaseline();
const live = enumerateAutologgerSubkeys();  // HKLM\\SYSTEM\\CurrentControlSet\\Control\\WMI\\Autologger
for (const entry of live) {
  if (!baseline.includes(entry.name)) {
    alert(\`Unexpected autologger entry: \${entry.name}\`);
  }
}
`}</RunnableCode>

With those five checks, the catalogue is no longer an abstraction. You have an inventory of what your host emits, an inventory of who consumes the most security-critical provider, an audit of the persistence surface that defines what gets emitted at all, a confirmation of the integrity layer that closes BYOVD, and a hunt for anyone who has somehow obtained the passport. Now we close with the questions every reader should expect to have.

## 15. Frequently asked questions

<FAQ title="Frequently asked questions">

<FAQItem question="Does Sysmon use ETW?">
Yes, for *publication*. Sysmon's kernel driver `SysmonDrv.sys` registers `PsSetCreateProcessNotifyRoutineEx` and the related thread- and image-load callbacks; the user-mode service then publishes the resulting events via its own `Microsoft-Windows-Sysmon` ETW provider GUID `{5770385F-C22A-43E0-BF4C-06F5698FFBD9}` [@ms-sysmon]. It does not consume the public catalogue providers via ETW for its kernel-event hot path; the kernel taps come straight from the callback API. This callback-then-publish architecture is why Sysmon's events are universally consumable by SIEM forwarders and downstream tools.
</FAQItem>

<FAQItem question="Why doesn't ETW patching defeat Defender?">
Because Defender consumes `Microsoft-Windows-Threat-Intelligence`, which fires from the kernel side of memory-modifying syscalls, not from the user-mode `ntdll!EtwEventWrite` trampoline. The fluxsec.red walkthrough states the asymmetry verbatim: "we cannot patch out the Threat Intelligence provider as this is emitted from within the kernel itself" [@fluxsec-eti]. The Adam Chester 2020 patch silences user-mode providers (like `Microsoft-Windows-DotNETRuntime`) for the patched process; it cannot silence kernel-emitted providers for any process. Defender's load-bearing security signal is structurally out of reach of the user-mode patch class.
</FAQItem>

<FAQItem question="Can a normal user-mode service consume Microsoft-Windows-Threat-Intelligence?">
No. The provider's security descriptor admits only Antimalware-PPL signers loaded by an ELAM driver. A non-PPL `EnableTraceEx2` call against the EtwTi GUID returns `ERROR_ACCESS_DENIED` (the [Microsoft Learn primary on EnableTraceEx2](https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-enabletraceex2) [@ms-enabletraceex2] documents the error code for insufficient-privilege callers; the PPL-specific gate that triggers it for EtwTi is described in [@fluxsec-eti]). The gate exists because an attacker who could trivially become an EtwTi consumer would have direct visibility into the kernel's view of every memory-modifying syscall on the host -- exactly the inventory needed to evade everything else.
</FAQItem>

<FAQItem question="What's the difference between manifest-based and TraceLogging providers?">
Schema location. Manifest-based providers ship an out-of-band XML manifest registered with `wevtutil im`; consumers decode events against the system-installed manifest using TDH. TraceLogging providers carry the schema *inline* in each event payload as type-length-value triples; consumers decode without any registered manifest. TraceLogging events are larger because the schema bytes ride in the payload; manifest events have a smaller per-event size at the cost of installation friction. Both inherit the eight-session cap [@ms-about-etw], [@ms-tracelogging-about].
</FAQItem>

<FAQItem question="What is the maximum number of ETW sessions on a system?">
Sixty-four globally per [@ms-etw-sessions], with Windows 2000 limited to 32. Per-provider, manifest-based and TraceLogging providers admit up to 8 simultaneous sessions; classic and WPP providers admit only 1 [@ms-about-etw], [@ms-etw-config]. The runtime symptom of the per-provider 8-session cap binding is `ERROR_NO_SYSTEM_RESOURCES` from `EnableTraceEx2` [@ms-enabletraceex2]; the runtime symptom of the global 64-session cap binding is the same error from `StartTrace`.
</FAQItem>

<FAQItem question="Is ETW deprecated by EventPipe or dotnet-trace?">
No. EventPipe is a managed-runtime cross-platform analogue to ETW that shipped in .NET Core 3.0 (September 2019) and remains available in every later release including .NET 5+. It runs on Linux and macOS as well as Windows. On Windows, the kernel-mode providers and the EtwTi security substrate have no EventPipe equivalent; EventPipe is a complement to ETW for managed workloads, not a replacement. The Windows EDR substrate remains ETW; managed-runtime tracing has acquired an additional cross-platform path that does not displace it.
</FAQItem>

</FAQ>

<StudyGuide slug="etw-event-tracing-for-windows-and-the-edr-substrate" keyTerms={[
  { term: "ETW", definition: "Event Tracing for Windows: kernel-buffered observability bus introduced in Windows 2000." },
  { term: "Provider", definition: "A component that emits ETW events tagged with a GUID." },
  { term: "Controller", definition: "A component that creates, configures, and stops trace sessions." },
  { term: "Consumer", definition: "A component that reads events from a session in real time or from an .etl file." },
  { term: "Manifest-based provider", definition: "Vista-era ETW provider class with XML manifest schema and 8-session cap." },
  { term: "TraceLogging", definition: "Self-describing ETW provider class with inline TLV schema, shipped in Windows 10." },
  { term: "EtwTi", definition: "Microsoft-Windows-Threat-Intelligence: the kernel-emitted memory-syscall provider; PPL+ELAM-gated." },
  { term: "Antimalware-PPL", definition: "Signer level on the PPL lattice for antimalware services; gates EtwTi consumption." },
  { term: "ELAM", definition: "Early Launch Antimalware: driver class that gates the certificate inventory for permitted Antimalware-PPL binaries." },
  { term: "BYOVD", definition: "Bring Your Own Vulnerable Driver: load a known-vulnerable signed driver to obtain kernel primitive." },
  { term: "Vulnerable Driver Blocklist", definition: "Microsoft-maintained hash blocklist; default-on in Windows 11 22H2." },
  { term: "Autologger", definition: "Registry-persisted boot-time ETW session under HKLM\\SYSTEM\\CurrentControlSet\\Control\\WMI\\Autologger\\." }
]} />

ETW is now twenty-six years old. It started as a performance facility for Windows 2000 driver authors who could not afford `DbgPrint` on production servers, and it became the substrate of every major Windows endpoint security product through a decade of unintended consequences. The Vista team that raised the per-provider session cap from 1 to 8 was thinking about ergonomics. The Windows 8.1 team that introduced Antimalware-PPL was thinking about Defender's hardening, not about future third-party EDRs. The team that shipped EtwTi in the Windows 10 RS-era understood the security stakes precisely. By 2026 those three decisions, taken in three different Microsoft contexts a decade apart, are the architecture of detection on the Windows endpoint -- and the reason the operator in the section 1 hook scene loses the round even when the patch works exactly as it should.
