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.