The Microsoft-Windows-Threat-Intelligence (ETW-TI) provider is the single most significant ETW provider from a defensive security perspective. Unlike user-mode ETW providers, ETW-TI is a kernel-mode provider registered directly by ntoskrnl.exe. Events from this provider cannot be silenced by patching user-mode ntdll functions, making it the definitive backstop against advanced in-memory attack techniques such as process injection, thread hijacking, and credential dumping.
| Related posts in this blog: Understanding and Attacking EDRs | Hunting the Watchers: Detecting EDR Hooks | Breaking ETW and EDR | EDR Bypass Roadmap | Hell’s Gate, Heaven’s Gate & Tartarus Gate |
- What is ETW-TI?
- Architecture: Why Kernel-Mode Matters
- Provider Internals: Registration, Event Flow, and Kernel Structures
- The Kernel Sensors: EtwTiLog Functions
- Querying ETW-TI Locally
- How EDRs Consume ETW-TI
- Purple Team: The Bypass Landscape
- Detection Engineering Recommendations
- Tools and Research Resources
- References
What is ETW-TI?
| Property | Value |
|---|---|
| Provider Name | Microsoft-Windows-Threat-Intelligence |
| GUID | {F4E1897C-BB5D-5668-F1D8-040F4D8DD344} |
| Mode | Kernel-mode (registered by ntoskrnl.exe) |
| Introduced | Windows 10 RS2/1703 |
| Access Requirement | PPL (Protected Process Light) Anti-Malware or higher |
ETW-TI was introduced in Windows 10 RS2/1703 to provide critical visibility into Memory Manager (Mm*) and Asynchronous Procedure Call (APC) operations. Since then, it has been continuously expanded to cover additional security-relevant syscalls. Microsoft’s goal was to provide security vendors with near real-time kernel telemetry about the most security-critical operations happening on a system.
Unlike regular ETW providers that any process can subscribe to, the ETW-TI provider is only accessible to Early Launch Anti-Malware (ELAM) signed drivers and processes running as SERVICE_LAUNCH_PROTECTED_ANTIMALWARE_LIGHT / PS_PROTECTED_ANTIMALWARE_LIGHT. This is part of the so-called Secure ETW Channel.
Architecture: Why Kernel-Mode Matters
The fundamental architectural difference between ETW-TI and user-mode ETW providers is where the instrumentation executes:
User-Mode ETW: Application -> ntdll!EtwEventWrite -> Event
(can be patched in user-mode)
ETW-TI (Kernel): syscall instruction -> ntoskrnl.exe -> EtwTiLog* -> Event
(fires AFTER transition to ring 0, untouchable from user-mode)
or as a picture:

The ETW-TI callback executes within the kernel after the syscall instruction completes. By the time the telemetry fires, the user-mode code has already transitioned into kernel-mode. This means:
- No user-mode hook can intercept the event
- No user-mode patching (ntdll, AMSI, etc.) affects ETW-TI
- The kernel sees the operation regardless of how the syscall was invoked
PPL Protection Requirement
The provider is only accessible to sessions running as PPL (Protected Process Light) or PP (Protected Process). Standard user-mode processes – including non-PPL EDR agents – cannot subscribe. This is the architectural reason why a kernel-mode driver is essential for EDR products: user-mode-only EDR has a structural blind spot for injection detection.
The kernel will only deliver ETW-TI events to services/processes running as PS_PROTECTED_ANTIMALWARE_LIGHT that have been signed by the Early Launch EKU certificate, which was also used to sign the corresponding ELAM driver installed via kernel32!InstallElamCertificateInfo.
Why Direct/Indirect Syscalls Cannot Bypass ETW-TI
This is a critical insight for both red and blue teamers:
ETW-TI events fire independently of how the syscall was invoked – whether via the hooked
ntdllstub, a direct syscall, or an indirect syscall. The kernel sees the operation and fires the TI event.
This is fundamentally different from user-mode hooking (inline hooks on ntdll), which can be trivially bypassed by calling syscall stubs directly. ETW-TI operates entirely in kernel-mode, making it immune to all user-land evasion techniques. To understand and enumerate which functions your EDR actively hooks at the user-mode level, see the companion article Hunting the Watchers: Detecting and Enumerating EDR User-Mode Hooks – it compares loaded DLL memory against disk baselines to produce a complete hook map.

