ETW-TI: Why Userland Bypasses Stop Here
Chapter 46 showed how to silence ETW by patching three bytes in ntdll. That works because those ETW writes originate in your process's memory, which you can modify. ETW-TI (Threat Intelligence) is categorically different: it's emitted by the Windows kernel itself — ntoskrnl.exe — in response to security-sensitive operations. No amount of userland patching touches it. This chapter explains what ETW-TI covers, why the architectural boundary matters, what the partial bypasses look like, and what implications this has for real implant design.
What ETW-TI Actually Is
Userland ETW (Ch46 — patchable):
─────────────────────────────────────────────────────────────────────────
Your process
│
├─ calls VirtualAllocEx()
│ └─ ntdll!EtwEventWrite() ← IN YOUR PROCESS, patchable
│ └─ NtTraceEvent() → kernel (writes to ETW buffer)
│
└─ Your patch: replace EtwEventWrite with ret → event never sent
ETW-TI (NOT patchable from userland):
─────────────────────────────────────────────────────────────────────────
Your process calls NtAllocateVirtualMemory()
│
└─ syscall → kernel (ring 0)
│
├─ NtAllocateVirtualMemory does its work
│
└─ Before returning: kernel checks if operation is monitored
│
└─ EtwTiLogAllocateVirtualMemory() — IN KERNEL MEMORY
│
└─ ETW write from kernel → ETW-TI log
│
└─ consumed by:
EDR kernel callbacks
Microsoft Defender for Endpoint (MDE)
Elastic Defend kernel driver
Why you cannot patch ETW-TI from userland:
─────────────────────────────────────────────────────────────────────────
EtwTiLog* functions are in ntoskrnl.exe.
ntoskrnl.exe is in kernel memory (ring 0).
User-mode code (ring 3) cannot write to ring 0 memory.
VirtualProtect/WriteProcessMemory only affect your process's address space.
The only way to patch ETW-TI: you need ring 0 code execution.
That means: a kernel driver (Chapter 15+), a kernel exploit, or BYOVD.
(BYOVD = Bring Your Own Vulnerable Driver — covered in Part 15)What ETW-TI Monitors
ETW-TI Event API that triggers it
─────────────────────────────────────────────────────────────────────────
ALLOCATE_VIRTUAL_MEMORY NtAllocateVirtualMemory()
PROTECT_VIRTUAL_MEMORY NtProtectVirtualMemory()
MAP_VIEW_OF_SECTION NtMapViewOfSection()
QUEUE_USER_APC NtQueueApcThread()
CREATE_THREAD NtCreateThreadEx()
SET_CONTEXT_THREAD NtSetContextThread()
OPEN_PROCESS NtOpenProcess()
OPEN_THREAD NtOpenThread()
READ_VIRTUAL_MEMORY NtReadVirtualMemory()
WRITE_VIRTUAL_MEMORY NtWriteVirtualMemory()
CREATE_PROCESS NtCreateUserProcess()
DEVICE_CONTROL NtDeviceIoControlFile()
Additional ETW-TI event categories (Windows 10 1903+):
─────────────────────────────────────────────────────────────────────────
LOAD_IMAGE Loading any PE into memory
QUEUE_USER_APC_SPECIAL Special-user APC (Win10 19H1+)
DUPLICATE_HANDLE NtDuplicateObject()
Key insight: these events include the CALL STACK:
─────────────────────────────────────────────────────────────────────────
Each ETW-TI event also records the userland call stack at the time of
the syscall. This is how ETW-TI catches:
- Direct syscall stubs not in module memory (Ch41)
- Indirect syscalls landing at mid-function offsets (Ch43)
- Injection attempts even when using direct/indirect syscalls
MDE uses ETW-TI to detect:
- WriteVirtualMemory events with destination in another process
- CreateThread events with start address in non-module memory
- AllocateVirtualMemory events with RWX protection (MEM_COMMIT + PAGE_EXECUTE_READWRITE)
- Suspicious call stack patterns (return address not in any known module)Partial Bypasses and Their Limits
/* ── ETW-TI "bypass" approaches — and why they're partial ─────────── */
/*
* Approach 1: Run in an unmonitored session
*
* ETW-TI consumers (like MDE) register callbacks for specific processes or
* session scopes. Services running in Session 0 are typically monitored.
* Interactive user sessions (Session 1+) are also monitored.
* BUT: in some lab environments, ETW-TI consumers may not be running at all.
* In production environments with MDE, they always are.
*
* This isn't a real bypass — it's hoping the consumer isn't there.
*/
/*
* Approach 2: Use shared section injection (NtCreateSection) to avoid
* WriteVirtualMemory events.
*
* Chapter 26 showed mapping injection: write payload via shared memory section,
* no NtWriteVirtualMemory call. But ETW-TI still fires for:
* - NtMapViewOfSection (remote mapping = inject attempt signal)
* - NtCreateThreadEx or NtQueueApcThread (to execute the payload)
*
* Reducing the ETW-TI surface, not eliminating it.
*/
/*
* Approach 3: Use existing threads (thread hijacking, APC in alertable thread)
* to avoid CreateThread events.
*
* Thread hijacking (Ch32): modifies existing thread via SetContextThread.
* SetContextThread IS in the ETW-TI catalog — you still get an event.
* APC injection (Ch30): QueueUserAPC. Also in ETW-TI catalog.
*
* Tradeoff: reduces the number of ETW-TI events vs a full injection chain.
* Still detected by a tuned ETW-TI consumer.
*/
/*
* Approach 4: Kernel bypass via BYOVD (Bring Your Own Vulnerable Driver)
*
* A legitimate, signed-by-WHQL driver with a memory disclosure or write primitive
* can be used to patch kernel code from ring 3 (using the driver's IOCTL interface).
* This is the only real ETW-TI bypass from userland — not patching ETW-TI itself,
* but using kernel write access through a legitimately-signed conduit.
*
* Examples from threat intel:
* - RobbinHood ransomware: abused GIGABYTE driver (GDRV.sys)
* - BlackMatter: abused MSI Afterburner driver (RTCore64.sys)
* - AvosLocker: abused AV driver
*
* BYOVD is Part 15 content (kernel topics).
*/
void etw_ti_bypass_status_report(void) {
printf("ETW-TI bypass from pure userland: NOT POSSIBLE\n\n");
printf("What is possible from userland:\n");
printf(" 1. Reduce ETW-TI events by choosing lower-noise injection techniques\n");
printf(" 2. Blind userland ETW (Ch46) to hide from SIEM log-based detections\n");
printf(" 3. BYOVD for kernel write access (Part 15)\n\n");
printf("What MDE sees even with all userland bypasses applied:\n");
printf(" - Every NtAllocateVirtualMemory with RWX or cross-process scope\n");
printf(" - Every NtWriteVirtualMemory to another process\n");
printf(" - Every NtCreateThreadEx in a remote process\n");
printf(" - The call stack at each of those events\n");
printf("Mitigation: combine low-noise techniques + BYOVD for full bypass\n");
}
The Full Detection Layer Stack
Detection layer Bypass What still sees you
───────────────────────────────────────────────────────────────────────────
ntdll hooks (EDR) Ch38-40 unhook EDR loses argument visibility
but ETW-TI still fires for syscalls
Userland ETW Ch46 patch SIEM log-based detections go silent
(PS script logs, DotNET runtime,
kernel-file/process/registry providers)
AMSI Ch45 patch Script content no longer scanned
before execution
WDAC/WLDP Ch45 patch PS Constrained Language Mode disabled
Call stack detection Ch43 indirect Kernel sees ntdll as syscall origin
syscalls (return address in ntdll, not heap)
ETW-TI NOT BYPASSED Kernel emits events for ALL:
from userland - cross-process memory writes
- remote thread creation
- APC queuing
- handle duplication
MDE, Elastic, SentinelOne all consume
these via kernel callbacks
File system AV scan Obfuscation / Dropped files still scanned on write;
in-memory only encrypt payloads, decrypt in memory
Behavior detection Choose low- Pattern: RWX alloc + write + exec
(heuristics) noise chains in remote process = high confidence
No single bypass defeats heuristics
Complete bypass state (all userland layers applied):
───────────────────────────────────────────────────────────────────────────
✓ EDR API hooks bypassed (syscalls / unhooking)
✓ AMSI silenced
✓ Userland ETW silenced
✓ Call stack shows ntdll origin (indirect syscalls)
✗ ETW-TI active (requires kernel access to suppress)
✗ File AV active (keep payloads encrypted / fileless)
✗ Behavioral heuristics may trigger on injection sequenceQuestions & Answers
Which Windows version introduced ETW-TI and when did it become widely used by EDRs?
ETW-TI was introduced in Windows 10 version 1703 (Creators Update, April 2017) with the Microsoft-Windows-Threat-Intelligence provider GUID. The initial API surface was limited: NtAllocateVirtualMemory and NtWriteVirtualMemory cross-process events. Over subsequent releases, coverage expanded significantly: Windows 10 1809 added call stack capture per event, and 1903 added image load events and APC events. Microsoft Defender for Endpoint (formerly MDATP) began consuming ETW-TI via their kernel sensor around 2018, and CrowdStrike Falcon adopted ETW-TI supplementally (alongside their own kernel callbacks) around the same period. By 2020, any EDR not consuming ETW-TI was considered behind the curve. Today (2026), every major enterprise EDR relies on ETW-TI as a primary detection channel.
Can you disable ETW-TI by disabling the ETW service or clearing the ETW consumer list?
No. ETW-TI is not mediated by the Event Log service or any userland service. The events flow from the kernel (ntoskrnl.exe) directly to registered kernel-mode ETW consumers via a kernel callback chain. These consumers are EDR kernel drivers that loaded before your process started and registered with the kernel's ETW subsystem via EtwRegister() in kernel mode. Stopping the Windows Event Log service (EventLog) kills the consumer that writes events to .evtx files on disk — but it doesn't affect kernel-to-driver ETW channels at all. Your process doesn't appear in the consumer list because you don't have permission to enumerate or modify kernel callback registrations from ring 3.
Does ETW-TI fire for every VirtualAlloc or only cross-process ones?
Both — but with different severity weights. Self-allocation (allocating memory in your own process) does generate ETW-TI events, but these are lower priority and typically filtered by EDR detection logic. The high-fidelity signals are: cross-process NtWriteVirtualMemory (writing to another process's memory), cross-process NtCreateThreadEx (starting a thread in another process), NtMapViewOfSection with a remote target handle, and NtQueueApcThread targeting an external thread. These cross-process operations are the classic injection chain, and their ETW-TI events include both source and target process information — making the injection chain trivially detectable even without any userland hooks. Self-process RWX allocations are also flagged if they're followed by execution, but the bar is higher.
If BYOVD is the only way to bypass ETW-TI from userland, what's the operational cost of using it?
Significant. BYOVD requires: (1) shipping a known-vulnerable, WHQL-signed driver as part of your payload — this driver itself is a static detection target (its hash is in most EDR block lists after public disclosure); (2) loading the driver, which generates an image load event and requires privileges to call NtLoadDriver (typically requires SeLoadDriverPrivilege, meaning you need to be an admin or SYSTEM before the BYOVD stage); (3) using the driver's vulnerability (memory write, physical memory access, etc.) to reach kernel structures — this requires specific kernel version knowledge and offsets. In practice, BYOVD is used by ransomware groups that have already achieved high privilege and are in a "kill the EDR" phase before deploying the encryptor. It's not a technique for initial access — it's a post-exploitation nuclear option.
Can you use process injection techniques that don't trigger ETW-TI at all?
The ETW-TI catalog covers all the injection-relevant syscalls: allocate, write, map, create thread, set context, queue APC. There's no injection technique that achieves code execution in another process without using at least one of these. The closest thing to a low-ETW-TI approach is Pool Party (Ch36) or PROPagate (Ch37) — they still require cross-process memory manipulation (mapped sections, shared properties), which generates ETW-TI events. The mitigation isn't to avoid ETW-TI events entirely but to avoid the high-confidence detection signatures: specifically, the pattern of "write foreign process → create thread in foreign process → thread starts in non-module memory" all within seconds is the canonical injection detection. Spreading the events across time, using existing threads (APC, thread hijack), and writing only to pre-existing module pages (module stomping, Ch28) reduces the correlation quality — but doesn't eliminate the telemetry.