ETW-TI

Microsoft-Windows-Threat-Intelligence is a kernel ETW provider. Defender (DefenderApiLogger) and EDRs subscribe. Ordinary processes cannot — consumers must be PPL-Antimalware.

GUID: {F4E1897C-BB5D-5668-F1D8-040F4D8DD344} — always hashes to bucket 29.

ETW flow: provider fires → session buffers → consumer reads. If no session has subscribed, IsEnabled=0 and the kernel skips the event entirely.

.reload /f first if names do not resolve. LiveKD is read-only — eb needs a writable kernel debug session.

Events fired:

Event Trigger
ALLOCVM NtAllocateVirtualMemory with exec permissions
MAPVIEW NtMapViewOfSection into another process
READVM / WRITEVM cross-process memory ops
PROTECTVM permission change to exec
QUEUEUSERAPC APC injection
SETTHREADCONTEXT context hijack

Key Structures

_ETW_GUID_ENTRY
+0x000  GuidList          LIST_ENTRY (bucket chain)
+0x028  Guid              16 bytes  (Data1 = 0xF4E1897C for TI)
+0x060  ProviderEnableInfo  _TRACE_ENABLE_INFO  (OR of all session slots)
+0x080  EnableInfo[8]     one slot per session, each 0x20 bytes
+0x188  SiloState         back-pointer to silo

_TRACE_ENABLE_INFO
+0x000  IsEnabled         1=on, 0=off
+0x004  Level
+0x006  LoggerId          USHORT — index into EtwpLoggerContext
+0x008  EnableProperty
+0x010  MatchAnyKeyword
+0x018  MatchAllKeyword

Slot s address: entry + 0x80 + s * 0x20.


1. Find the silo

dp nt!EtwpDebuggerData L4

Dumps 4 pointers. Index [3] (+0x18) = _ETW_SILODRIVERSTATE. Save it.

? nt!EtwpDebuggerData - nt

RVA of the symbol — stable per build.

dt nt!_ETW_SILODRIVERSTATE <silo>

Two fields you need:

Field Offset Use
EtwpLoggerContext +0x1C8 session array (names)
EtwpGuidHashTable +0x1D0 64 buckets, embedded at silo+0x1D0

2. Navigate to bucket 29

?? sizeof(nt!_ETW_HASH_BUCKET)

Expect 0x38 (56 bytes). Bucket 29 offset = 29 * 0x38 = 0x658.

TI GUID hash is fixed:

(Data1 ^ Data2Data3 ^ Data4[0] ^ Data4[4]) & 0x3F
= (0xF4E1897C ^ 0xBB5D5668 ^ 0xF1 ^ 0x04) & 0x3F
= 0x1D = 29
dt nt!_ETW_HASH_BUCKET <silo>+0x1D0+0x658

ListHead[0].Flink = first entry in the bucket chain.


3. Find the TI GUID entry

dt nt!_ETW_GUID_ENTRY <ListHead[0].Flink>

Check Guid at +0x028. First DWORD (Data1) must be 0xF4E1897C. If not, follow GuidList.Flink to the next entry. Stop when Flink equals the bucket address (silo+0x1D0+0x658).

Fallback — byte search if the list walk is messy:

s -b <silo>+0x1D0 L?0x2000000 7c 89 e1 f4 5d bb 68 56 f1 d8 04 0f 4d 8d d3 44

The hit is the Guid field, not the struct start. Subtract 0x28:

dt nt!_ETW_GUID_ENTRY <hit>-0x28

Multiple hits = extra silos — keep the one whose SiloState (+0x188) matches your silo.


4. Check who is listening

dt nt!_TRACE_ENABLE_INFO <entry>+0x60

Aggregate slot at +0x60. IsEnabled=1 → at least one session is subscribed. Defender typically has MatchAnyKeyword = 0xFFFFFFFFFFFFFFFF.

Walk all 8 per-session slots (step +0x20):

dt nt!_TRACE_ENABLE_INFO <entry>+0x80
dt nt!_TRACE_ENABLE_INFO <entry>+0xa0
dt nt!_TRACE_ENABLE_INFO <entry>+0xc0
dt nt!_TRACE_ENABLE_INFO <entry>+0xe0
dt nt!_TRACE_ENABLE_INFO <entry>+0x100
dt nt!_TRACE_ENABLE_INFO <entry>+0x120
dt nt!_TRACE_ENABLE_INFO <entry>+0x140
dt nt!_TRACE_ENABLE_INFO <entry>+0x160

Note LoggerId for each slot where IsEnabled=1.


5. Identify the consumer (optional)

dt nt!_ETW_SILODRIVERSTATE <silo> EtwpLoggerContext

Gets the EtwpLoggerContext pointer. Each slot is 8 bytes. LoggerId N → offset N*8.

dq <EtwpLoggerContext>+<N*8> L1

Right column = _WMI_LOGGER_CONTEXT *.

dt nt!_WMI_LOGGER_CONTEXT <result>

LoggerName = session name (e.g. DefenderApiLogger). Skip slot [0] — it is a sentinel.


6. Disable

Zero the aggregate (fast — stops all event delivery immediately):

eb <entry>+0x60 0

Zero one per-session slot (slot 0 = +0x80):

eb <entry>+0x80 0

Zeroing both aggregate and all 8 slots ensures no slot re-enables delivery on its own.


Commands reference

Cmd Does
dp / dq / dd dump pointers / QWORDs / DWORDs
eb write one byte (IsEnabled)
dt dump typed struct or one field
?? sizeof(...) type size — no memory read
s -b byte search (GUID bytes in memory)
poi(addr) dereference pointer at addr

Offsets (+0x1D0, +0x60, +0x80, …) are build-specific — confirm with dt on the target. Bucket 29 is GUID-fixed regardless of build.