ETW as Cover Traffic for Kernel Drivers
Windows kernel drivers can register as Event Tracing for Windows (ETW) providers by calling a single function, EtwRegister, with an arbitrary Globally Unique Identifier (GUID) and no proof that the driver performs the work the GUID implies. The registration path enforces no manifest validation, no signature verification, and no provenance check against known provider catalogs. A driver that pairs this unchecked registration with a background thread collecting genuine process data and emitting structured events referencing that data produces telemetry output that standard forensic tools cannot distinguish from a shipping Microsoft profiler. The technique flips the traditional evasion model: instead of minimizing observable behavior, the driver manufactures a plausible behavioral footprint that survives basic and intermediate inspection.
This article walks through the registration mechanism, the real sampling loop that supplies ground truth data, the event emission pipeline that references it, the coordination model that keeps timestamps consistent, and the output an administrator actually sees. It then examines where the cover breaks down under deeper analysis and what operational constraints bound the approach.
Background
ETW is the unified kernel level tracing infrastructure that underpins nearly every observability tool on Windows. Performance Monitor, Process Monitor, Windows Performance Analyzer (WPA), and Endpoint Detection and Response (EDR) agents all consume ETW event streams. The architecture consists of three distinct components: providers (which emit events), controllers (which manage trace sessions and enable providers), and consumers (which read event data). Controllers create and manage trace sessions that aggregate events from enabled providers. Over a thousand providers ship with a default Windows installation, covering everything from disk I/O to network stack internals to graphics compositor scheduling.
The registration surface is permissive by design. Microsoft built ETW to be extensible. Any kernel driver or user mode component can become a provider. This openness is what makes the infrastructure useful for third party tooling, but it also means the trust boundary around provider identity does not exist. Nothing in the registration path distinguishes a driver that genuinely profiles thread contexts from one that claims to.
ETW Provider Registration Mechanics
Registering an ETW provider from a kernel driver requires one call to EtwRegister. The function accepts a pointer to a GUID, optional callback parameters, and returns a registration handle. After this call succeeds, the driver is a provider. Trace sessions can subscribe to its GUID, and events the driver emits flow through the standard ETW pipeline.
static REGHANDLE g_etwHandle = 0;
// this name shows up in: logman query providers, perfmon, event viewer
// pick something that belongs in the list
static const GUID PROVIDER_GUID = {
0xE8109B99, 0x0923, 0x4C5A,
{ 0x8A, 0x21, 0x67, 0x72, 0xB0, 0xAF, 0x44, 0x3D }
};
NTSTATUS initEtwProvider() {
return EtwRegister(&PROVIDER_GUID, NULL, NULL, &g_etwHandle);
}
For the provider to appear in logman query providers and the Event Viewer channel list, the driver must also install a manifest through wevtutil im or write the corresponding registry keys under HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WINEVT\Publishers. Without an installed manifest, the provider remains invisible to enumeration tools and only surfaces when an active trace session captures its events.
The GUID itself is arbitrary. Copying a real Microsoft profiler GUID makes the cover more convincing at the cost of collision risk if both providers load simultaneously. A custom GUID paired with a plausible name avoids that hazard. In practice, investigators do not cross reference GUIDs against a known good catalog during live triage. The sheer volume of registered providers on a production system makes manual verification impractical.
Real Sampling as Ground Truth
The distinction between a convincing cover and an obvious decoy comes down to one question: does the driver actually collect the data it claims to collect? A provider that emits profiling events without ever attaching to a process falls apart the moment anyone inspects the event payloads against system state. The driver must run a genuine sampling loop that touches real processes and records real thread contexts.
static const WCHAR* g_sampleTargets[] = {
L"svchost.exe",
L"csrss.exe",
L"dwm.exe",
L"explorer.exe",
L"wmiprvse.exe",
L"services.exe",
L"lsass.exe"
};
void profilerThread(PVOID context) {
UNREFERENCED_PARAMETER(context);
while (g_profilerActive) {
// pick a target from the rotation
ULONG idx = (g_sampleIndex++) % ARRAYSIZE(g_sampleTargets);
PEPROCESS process = NULL;
if (NT_SUCCESS(findProcessByName(g_sampleTargets[idx], &process))) {
KAPC_STATE apcState;
KeStackAttachProcess(process, &apcState);
// sample thread contexts - real data, not fake
enumerateThreads(process, collectThreadSample);
KeUnstackDetachProcess(&apcState);
// record what was sampled before dropping the reference
g_lastSampledPid = PsGetProcessId(process);
g_sampleCount++;
ObDereferenceObject(process);
}
// randomize interval to avoid fingerprinting
LARGE_INTEGER delay;
delay.QuadPart = -((LONGLONG)(50 + (RtlRandomEx(&g_seed) % 450)) * 10000);
KeDelayExecutionThread(KernelMode, FALSE, &delay);
}
PsTerminateSystemThread(STATUS_SUCCESS);
}
The target list is composed of processes that any legitimate profiler would sample: core system services, the Desktop Window Manager (DWM) compositor, and the shell. Randomizing the delay between 50 and 500 milliseconds prevents the constant interval signature that automated behavioral analysis flags. If a forensic examiner dumps the driver’s data section, the contents are authentic profiling samples rather than static placeholder values.
A dedicated system thread created via PsCreateSystemThread hosts the loop. Work items are not suitable here. Microsoft’s documentation explicitly prohibits long running blocking loops inside work item callbacks because they starve the system worker thread pool. A standalone thread with its own sleep cycle is the correct pattern for persistent background activity in a kernel driver.
Structured Event Emission
ETW events must carry the right metadata to pass inspection. The event descriptor defines the severity level, task identifier, and keyword mask that trace consumers use for filtering and categorization. A profiling provider emits informational level events tagged with a sampling keyword:
static const EVENT_DESCRIPTOR SAMPLE_EVENT = {
.Id = 1000,
.Version = 1,
.Channel = 0,
.Level = TRACE_LEVEL_INFORMATION,
.Opcode = 0,
.Task = 1,
.Keyword = 0x1 // PERF_SAMPLING
};
void emitSampleEvent() {
if (!g_etwHandle)
return;
ULONG pid = HandleToUlong(g_lastSampledPid);
ULONG count = g_sampleCount;
LARGE_INTEGER timestamp;
KeQuerySystemTimePrecise(×tamp);
EVENT_DATA_DESCRIPTOR eventData[3];
EventDataDescCreate(&eventData[0], &pid, sizeof(ULONG));
EventDataDescCreate(&eventData[1], &count, sizeof(ULONG));
EventDataDescCreate(&eventData[2], ×tamp, sizeof(LARGE_INTEGER));
EtwWrite(g_etwHandle, &SAMPLE_EVENT, NULL, 3, eventData);
}
Each event payload references a Process Identifier (PID) that the sampling thread actually touched, a monotonically increasing sample count, and a precise timestamp. When an analyst enables the provider in a trace session, every field in the captured output corresponds to verifiable system state. The events are not fabricated. They describe real activity that genuinely occurred.
Temporal Alignment Between Collection and Emission
Sampling and event emission operate on separate schedules, but the data they reference must be temporally consistent. An event that claims to have sampled svchost.exe at time T only holds up if the sampling thread actually attached to that process near time T. The shared state between the two components is deliberately minimal:
struct profiler_context {
ULONG_PTR lastSampledPid; // use InterlockedExchangePointer for cross-CPU visibility
ULONG sampleCount; // use InterlockedIncrement in production
BOOLEAN active; // use InterlockedExchange8 or KeEvent for shutdown
};
The event emitter reads from this shared context on a schedule offset from the sampling thread by 20 to 80 milliseconds. This lag is intentional and realistic. Production profilers decouple data collection from telemetry emission because writing events inline with sampling adds latency to the measurement itself. Events that fire at the exact same instant as the corresponding sample would actually appear more anomalous than a small propagation delay.
What an Administrator Sees
An administrator who enables a trace session for this provider encounters output that blends seamlessly into the noise of a production Windows system. With an installed manifest, the provider name appears in logman query providers alongside hundreds of built in Windows providers. The trace output is unremarkable:
Provider Name: WmiPerfHost
Events/sec: ~3-8 (consistent)
Level: Informational
Keywords: PERF_SAMPLING
Events in the captured trace look like routine profiling telemetry: PIDs, sample counts, timestamps. In WPA, the activity registers as periodic CPU micro spikes consistent with a background thread cycling through process attachments and thread context reads. The pattern is indistinguishable from dozens of other providers already active on the system.
Without an installed manifest, events emitted on channel 0 do not route to Event Viewer by default. The provider still appears in active trace sessions, but passive discovery through the Event Viewer user interface does not surface it. Installing the manifest trades discoverability for completeness: the provider shows up where an analyst would expect a legitimate profiler to show up.
The cover holds through standard investigation workflows. Moving beyond it requires disassembly of the driver binary itself, a significant escalation in effort that most triage processes do not reach unless independent evidence already points to the driver as suspicious.
Extending the Cover Surface
The baseline profiling simulation can layer additional behaviors that reinforce the cover identity across multiple inspection vectors.
Diverse event types. Real profilers do not emit a single event class. They produce context switch notifications, stack walk captures, and module load records alongside periodic samples. Adding these event types increases the apparent maturity of the provider and broadens the telemetry footprint an analyst must evaluate.
Performance counters. Registering custom counters through PcwRegister places the driver in Performance Monitor’s counter catalog. Counters named “Samples/sec” and “Context Switches Observed” add a second observable surface that passes independent inspection through a completely separate tool.
Windows Management Instrumentation integration. WMI providers are enumerable through PowerShell and system management consoles. Registering a Windows Management Instrumentation (WMI) class that exposes profiling statistics means the driver appears in yet another legitimate discovery path. An analyst querying Get-WmiObject finds a well formed profiling interface backed by real data.
Each additional surface multiplies the effort an investigator must spend to determine the driver is not what it claims. The trade off is implementation complexity. More code means more imports, more behavior to implement correctly, and more binary surface area available to static analysis.
Where the Cover Breaks
Several analysis techniques penetrate the disguise.
GUID provenance checks. Microsoft documents its own profiler GUIDs, and community databases catalog third party provider identifiers. A GUID that appears in no known catalog is an anomaly. Automated tooling can perform this lookup at scale. Using a real Microsoft GUID avoids the catalog gap but introduces collision risk.
Event schema comparison. Legitimate Windows profiler events follow specific field layouts defined in their published manifests. An analyst who pulls the manifest for a known profiler and compares its event schema field by field against the impersonator’s output notices structural differences. The events look correct at a surface level. They do not survive direct comparison against the specification.
Cross source behavioral correlation. If the driver also services Input/Output Control (IOCTL) requests unrelated to profiling, correlating IOCTL timing with ETW event timing reveals that the telemetry is decoupled from the driver’s actual purpose. The events describe sampling activity. The IOCTLs describe something else entirely. Any analyst examining both data sources simultaneously sees the contradiction.
Binary static analysis. The driver image contains code for both the cover activity and the real functionality. Disassembly exposes imports, code paths, and data structures that have no relationship to profiling. No amount of runtime telemetry simulation changes what the binary contains on disk. This remains the hardest gap to close because the evidence is structural rather than behavioral.
Provider lifecycle timing. A profiling provider that registers only after a specific application launches and deregisters when that application exits does not match the lifecycle of legitimate monitoring tools. Persistent installation that follows normal driver load patterns is necessary for the temporal profile to hold.
Operational Boundaries
This technique operates strictly in the post load domain. It does not assist with loading an unsigned driver, bypassing Authenticode verification, or surviving Hypervisor-protected Code Integrity (HVCI) enforcement. The driver must already be running.
Execution quality matters more than execution breadth. A partially implemented profiler with static event payloads or missing sampling infrastructure is worse than no cover at all. Inconsistencies between claimed behavior and observable behavior create investigative hooks that pure stealth never would have introduced. If the goal is manufactured legitimacy, the simulation must survive the level of scrutiny the target environment actually applies.
Real profilers are documented. Many are open source. Studying their ETW manifests, event field layouts, sampling cadences, and lifecycle patterns before building a simulation is the difference between a cover story and a tell.
Conclusion
ETW cover traffic inverts the evasion paradigm from minimizing observability to manufacturing plausible observability. The inversion works because EtwRegister imposes no authenticity requirement on the GUID, metadata, or behavioral claims a provider makes at registration time. For a driver that is already loaded, this authentication gap transforms the operating system’s own telemetry infrastructure into camouflage. An investigator must actively disbelieve the provider’s identity rather than passively detect its presence, and that shift from detection to disconfirmation is the core advantage the technique provides.