User-Mode Hook: ntdll!NtWriteVirtualMemory (hooked) -> EDR callback -> syscall
Direct Syscall: mov r10, rcx; mov eax, SSN; syscall (bypasses hook)
Indirect Syscall: jmp ntdll!NtWriteVirtualMemory+0x12 (bypasses hook)
ETW-TI: ALL THREE -> kernel -> EtwTiLogReadWriteVm -> Event logged
Provider Internals: Registration, Event Flow, and Kernel Structures
This section dives into the low-level mechanisms that make ETW-TI work inside ntoskrnl.exe. Understanding these internals is essential for reverse engineering detection capabilities, building custom consumers, and understanding what attackers must subvert to blind this provider.
Provider Registration in ntoskrnl.exe
Like any ETW provider, the TI provider must call EtwRegister before it can emit events. Because ETW-TI is a kernel-mode provider, this registration happens inside ntoskrnl.exe during system initialization.
The registration call is found within the EtwInitialize function, which registers many kernel ETW providers during boot:
NTSTATUS EtwRegister(
[in] LPCGUID ProviderId, // Provider GUID
[in, optional] PETWENABLECALLBACK EnableCallback, // Called when consumer enables/disables
[in, optional] PVOID CallbackContext, // Context for callback
[out] PREGHANDLE RegHandle // Output: registration handle
);
For the TI provider, the parameters are:
| Parameter | Value |
|---|---|
ProviderId |
ThreatIntProviderGuid = {f4e1897c-bb5d-5668-f1d8-040f4d8dd344} |
RegHandle (output) |
Stored in the global EtwThreatIntProvRegHandle |
In IDA/Ghidra, following the cross-references from EtwRegister inside ntoskrnl.exe leads to EtwInitialize, where you can observe the TI provider GUID being passed as the first argument.
The EtwThreatIntProvRegHandle Global
The registration produces a handle stored in the kernel global variable EtwThreatIntProvRegHandle. This handle is the linchpin of the entire ETW-TI system:
nt!EtwThreatIntProvRegHandle -> REGHANDLE (opaque kernel handle)
Every EtwTiLog* function references this handle when calling EtwWrite to emit events. By cross-referencing EtwThreatIntProvRegHandle in a disassembler, you can discover all functions that write to the TI provider:
0: kd> x nt!EtwThreatIntProvRegHandle
fffff805`27c4a1b0 nt!EtwThreatIntProvRegHandle = <...>
Cross-referencing this symbol reveals all callers – which is precisely the list of EtwTiLog* sensor functions. This is the reverse-engineering technique used by researchers (including Jonathan Johnson) to map out the complete set of instrumented operations.
Attack relevance: One known kernel-level bypass involves zeroing EtwThreatIntProvRegHandle via a vulnerable driver. If this handle is null, subsequent calls to EtwWrite with it will silently fail, effectively disabling all TI events. However, PatchGuard periodically validates kernel data integrity and may detect this modification.
Event Writing: From Syscall to EtwWrite
When a security-relevant syscall executes, the kernel calls the corresponding EtwTiLog* function, which ultimately invokes EtwWrite:
NTSTATUS EtwWrite(
[in] REGHANDLE RegHandle, // EtwThreatIntProvRegHandle
[in] PCEVENT_DESCRIPTOR EventDescriptor, // Event metadata (ID, level, keyword)
[in, optional] LPCGUID ActivityId, // Correlation GUID
[in] ULONG UserDataCount, // Number of data items
[in, optional] PEVENT_DATA_DESCRIPTOR UserData // Event payload
);
The EVENT_DESCRIPTOR structure defines the event’s identity:
typedef struct _EVENT_DESCRIPTOR {
USHORT Id; // Event ID (unique per event type)
UCHAR Version; // Schema version
UCHAR Channel; // Log channel
UCHAR Level; // Severity (Informational, Warning, etc.)
UCHAR Opcode; // Operation type
USHORT Task; // Task category
ULONGLONG Keyword; // Filtering bitmask
} EVENT_DESCRIPTOR, *PEVENT_DESCRIPTOR;
Each EtwTiLog* function constructs the appropriate EVENT_DESCRIPTOR and populates UserData with contextual information (PIDs, addresses, sizes, protection flags, etc.) before calling EtwWrite.
Event Routing: Local vs Remote, Read vs Write
A detailed examination of EtwTiLogReadWriteVm reveals how the kernel decides which specific event to emit. The logic follows this decision tree:

The corresponding event descriptors (found as static data in ntoskrnl.exe):
| Symbol | Event ID (hex) | Keyword Mask | Trigger |
|---|---|---|---|
THREATINT_READVM_LOCAL |
0x0d (13) |
0x8000000000004000 |
Same-process read |
THREATINT_READVM_REMOTE |
0x0e (14) |
0x8000000000008000 |
Cross-process read |
THREATINT_WRITEVM_LOCAL |
0x0f (15) |
0x8000000000010000 |
Same-process write |
THREATINT_WRITEVM_REMOTE |
0x10 (16) |
0x8000000000020000 |
Cross-process write |
The complete call chain for a WriteProcessMemory operation:
User-Mode:
WriteProcessMemory() [kernel32.dll]
└─ NtWriteVirtualMemory() [ntdll.dll]
└─ syscall instruction -> Ring 0 transition
Kernel-Mode (ntoskrnl.exe):
NtWriteVirtualMemory(ProcessHandle, BaseAddress, Buffer, Size, ...)
└─ MiReadWriteVirtualMemory(SourceProcess, TargetProcess, ..., 0x20)
├─ [Performs the actual memory write operation]
└─ EtwTiLogReadWriteVm(SourceProcess, TargetProcess, 0x20, ...)
├─ Determines: REMOTE (different process) + WRITE (0x20)
├─ Selects: THREATINT_WRITEVM_REMOTE descriptor
└─ EtwWrite(EtwThreatIntProvRegHandle, &THREATINT_WRITEVM_REMOTE, ...)
└─ Event delivered to PPL consumer sessions
This architecture means the event fires after the write completes but within the same syscall context – there is no async delay at the kernel instrumentation level.
The Secure ETW Channel and PPL Enforcement
ETW-TI uses what Microsoft calls the Secure ETW Channel. This is not a separate transport mechanism but rather an access control enforcement layer:
-
Provider Registration: When
EtwRegisteris called for the TI provider GUID, the kernel marks this registration as “secure” in the internalETW_REG_ENTRYstructure. - Session Subscription: When a consumer calls
StartTrace/EnableTraceEx2to subscribe to the TI provider, the kernel checks the caller’s protection level:if (Process->Protection.Type < PsProtectedTypeProtectedLight || Process->Protection.Signer < PsProtectedSignerAntimalware) { return STATUS_ACCESS_DENIED; } - ELAM Requirement: The PPL-AM protection level requires:
- A kernel-mode ELAM driver signed with Microsoft’s Early Launch EKU
- The user-mode service binary signed with the same certificate chain
- Installation via
InstallElamCertificateInfoduring ELAM driver load
- Event Delivery: Events are only routed to trace sessions whose owning process meets the PPL-AM threshold. Non-qualifying sessions never receive TI events, even if they request the provider GUID.
The verification check within NtTraceControl (which backs EnableTraceEx2):
// Simplified kernel logic for ETW-TI subscription validation
if (IsSecureProvider(ProviderGuid)) {
PEPROCESS Consumer = PsGetCurrentProcess();
PS_PROTECTION protection = Consumer->Protection;
if (protection.Type < PsProtectedTypeProtectedLight)
return STATUS_ACCESS_DENIED;
if (protection.Signer < PsProtectedSignerAntimalware)
return STATUS_ACCESS_DENIED;
}
EPROCESS Protection Levels
The EPROCESS kernel structure contains a PS_PROTECTION field that determines the process’s protection level:
typedef struct _PS_PROTECTION {
union {
UCHAR Level;
struct {
UCHAR Type : 3; // PS_PROTECTED_TYPE
UCHAR Audit : 1; // Reserved
UCHAR Signer : 4; // PS_PROTECTED_SIGNER
};
};
} PS_PROTECTION, *PPS_PROTECTION;
Protection Types (relevant hierarchy):
| Value | Type | Description |
|---|---|---|
| 0 | PsProtectedTypeNone |
No protection (normal process) |
| 1 | PsProtectedTypeProtectedLight |
PPL – Protected Process Light |
| 2 | PsProtectedTypeProtected |
PP – Full Protected Process |
Signer Levels (relevant hierarchy, highest to lowest):
| Value | Signer | Who Uses It |
|---|---|---|
| 6 | PsProtectedSignerWinTcb |
Core Windows components (csrss, smss) |
| 5 | PsProtectedSignerWindows |
Windows services |
| 4 | PsProtectedSignerLsa |
LSA (credential protection) |
| 3 | PsProtectedSignerAntimalware |
EDR/AV (minimum for ETW-TI) |
| 2 | PsProtectedSignerCodeGen |
Code generation services |
| 1 | PsProtectedSignerAuthenticode |
Standard Authenticode |
| 0 | PsProtectedSignerNone |
No signer |
For ETW-TI access, a process needs at minimum: Type = PsProtectedTypeProtectedLight (1) AND Signer = PsProtectedSignerAntimalware (3), giving a combined Level byte of 0x31.
This is what tools like KDU exploit: they use a vulnerable driver to directly modify the EPROCESS->Protection field in kernel memory, elevating a normal process to PPL-AM status.
Per-Process ETW-TI Enablement Flags
ETW-TI events are not globally enabled for all processes – they are controlled on a per-process basis via flags in the EPROCESS structure. These flags determine whether TI events fire when the process is involved in a monitored operation:
// Relevant EPROCESS fields (undocumented, offsets vary by build)
struct _EPROCESS {
// ...
union {
ULONG MitigationFlags2;
struct {
ULONG EnableReadVmLogging : 1;
ULONG EnableWriteVmLogging : 1;
ULONG EnableProcessSuspendResumeLogging : 1;
ULONG EnableThreadSuspendResumeLogging : 1;
ULONG EnableLocalExecProtectVmLogging : 1; // Win11+
ULONG EnableRemoteExecProtectVmLogging : 1; // Win11+
// ...
};
};
// ...
};
These flags can be queried and set via NtSetInformationProcess with:
ProcessEnableReadWriteVmLogging(class0x57)ProcessEnableLogging(class0x60)
Under normal circumstances, modifying these flags requires PPL-AM privileges. The ETW-ByeBye bug (discussed later) exploits the absence of this PPL check on vulnerable Windows versions.
The Kernel Sensors: EtwTiLog Functions
The Windows kernel sensors are categorized into two groups:
- Threat Intelligence (Ti):
EtwTiLog*prefix – high-fidelity security sensors - Security Mitigations (Tim):
EtwTimLog*prefix – mitigation-related logging
These are the kernel-internal functions that hook directly into critical operations:
| Sensor Function | Trigger | What It Detects |
|---|---|---|
EtwTiLogReadWriteVm |
MiReadWriteVirtualMemory |
Cross-process memory read/write (LSASS dumping, injection) |
EtwTiLogSetContextThread |
PspSetContextThreadInternal |
Thread hijacking / context manipulation |
EtwTiLogAllocExecVm |
RWX memory allocations | Executable memory allocation (shellcode staging) |
EtwTiLogProtectExecVm |
Memory protection changes | VirtualProtect to PAGE_EXECUTE_* |
EtwTiLogMapExecView |
Section mapping with execute | Executable section mappings (module stomping) |
EtwTiLogInsertQueueUserApc |
APC queueing | APC-based injection techniques |
EtwTiLogSuspendResumeProcess |
Process suspend/resume | Process hollowing preparation |
EtwTiLogSuspendResumeThread |
Thread suspend/resume | Thread manipulation |
EtwTiLogDriverObjectLoad |
Driver loading | Malicious/vulnerable driver loading |
EtwTiLogDriverObjectUnLoad |
Driver unloading | Driver cleanup after exploitation |
EtwTiLogDeviceObjectLoadUnload |
Device object operations | Device driver manipulation |
Complete Sensor List via WinDbg
In a kernel debugging session, list all ETW-TI symbols:
0: kd> x nt!EtwTiLog*
fffff805`27b75280 nt!EtwTiLogDriverObjectLoad (void)
fffff805`27a5f94c nt!EtwTiLogDeviceObjectLoadUnload (void)
fffff805`27759390 nt!EtwTiLogInsertQueueUserApc (void)
fffff805`27a79ecc nt!EtwTiLogProtectExecVm (void)
fffff805`27b6d22c nt!EtwTiLogDriverObjectUnLoad (void)
fffff805`27aaac4c nt!EtwTiLogSetContextThread (void)
fffff805`27d3f76c nt!EtwTiLogSuspendResumeProcess (void)
fffff805`27a79ce0 nt!EtwTiLogAllocExecVm (void)
fffff805`27a79b30 nt!EtwTiLogReadWriteVm (void)
fffff805`27b22544 nt!EtwTiLogMapExecView (void)
fffff805`27d3f8d4 nt!EtwTiLogSuspendResumeThread (void)
ETW-TI Monitored Events
Each event captures rich contextual data:
| Event ID | Syscall | Detection Use Case |
|---|---|---|
| 1 | NtAllocateVirtualMemory |
Memory allocation with PAGE_EXECUTE_* |
| 2 | NtProtectVirtualMemory |
Changing memory to executable |
| 3 | NtMapViewOfSection |
Section mapping (especially SEC_IMAGE) |
| 4 | NtQueueApcThread |
APC-based injection |
| 5 | NtSetContextThread |
Thread context modification (hijacking) |
| 6 | NtCreateThreadEx |
Remote thread creation |
| 7 | NtOpenProcess |
Process handle with high privileges |
| 8 | NtReadVirtualMemory |
Cross-process reads (credential dumping) |
| 9 | NtWriteVirtualMemory |
Cross-process writes (code injection) |
| 10 | Image loads | DLL injection via LoadLibrary |
For example, when NtAllocateVirtualMemory is called with PAGE_EXECUTE_READWRITE, ETW-TI logs:
- Process ID making the call
- Target process (if cross-process)
- Base address allocated
- Region size
- Protection flags (RWX is highly suspicious)
- Call stack (user-mode portion)
- Timestamp
Querying ETW-TI Locally
Query the full list of supported events from an elevated command prompt:
logman.exe query providers Microsoft-Windows-Threat-Intelligence
Example output:
Provider GUID
-------------------------------------------------------------------------------
Microsoft-Windows-Threat-Intelligence {F4E1897C-BB5D-5668-F1D8-040F4D8DD344}
Value Keyword Description
-------------------------------------------------------------------------------
0x0000000000000001 KERNEL_THREATINT_KEYWORD_ALLOCVM_LOCAL
0x0000000000000002 KERNEL_THREATINT_KEYWORD_ALLOCVM_LOCAL_KERNEL_CALLER
0x0000000000000004 KERNEL_THREATINT_KEYWORD_ALLOCVM_REMOTE
0x0000000000000008 KERNEL_THREATINT_KEYWORD_PROTECTVM_LOCAL
0x0000000000000010 KERNEL_THREATINT_KEYWORD_PROTECTVM_LOCAL_KERNEL_CALLER
0x0000000000000020 KERNEL_THREATINT_KEYWORD_PROTECTVM_REMOTE
0x0000000000000040 KERNEL_THREATINT_KEYWORD_MAPVIEW_LOCAL
0x0000000000000080 KERNEL_THREATINT_KEYWORD_MAPVIEW_LOCAL_KERNEL_CALLER
0x0000000000000100 KERNEL_THREATINT_KEYWORD_MAPVIEW_REMOTE
0x0000000000000200 KERNEL_THREATINT_KEYWORD_QUEUEUSERAPC_REMOTE
0x0000000000000400 KERNEL_THREATINT_KEYWORD_SETTHREADCONTEXT_REMOTE
0x0000000000000800 KERNEL_THREATINT_KEYWORD_READVM_LOCAL
0x0000000000001000 KERNEL_THREATINT_KEYWORD_READVM_REMOTE
0x0000000000002000 KERNEL_THREATINT_KEYWORD_WRITEVM_LOCAL
0x0000000000004000 KERNEL_THREATINT_KEYWORD_WRITEVM_REMOTE
0x0000000000008000 KERNEL_THREATINT_KEYWORD_SUSPEND_THREAD
0x0000000000010000 KERNEL_THREATINT_KEYWORD_RESUME_THREAD
0x0000000000020000 KERNEL_THREATINT_KEYWORD_SUSPEND_PROCESS
0x0000000000040000 KERNEL_THREATINT_KEYWORD_RESUME_PROCESS
...
How EDRs Consume ETW-TI
While synchronous blocking (inline callbacks for these syscalls) is not yet possible, ETW-TI represents the most reliable kernel-side detection mechanism accessible to security vendors. EDRs consume ETW-TI events through their kernel-mode driver component, which runs as PPL-AntiMalware.
Elastic Security: Call Stack-Based Detections
Elastic Security (since version 8.8+) combines ETW-TI events with kernel call stack analysis. Their implementation:
- Subscribes to ETW-TI via their PPL kernel driver
- Fingerprints each event and deduplicates (unique events only emitted)
- Processes events on-host for behavior detection rules
- Optionally streams to SIEM for SOC correlation
Currently monitored APIs include:
VirtualAlloc/VirtualAllocExVirtualProtect/VirtualProtectExMapViewOfFile/MapViewOfFile2QueueUserAPCSetThreadContextWriteProcessMemoryReadProcessMemory(targeting lsass)
API Event Behaviors (Elastic Enrichment)
Elastic enriches API events with behavioral indicators:
| Behavior | Description |
|---|---|
cross-process |
Activity between two different processes |
native_api |
Direct call to undocumented Native API |
direct_syscall |
Syscall instruction originated outside ntdll |
proxy_call |
Proxied API call masking the true caller |
shellcode |
Suspicious non-image executable memory calling sensitive API |
image-hooked |
Call stack entry appears hooked |
image_rop |
Call stack entry not preceded by a call instruction |
unbacked_rwx |
Non-image writable+executable memory in call stack |
allocate_shellcode |
Non-image executable memory allocating more executable memory |
execute_fluctuation |
PAGE_EXECUTE protection unexpectedly changing |
write_fluctuation |
PAGE_WRITE of executable memory fluctuating |
hollow_image |
Large executable image region protection changed |
hardware_breakpoint_set |
Hardware breakpoint potentially set |
Detection Rule Examples
Direct Syscall Detection:
api where event.category == "intrusion_detection" and
process.Ext.api.behaviors == "direct_syscall" and
process.Ext.api.name : ("VirtualAlloc*", "VirtualProtect*",
"MapViewOfFile*", "WriteProcessMemory")
ETW Patching Detection:
api where process.Ext.api.name : "WriteProcessMemory*" and
process.Ext.api.summary : ("*ntdll.dll!Etw*", "*ntdll.dll!NtTrace*") and
not process.executable : ("?:\\Windows\\System32\\lsass.exe")
AMSI/WLDP Bypass Detection:
api where
(
(process.Ext.api.name : "VirtualProtect*" and
process.Ext.api.parameters.protection : "*W*") or
process.Ext.api.name : "WriteProcessMemory*"
) and
process.Ext.api.summary : ("* amsi.dll*", "* mpoav.dll*", "* wldp.dll*")
Remote Module Hooking (ThreadlessInject detection):
api where process.Ext.api.name : "WriteProcessMemory" and
process.Ext.api.behaviors == "cross-process" and
process.Ext.api.summary : ("*ntdll.dll*", "*kernelbase.dll*")
Purple Team: The Bypass Landscape
Since ETW-TI is a kernel-mode provider, any suppression requires kernel-level access – via BYOVD (Bring Your Own Vulnerable Driver), a kernel exploit, or a signed kernel driver. This makes ETW-TI the defensive backstop against injection techniques: all userland patching leaves it untouched.
User-Mode Bypasses That DO NOT Work
| Technique | Why It Fails Against ETW-TI |
|---|---|
Patching ntdll!EtwEventWrite |
Only affects user-mode ETW providers |
| Direct Syscalls | Kernel still sees the operation |
| Indirect Syscalls | Kernel still sees the operation |
| Unhooking ntdll | ETW-TI has no user-mode hooks to remove |
| AMSI patching | Completely different subsystem |
| Process hollowing from fresh ntdll | Kernel instrumentation is independent |
Kernel-Mode Bypass Vectors
These are relevant for detection engineering (knowing what to watch for):
| Vector | Method | Risk |
|---|---|---|
Nulling EtwThreatIntProvRegHandle |
Zero the kernel registration handle | PatchGuard detection -> BSOD |
Patching nt!EtwTiLogAllocExecVm |
ret at function entry |
PatchGuard / HVCI violation |
| BYOVD | Load vulnerable signed driver for kernel R/W | Driver blocklist, detection of driver load |
| Kernel exploit | 0-day or N-day for ring-0 code execution | Highly advanced, rare |
NtSetInformationProcess bug |
Disable per-process TI logging (patched Win11) | Only works on unpatched Win10 |
The NtContinue Technique (Hardware Breakpoints)
Research from Praetorian demonstrates that NtContinue can be used to set hardware breakpoints (debug registers) without triggering EtwTiLogSetContextThread. This is because NtContinue updates the thread context including debug registers without invoking the same kernel path as NtSetContextThread.
The attack flow:
- Register a Vectored Exception Handler
- Capture current thread context via
RtlCaptureContext - Set
Dr0-Dr3andDr7to hook target functions (e.g.,Sleep,AmsiScanBuffer) - Call
NtContinueto apply the context (no ETW-TI event) - When the breakpoint fires, the VEH handles the exception
This is relevant because EDRs create detections around SetThreadContext/NtSetContextThread for hardware breakpoint abuse. Using NtContinue avoids this specific detection vector.
Detection approach: Monitor for NtContinue calls with CONTEXT_DEBUG_REGISTERS flag, or detect the behavioral outcome (patchless hooks on AMSI/ETW functions).
ETW-ByeBye: The ProcessEnableLogging Bug
Research from RiskInsight (2023) revealed that on certain Windows 10 versions, calling NtSetInformationProcess with ProcessEnableLogging or ProcessEnableReadWriteVmLogging could disable ETW-TI event logging for a process without the expected PPL requirement. Only SeDebug or SeTcb privileges were needed.
Vulnerable versions:
- Win10 1709 through 22H2 (ReadVm/WriteVm operations)
- Win10 1809 through 22H2 (Process/Thread suspend/resume)
- Patched in Windows 11
// Simplified POC (works only on unpatched systems)
PROCESS_LOGGING_INFORMATION info = { 0 };
info.Flags = 0xf;
info.EnableReadVmLogging = 1;
info.EnableWriteVmLogging = 1;
info.EnableProcessSuspendResumeLogging = 1;
info.EnableThreadSuspendResumeLogging = 1;
NtSetInformationProcess((HANDLE)-1, ProcessEnableLogging,
&info, sizeof(info));
// ETW-TI events for this process are now silenced
Detection approaches:
- Hook
NtSetInformationProcessin user-mode (fragile but layered defense) - Walk
KPROCESSlist and check if ETW-TI was disabled for unexpected processes - Alert on absence of expected ETW-TI telemetry from a process
- Upgrade to Windows 11 where the bug is fixed
Detection Engineering Recommendations
For CSIRT/SOC teams building detection capabilities around ETW-TI:
High-Fidelity Detection Patterns:
| Pattern | Detects | MITRE ATT&CK |
|---|---|---|
Cross-process NtWriteVirtualMemory + NtCreateThreadEx |
Classic process injection | T1055.001 |
NtAllocateVirtualMemory(RWX) -> NtWriteVirtualMemory -> NtProtectVirtualMemory(RX) |
Staged injection | T1055 |
NtReadVirtualMemory targeting lsass.exe |
Credential dumping | T1003.001 |
NtSetContextThread on remote process |
Thread hijacking | T1055.003 |
NtQueueApcThread to remote process |
APC injection | T1055.004 |
NtMapViewOfSection with execute to remote process |
Section-based injection | T1055.012 |
| RWX allocation + immediate execution from non-image memory | Shellcode execution | T1620 |
| Driver load from unusual path | BYOVD attempt | T1068 |
Behavioral Evasion Awareness:
Sophisticated adversaries may attempt to reduce detection scores by:
- Staging operations with delays (spreading RWX alloc -> write -> protect -> execute over minutes)
- Using existing executable memory (code caves) instead of new allocations
- Using
RWallocation first, then changing toRX(noRWXflag) - Leveraging legitimate processes for early injection stages
Correlation with User-Mode Hook Coverage:
ETW-TI provides kernel-level visibility, but most EDRs also maintain user-mode inline hooks on ntdll.dll for real-time interception. The two layers are complementary: ETW-TI catches operations that bypass user-mode hooks (via direct/indirect syscalls), while hooks provide synchronous blocking capability that ETW-TI lacks. Use the EDR Hook Detection Scanner to map which Nt* functions your EDR intercepts at user-mode level, then cross-reference with the ETW-TI monitored events table above to identify any operations that lack coverage at both layers.
Splunk SPL Example (if forwarding ETW-TI via Fibratus/Sealighter):
index=etw_ti sourcetype="etw:ti"
| where CallingProcessId != TargetProcessId
| where Protection="PAGE_EXECUTE_READWRITE"
| stats count by CallingProcessName, TargetProcessName, EventType
| where count > 3
| sort - count
Tools and Research Resources
Research / Consume ETW-TI
| Tool | Description | Link |
|---|---|---|
| SealighterTI | Combines Sealighter with exploits to subscribe to ETW-TI without a signed driver (patched on current systems) | GitHub |
| Sealighter | ETW research tool – subscribes to multiple providers, filters events, outputs JSON; forwards to Splunk/ELK | GitHub |
| EtwTiViewer | Live ETW-TI event viewer with kernel driver + ImGui frontend; displays the same signals commercial EDRs consume | GitHub |
| TiEtwAgent | ETW-based process injection detection agent | GitHub |
| Fibratus | Open-source Windows runtime security sensor for real-time kernel telemetry and threat detection via ETW | fibratus.io |
| KDU | Kernel Driver Utility – uses vulnerable drivers to gain kernel access; can launch processes as PPL-AM | GitHub |
Deep-Dive Blog Articles
| Article | Author | Focus |
|---|---|---|
| Uncovering Windows Events: Threat Intelligence ETW | Jonathan Johnson | Reverse engineering EtwRegister/EtwInitialize in ntoskrnl.exe |
| Event Tracing for Windows (ETW) | Nation State Minds | Comprehensive ETW deep dive including suppression techniques |
| ETW Threat Intelligence and Hardware Breakpoints | Praetorian | NtContinue bypass, kernel debugging of EtwTiLog functions |
| Introduction into Microsoft Threat Intelligence Drivers | Meekolab | Historical context, ELAM/PPL architecture |
| Doubling Down: Detecting In-Memory Threats with Kernel ETW Call Stacks | Elastic Security Labs | Production detection rules, API event enrichment |
| Protecting Windows Protected Processes | Elastic | PPL architecture and EDR integration |
| Gaining Threat-Intelligence the dodgy way | pat_h/to/file | SealighterTI development, PPL exploit chain |
| Gaining Threat-Intelligence the REALLY dodgy way | pat_h/to/file | BYOVD approach using KDU for ETW-TI access |
| Getting started with ETW-TI Provider | HackBalak | Practical walkthrough: SealighterTI + ELK integration |
| ETW-ByeBye: Disabling ETW-TI Without PPL | Legacyy | NtSetInformationProcess bug exploitation and detection |
| The ETW Blind Spot | Valhguard | Red team tactics for blinding Windows Event Tracing |
| ETW-TI Limitations (SysWhispers4) | SysWhispers4 Docs | User-mode vs kernel-mode evasion boundaries |
| RedTeaming CheatSheet: EDR/ETW | 0xJs | Quick reference for EDR evasion techniques |
References
- Microsoft Documentation - Event Tracing for Windows (ETW)
- Elastic Security Labs - Doubling Down: Detecting In-Memory Threats with Kernel ETW Call Stacks (January 2024)
- Praetorian - ETW Threat Intelligence and Hardware Breakpoints (January 2025)
- Meekolab - Introduction into Microsoft Threat Intelligence Drivers (September 2022)
- pathtofile - SealighterTI and KDU approach
- Legacyy - ETW-ByeBye: Disabling ETW-TI Without PPL (April 2024)
- RiskInsight Wavestone - A universal EDR bypass built in Windows 10 (October 2023)
- SysWhispers4 - ETW-TI Limitations
- jdu2600 - Windows 10 ETW Events manifest