175 lines
6.2 KiB
Zig
175 lines
6.2 KiB
Zig
// PEB-based module and function resolution, everything resolved at runtime.
|
|
const std = @import("std");
|
|
const win = @import("win32.zig");
|
|
const pe = @import("pe.zig");
|
|
|
|
pub const LIST_ENTRY = extern struct {
|
|
Flink: *LIST_ENTRY,
|
|
Blink: *LIST_ENTRY,
|
|
};
|
|
|
|
pub const PEB_LDR_DATA = extern struct {
|
|
Length: win.ULONG,
|
|
Initialized: win.BYTE,
|
|
Padding: [3]win.BYTE,
|
|
SsHandle: win.LPVOID,
|
|
InLoadOrderModuleList: LIST_ENTRY,
|
|
InMemoryOrderModuleList: LIST_ENTRY,
|
|
InInitializationOrderModuleList: LIST_ENTRY,
|
|
};
|
|
|
|
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,
|
|
};
|
|
|
|
// 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,
|
|
Ldr: *PEB_LDR_DATA,
|
|
};
|
|
|
|
// ror13 hash, case-insensitive.
|
|
pub fn hash_ror13(input: []const u8) u32 {
|
|
var hash: u32 = 0;
|
|
for (input) |c| {
|
|
var c_upper = c;
|
|
if (c_upper >= 'a' and c_upper <= 'z') c_upper -= 32;
|
|
hash = (hash >> 13) | (hash << 19);
|
|
hash +%= c_upper;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
pub fn get_peb() *PEB {
|
|
const ptr: usize = asm volatile ("mov %%gs:0x60, %[ptr]"
|
|
: [ptr] "=r" (-> usize),
|
|
);
|
|
return @as(*PEB, @ptrFromInt(ptr));
|
|
}
|
|
|
|
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));
|
|
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")))));
|
|
|
|
if (le.BaseDllName.Length == 0) continue;
|
|
|
|
const buf = le.BaseDllName.Buffer;
|
|
const len = le.BaseDllName.Length / 2;
|
|
var mod_hash: u32 = 0;
|
|
var i: usize = 0;
|
|
while (i < len) : (i += 1) {
|
|
var c = buf[i];
|
|
if (c >= 'a' and c <= 'z') c -= 32;
|
|
mod_hash = (mod_hash >> 13) | (mod_hash << 19);
|
|
mod_hash +%= @as(u32, c);
|
|
}
|
|
|
|
if (mod_hash == hash) return le.DllBase;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
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);
|
|
const dot_pos = std.mem.indexOfScalar(u8, str, '.') orelse return null;
|
|
const dll_hash = hash_ror13(str[0..dot_pos]);
|
|
const func_hash = hash_ror13(str[dot_pos + 1 ..]);
|
|
const dll_base = get_module_by_hash(dll_hash) orelse return null;
|
|
return get_func_by_hash(dll_base, func_hash);
|
|
}
|
|
|
|
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)));
|
|
if (dos.e_magic != 0x5A4D) return null;
|
|
|
|
const nt = @as(*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)))));
|
|
|
|
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)))));
|
|
|
|
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 = std.mem.sliceTo(name_ptr, 0);
|
|
|
|
if (hash_ror13(name) == func_hash) {
|
|
const ordinal = ords[i];
|
|
if (ordinal >= exp.NumberOfFunctions) continue;
|
|
const rva = funcs[ordinal];
|
|
if (rva >= export_dir.VirtualAddress and rva < export_dir.VirtualAddress + export_dir.Size) {
|
|
return resolve_forward_string(base_bytes, rva);
|
|
}
|
|
return @as(*anyopaque, @ptrCast(base_bytes + @as(usize, @intCast(rva))));
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
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| {
|
|
if (get_func_by_hash(base, func_hash)) |f| return f;
|
|
}
|
|
}
|
|
|
|
if (cached_kernel32 == null) cached_kernel32 = get_module_by_hash(hash_ror13("kernel32.dll"));
|
|
if (cached_kernel32) |base| {
|
|
if (get_func_by_hash(base, func_hash)) |f| return f;
|
|
}
|
|
|
|
if (cached_ntdll == null) cached_ntdll = get_module_by_hash(hash_ror13("ntdll.dll"));
|
|
if (cached_ntdll) |base| {
|
|
if (get_func_by_hash(base, func_hash)) |f| return f;
|
|
}
|
|
|
|
const peb_ptr = get_peb();
|
|
const ldr = peb_ptr.Ldr;
|
|
const head = @as(*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")))));
|
|
|
|
if (cached_kernel32) |ck| if (le.DllBase == ck) continue;
|
|
if (cached_ntdll) |cn| if (le.DllBase == cn) continue;
|
|
|
|
if (get_func_by_hash(le.DllBase, func_hash)) |f| return f;
|
|
}
|
|
|
|
return null;
|
|
}
|