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.
Permalink1. Why didn't the patch silence Defender?
A red-team operator drops onto a 2026 Defender-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 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 [1], 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 [2], 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 [3] -- 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.
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.
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.
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.
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
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 [4] -- 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 [5] 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:
"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 [4]
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 [6]. 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.
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.
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.
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.
Diagram source
flowchart LR
Ctl[Controller
StartTrace + EnableTrace] --> Sess[Trace Session
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
asynchronous drain]
CPU1 --> WT
CPUN --> WT
Sess -.governs.-> CPU0
Sess -.governs.-> CPU1
Sess -.governs.-> CPUN
WT --> File[(.etl file)]
WT --> RT[Real-time consumer
OpenTrace + ProcessTrace] The original Windows 2000 implementation supported 32 trace sessions running simultaneously [7], 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 [6] -- 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.
Windows 2000's 32-session global cap [7] 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.
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:
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.
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.
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.
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 [7], 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.
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.
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:
"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 [4]
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 [9] 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:
"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 [4]
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.
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.
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.
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 [10] explicitly, and notes that each process can register up to 1,024 providers [10]. In practice few processes come close.
Diagram source
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
for ETW sessions]
H --> J[Channel demux
Admin / Operational / Analytical / Debug]
J --> K[(.evtx log files)]
I --> L[Real-time consumers]
E -.decode metadata.-> L
E -.decode metadata.-> K The cap rules now read like this: eight trace sessions can enable a manifest-based provider concurrently [11]; up to 64 sessions can run on the system at once [7]; EnableTraceEx2 returns ERROR_NO_SYSTEM_RESOURCES when the per-provider cap binds [12]. The 8-session number was chosen for ergonomics, not for security planning, but it is the load-bearing number in modern Windows endpoint security.
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-Processwould silently steal events from the first.
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" [4] 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 [13] 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."
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:
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.
"WPP traces compounds the issues, providing almost no easy-to-find data about provider and their events." -- Sealighter README [14]
WPP providers also inherit the classic one-session-per-provider cap [11], 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 [15] 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 [15]
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.
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.
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 [16]. 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: [11, 8, 15, 13].
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 [5] with a session name, buffer size, logging mode, and destination, then calls StartTrace. The kernel allocates the buffers -- at least two per logical processor [5] -- and returns a session handle. The controller then calls EnableTraceEx2 [12] 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.
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." [5] Production session buffer sizes typically sit in the 32-64KB range.
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.
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.
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.
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.
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 [17] (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.
Diagram source
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] ERROR_NO_SYSTEM_RESOURCES from EnableTraceEx2 is the runtime symptom of the eight-session cap binding [12]. 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.
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.
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.
Diagram source
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 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.
Microsoft.Diagnostics.Tracing.TraceEvent [19] 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 [20] which Microsoft uses internally to analyze ETW data from the Windows engineering system.
SilkETW [21], originally released by Ruben Boonen at FireEye in March 2019 [9] (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 [14], 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." -- [18]
"Throwing exceptions in the event handler callback ... will cause the trace to stop processing events." -- [18]
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.
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.
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:
// 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 Press Run to execute.
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.
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 [22] tracks the count across builds, and the repnz manifest archive [23] 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) [24], 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 [9]. 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 [25] and surveyed in the Palantir CIRT taxonomy [17]. 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.
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 [25]; the .NET CLR's assembly load path joined the list with .NET Framework 4.8, as documented in Adam Chester's CLR walk-through [2]. 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.
The AMSI Operational event log channel typically appears empty by default. The Palantir taxonomy [17] 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.
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 [26] 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 [2]. 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.
This is the provider Adam Chester targeted in the canonical March 17, 2020 ETW patching post [2]. 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.
Microsoft-Windows-Sysmon
GUID {5770385F-C22A-43E0-BF4C-06F5698FFBD9}, surfaced by wevtutil gp Microsoft-Windows-Sysmon and inventoried in [22]; the Microsoft Learn Sysmon page by Russinovich and Garnier [27] 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 [3]. 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(NtAllocateVirtualMemorycross-process)PROTECTVM(NtProtectVirtualMemory)MAPVIEW(section mapping; cross-process and self)QUEUEUSERAPC(NtQueueApcThreadcross-process)SETTHREADCONTEXT(NtSetContextThreadcross-process)READVM(NtReadVirtualMemorycross-process)WRITEVM(NtWriteVirtualMemorycross-process)SUSPENDRESUME_THREADSUSPENDRESUME_PROCESSDRIVER_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 [28] 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 [28]
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.
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 [29], 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) | [24] |
| Microsoft-Windows-Kernel-Process | {22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716} | Process / thread / image-load events | None (admin) | [9], [22] |
| Microsoft-Windows-Kernel-File | (manifest archive) | File I/O syscalls | None (admin) | [22], [23] |
| Microsoft-Windows-Kernel-Network | (manifest archive) | TCP/UDP send/receive | None (admin) | [22], [23] |
| Microsoft-Windows-Kernel-Registry | (manifest archive) | Registry create/open/set/delete | None (admin) | [22], [23] |
| Microsoft-Antimalware-Scan-Interface | {2A576B87-09A7-520E-C21A-4942F0271D67} | Post-deobfuscation script content | None (admin) | [25], [17] |
| Microsoft-Windows-PowerShell | {a0c1853b-5c40-4b15-8766-3cf1c58f985a} | Script-block logging (4104), pipeline | None (admin) | [22] |
| Microsoft-Windows-DotNETRuntime | {e13c0d23-ccbc-4e12-931b-d9cc2eee27e4} | CLR assembly load, JIT, exceptions | None (admin) | [2] |
| Microsoft-Windows-Sysmon | {5770385F-C22A-43E0-BF4C-06F5698FFBD9} | Sysmon driver re-publication | None (admin) | [22], [27] |
| Microsoft-Windows-Threat-Intelligence | {f4e1897c-bb5d-5668-f1d8-040f4d8dd344} | Memory-modifying syscalls (kernel-emitted) | PPL + ELAM (Antimalware signer level) | [3], [28] |
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, (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 driver registered at boot. Two of those three were not possible for third parties until the Windows 10 RS-era.
fluxsec.red [3] 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." -- [3]
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 [30] 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." -- [30]
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.
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.
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 [12] 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 [3] prerequisite list).
Early Launch Antimalware
ELAM is a driver class that loads before any other non-Microsoft boot driver. The Microsoft Learn primary [31] 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." -- [31]
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.
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.
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 [29]. 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" -- [29]
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.
Diagram source
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 Why this gate matters for the section 1 hook
The asymmetry that defines the entire generation is one sentence in the fluxsec.red walkthrough [3]:
"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." -- [3]
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.
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.
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 [32] and PsSetCreateThreadNotifyRoutine [33] 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.
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.
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 [29]. 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 [27] -- 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 [28], 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 | [30] |
| CrowdStrike Falcon | Driver callbacks + ETW | Antimalware-PPL | Yes ([29] live evidence) | [29] |
| SentinelOne | Driver callbacks + ETW | Antimalware-PPL | Widely reported | -- (vendor docs; SentinelAgent.exe not in [29] sample) |
| Sysmon | SysmonDrv.sys callbacks; publishes via own ETW provider | Protected (not Antimalware) | No | [27] |
| Wazuh | ETW only (SilkETW-class) | Standard SYSTEM | No | -- |
| Elastic Defend | Own kernel driver + ETW | Standard SYSTEM | No | [28] |
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.
Diagram source
flowchart LR
K[Kernel callbacks
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
asynchronous, observe-only
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 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 [34]. That story belongs to the App Identity in Windows article 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.
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 [17], 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." -- [17]
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 [27] 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 [35], 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 [2]. The mechanic is one byte:
- Locate
ntdll!EtwEventWrite(or in modern variantsntdll!NtTraceEvent) in the calling process's memory. - Use
VirtualProtectto make the page writable. - Write the byte
0xC3over the function's first byte. - Restore the page protection.
0xC3 is the near-return opcode [1]: "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 [36] 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." -- [36]
Here is the structure of the patch in TypeScript pseudocode -- not actually runnable Win32, but mirroring exactly what a Windows binary would do:
// 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. Press Run to execute.
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: 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 [37] documents the surface verbatim:
"Many ways to disable ETW logging are publicly available from passing a TRUE boolean parameter into a
nt!EtwpStopTracefunction to finding an ETW specific structure and dynamically modifying it or patchingntdll!ETWEventWriteoradvapi32!EventWriteto return immediately thus stopping the user-mode loggers." -- [37]
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 [29] 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" -- [29]
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 [38]:
"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." -- [38]
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.
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 [29].
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 [38].
The LOLDrivers project [39] 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.
Diagram source
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 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 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 [3] 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 [11] 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." -- [11]
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 [32] CreationStatus field; ETW-only EDRs cannot. ETW is observation, not enforcement.
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.
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.
The "events can be lost" enumeration in [11] 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.
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 [22] and the repnz manifest archive [23] -- 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.
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 [40] covers the producer side of the story.
3. Forensic soundness of ETW telemetry
ETW is lossy by design (per the [11] 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) [27] 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 [38] is hash-based and updated quarterly. The LOLDrivers project [39] 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 [3] 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.
For the producer side of the Secure Kernel ETW story (EtwSi*), see this article's companion piece on VBS Trustlets [40] in the same series. The Trustlet-side architecture is a separate topic large enough to need its own walkthrough.
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.
The structure of the check in pseudocode -- mirroring the WinDbg JS approach in [29]:
// 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}`);
}
} Press Run to execute.
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
Frequently asked questions
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} [27]. 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.
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" [3]. 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.
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 [12] documents the error code for insufficient-privilege callers; the PPL-specific gate that triggers it for EtwTi is described in [3]). 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.
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 [11], [15].
What is the maximum number of ETW sessions on a system?
Sixty-four globally per [7], 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 [11], [8]. The runtime symptom of the per-provider 8-session cap binding is ERROR_NO_SYSTEM_RESOURCES from EnableTraceEx2 [12]; the runtime symptom of the global 64-session cap binding is the same error from StartTrace.
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.
Study guide
Key terms
- ETW
- Event Tracing for Windows: kernel-buffered observability bus introduced in Windows 2000.
- Provider
- A component that emits ETW events tagged with a GUID.
- Controller
- A component that creates, configures, and stops trace sessions.
- Consumer
- A component that reads events from a session in real time or from an .etl file.
- Manifest-based provider
- Vista-era ETW provider class with XML manifest schema and 8-session cap.
- TraceLogging
- Self-describing ETW provider class with inline TLV schema, shipped in Windows 10.
- EtwTi
- Microsoft-Windows-Threat-Intelligence: the kernel-emitted memory-syscall provider; PPL+ELAM-gated.
- Antimalware-PPL
- Signer level on the PPL lattice for antimalware services; gates EtwTi consumption.
- ELAM
- Early Launch Antimalware: driver class that gates the certificate inventory for permitted Antimalware-PPL binaries.
- BYOVD
- Bring Your Own Vulnerable Driver: load a known-vulnerable signed driver to obtain kernel primitive.
- Vulnerable Driver Blocklist
- Microsoft-maintained hash blocklist; default-on in Windows 11 22H2.
- Autologger
- 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.
References
- RET - Intel x86/x64 Instruction Reference. https://www.felixcloutier.com/x86/ret - Opcode anchor: C3 RET ZO Valid Valid Near return to calling procedure. ↩
- (2020). Hiding your .NET - ETW. https://blog.xpnsec.com/hiding-your-dotnet-etw/ - Canonical user-mode EtwEventWrite 0xC3 RET patch primary. ↩
- Event Tracing for Windows: Threat Intelligence Rust Consumer. fluxsec.red. https://fluxsec.red/event-tracing-for-windows-threat-intelligence-rust-consumer - PPL+ELAM+logging prerequisites; verbatim 10 KERNEL_THREATINT_TASK_* enumeration. ↩
- (2007). Event Tracing: Improve Debugging And Performance Tuning With ETW. MSDN Magazine. https://learn.microsoft.com/en-us/archive/msdn-magazine/2007/april/event-tracing-improve-debugging-and-performance-tuning-with-etw - Canonical primary on the Vista unified provider model and the Windows 2000 origin of ETW. ↩
- EVENT_TRACE_PROPERTIES structure. Microsoft Learn. https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-event_trace_properties - BufferSize semantics; 2 buffers per logical processor reservation. ↩
- Event Tracing for Windows (ETW). Microsoft Learn (Windows Driver Kit). https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/event-tracing-for-windows--etw- - Kernel-mode ETW API; provider/controller/consumer trichotomy. ↩
- Event Tracing Sessions. Microsoft Learn. https://learn.microsoft.com/en-us/windows/win32/etw/event-tracing-sessions - 64-session global ceiling and Windows 2000 32-session legacy cap. ↩
- Configuring and Starting an Event Tracing Session. Microsoft Learn. https://learn.microsoft.com/en-us/windows/win32/etw/configuring-and-starting-an-event-tracing-session - 8-session manifest cap and 1-session classic cap "second session steals first" semantics. ↩
- (2019). SilkETW: Because Free Telemetry is # FreeTelemetry. https://www.fireeye.com/blog/threat-research/2019/03/silketw-because-free-telemetry-is-free.html - SilkETW launch primary (March 2019); Win2000 ETW origin corroboration. ↩
- EventRegister function. Microsoft Learn. https://learn.microsoft.com/en-us/windows/win32/api/evntprov/nf-evntprov-eventregister - 1,024 providers per process registration limit. ↩
- About Event Tracing. Microsoft Learn. https://learn.microsoft.com/en-us/windows/win32/etw/about-event-tracing - The four provider classes and their per-class session caps, verbatim. ↩
- EnableTraceEx2 function. Microsoft Learn. https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-enabletraceex2 - ERROR_NO_SYSTEM_RESOURCES on 8-session cap; ERROR_ACCESS_DENIED for non-PPL caller. ↩
- WPP Software Tracing. Microsoft Learn. https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/wpp-software-tracing - WPP design intent and relationship to WMI event tracing. ↩
- Sealighter: ETW and WPP-based Threat Hunting Tool. GitHub. https://github.com/pathtofile/Sealighter - Sealighter codebase wrapping KrabsETW; multi-provider subscription model. ↩
- About TraceLogging. Microsoft Learn. https://learn.microsoft.com/en-us/windows/win32/tracelogging/trace-logging-about - TraceLogging self-describing event payload model. ↩
- TraceLogging. Microsoft Learn. https://learn.microsoft.com/en-us/windows/win32/tracelogging/trace-logging-portal - TraceLogging user-mode and kernel-mode availability. ↩
- (2018). Tampering with Windows Event Tracing: Background, Offense, and Defense. https://web.archive.org/web/2023/https://blog.palantir.com/tampering-with-windows-event-tracing-background-offense-and-defense-4be7ac62ac63 - Wayback mirror of December 24, 2018 Palantir CIRT post; canonical autologger-tampering taxonomy. ↩
- krabsetw - Microsoft. GitHub. https://github.com/microsoft/krabsetw - C++ ETW library; Office 365 Security production usage; common pitfalls. ↩
- Microsoft.Windows.EventTracing.Processing.All. NuGet Gallery. https://www.nuget.org/packages/Microsoft.Windows.EventTracing.Processing.All - .NET TraceProcessing canonical NuGet path. ↩
- Event Tracing. Microsoft Learn. https://learn.microsoft.com/en-us/windows/win32/etw/event-tracing-portal - ETW portal page; .NET TraceProcessing NuGet pointer. ↩
- SilkETW & SilkService - Mandiant. GitHub. https://github.com/mandiant/SilkETW - SilkETW codebase; .NET TraceEvent NuGet dependency. ↩
- Windows 10 ETW Events. GitHub. https://github.com/jdu2600/Windows10EtwEvents - Cross-build manifest + MOF provider inventory; community event ID drift tracker. ↩
- ETW Providers Documentation. GitHub. https://github.com/repnz/etw-providers-docs - Byte-stable git-versioned manifest archive for Win10/Win7; reverse-engineered Win32k provider docs. ↩
- 4624(S): An account was successfully logged on. Microsoft Learn. https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4624 - Microsoft-Windows-Security-Auditing provider GUID and Event 4624/4688 audit-policy schema. ↩
- Antimalware Scan Interface Portal. Microsoft Learn. https://learn.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-portal - Canonical Microsoft primary for the AMSI in-box integrator list (UAC, PowerShell, Windows Script Host, JavaScript/VBScript, Office VBA macros); anchors the Microsoft-Antimalware-Scan-Interface ETW provider GUID context. ↩
- about_Logging_Windows. Microsoft Learn. https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_logging_windows - Canonical Microsoft primary for PowerShell event-log writing on Windows: documents script-block logging (EventId 4104), module logging, the PowerShellCore/Operational channel, and the PowerShell ETW provider GUID. ↩
- Sysmon - Sysinternals. Microsoft Learn. https://learn.microsoft.com/en-us/sysinternals/downloads/sysmon - Sysmon authorship; service runs as protected process; Sysmon/Operational evtx channel. ↩
- Doubling Down: Detecting In-Memory Threats with Kernel ETW Call Stacks. Elastic Security Labs. https://www.elastic.co/security-labs/doubling-down-etw-callstacks - Microsoft endorses TiETW as the EDR memory-protection substrate; named Win32/Nt syscalls. ↩
- (2023). ETW internals for security research and forensics. https://blog.trailofbits.com/2023/11/22/etw-internals-for-security-research-and-forensics/ - _ETW_REALTIME_CONSUMER structure walk; live debugger evidence of CSFalconService EtwConsumer handles. ↩
- Protecting Anti-Malware Services. Microsoft Learn. https://learn.microsoft.com/en-us/windows/win32/services/protecting-anti-malware-services- - Antimalware-PPL since Windows 8.1; embedded-cert resource for permitted user-mode binaries. ↩
- Early Launch Antimalware. Microsoft Learn. https://learn.microsoft.com/en-us/windows-hardware/drivers/install/early-launch-antimalware - ELAM driver class and initialization order; runs as PPL. ↩
- PsSetCreateProcessNotifyRoutineEx. Microsoft Learn. https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreateprocessnotifyroutineex - Synchronous PASSIVE_LEVEL kernel notify routine; CreationStatus deny field. ↩
- PsSetCreateThreadNotifyRoutine. Microsoft Learn. https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreatethreadnotifyroutine - Per-thread create/delete callback registration API. ↩
- (2024). Helping our customers through the CrowdStrike outage. Microsoft Blog. https://blogs.microsoft.com/blog/2024/07/20/helping-our-customers-through-the-crowdstrike-outage/ - Microsoft primary that pins the 8.5 million Windows devices figure verbatim ("We currently estimate that CrowdStrike's update affected 8.5 million Windows devices, or less than one percent of all Windows machines"). Anchors the §10 Sidenote outage figure. ↩
- Palantir ExploitGuard. GitHub. https://github.com/palantir/exploitguard - Confirms Matt Graeber as researcher behind Palantir CIRT ETW work. ↩
- ETW Patching in Rust. fluxsec.red. https://fluxsec.red/etw-patching-rust - Modern Rust port of the 0xC3 RET patch; NtTraceEvent as the canonical syscall stub. ↩
- (2021). Design issues of modern EDRs: bypassing ETW-based solutions. Binarly. https://www.binarly.io/posts/Design_issues_of_modern_EDRs_bypassing_ETW-based_solutions/index.html - Black Hat Europe 2021 talk; full ETW kernel/user attack surface taxonomy. ↩
- Microsoft Recommended Driver Block Rules. Microsoft Learn. https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules - Vulnerable Driver Blocklist on by default since Windows 11 22H2; quarterly update cadence. ↩
- Living Off The Land Drivers. Michael Haag, Magicsword.io. https://www.loldrivers.io/ - Community catalogue of known-vulnerable signed drivers; empirical anchor for the BYOVD-vs-blocklist quarterly window. ↩
- (2026). VBS Trustlets: What Actually Runs in the Secure Kernel. paragmali.com. https://paragmali.com/blog/vbs-trustlets-what-actually-runs-in-the-secure-kernel/ - Companion piece in the same Windows security architecture series; covers the producer side of the Secure Kernel ETW (EtwSi*) story. ↩