mirror of
https://github.com/0xflux/sanctum
synced 2026-06-06 15:04:29 +00:00
Detects thread start on sensitive APIs
This commit is contained in:
Vendored
+1
@@ -4,6 +4,7 @@
|
||||
|
||||
"cSpell.words": [
|
||||
"Addreg",
|
||||
"addrs",
|
||||
"alertable",
|
||||
"AMSI",
|
||||
"APIC",
|
||||
|
||||
@@ -29,6 +29,7 @@ const SSN_COUNT: usize = 0x500;
|
||||
|
||||
pub const SSN_NT_OPEN_PROCESS: u32 = 0x26;
|
||||
pub const SSN_NT_ALLOCATE_VIRTUAL_MEMORY: u32 = 0x18;
|
||||
pub const SSN_NT_CREATE_THREAD_EX: u32 = 0x00c9;
|
||||
pub const SSN_NT_WRITE_VM: u32 = 0x003a;
|
||||
|
||||
const NT_OPEN_FILE: u32 = 0x0033;
|
||||
@@ -452,7 +453,8 @@ pub unsafe extern "system" fn syscall_handler(
|
||||
match ssn {
|
||||
SSN_NT_OPEN_PROCESS
|
||||
| SSN_NT_ALLOCATE_VIRTUAL_MEMORY
|
||||
| SSN_NT_WRITE_VM => KernelSyscallIntercept::from_alt_syscall(ktrap_frame),
|
||||
| SSN_NT_WRITE_VM
|
||||
| SSN_NT_CREATE_THREAD_EX => KernelSyscallIntercept::from_alt_syscall(ktrap_frame),
|
||||
// 0x4e => {
|
||||
// println!(
|
||||
// "[create thread] [i] Hook. SSN {:#x}, rcx as usize: {}. Stack ptr: {:p}",
|
||||
|
||||
@@ -27,8 +27,7 @@ use wdk_sys::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
DRIVER_MESSAGES, REGISTRATION_HANDLE, core::process_monitor::ProcessMonitor,
|
||||
device_comms::ImageLoadQueueForInjector, utils::unicode_to_string,
|
||||
core::process_monitor::{LoadedModule, ProcessMonitor}, device_comms::ImageLoadQueueForInjector, utils::unicode_to_string, DRIVER_MESSAGES, REGISTRATION_HANDLE
|
||||
};
|
||||
|
||||
/// Callback function for a new process being created on the system.
|
||||
@@ -287,9 +286,23 @@ extern "C" fn image_load_callback(
|
||||
let name_slice = slice_from_raw_parts(image_name.Buffer, (image_name.Length / 2) as usize);
|
||||
let name = String::from_utf16_lossy(unsafe { &*name_slice }).to_lowercase();
|
||||
|
||||
// For now only concern ourselves with image loads where its an exe, except in the event its the sanctum EDR DLL -
|
||||
// see below comments for why.
|
||||
// In the event it is a DLL load, we want to grab & track its mappings
|
||||
if name.contains(".dll") && !name.contains("sanctum.dll") {
|
||||
// todo hash check on the sanctum DLL to make sure an adversary isn't calling their malicious DLL `sanctum.dll`
|
||||
// which would interfere with what we are doing in this segment.
|
||||
|
||||
// todo is it re-loading NTDLL when NTDLL already exists in the process? Bad, we want to stop this and report
|
||||
// on it.
|
||||
|
||||
let lm = LoadedModule::new(
|
||||
image_info.ImageBase as _,
|
||||
image_info.ImageSize as _,
|
||||
None,
|
||||
None
|
||||
);
|
||||
|
||||
ProcessMonitor::add_loaded_module(lm, &name, pid as u32);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
//! Ghost Hunting telemetry
|
||||
|
||||
use core::{
|
||||
ffi::c_void, mem::replace, ptr::null_mut, time::Duration
|
||||
arch::asm, ffi::c_void, mem::replace, ptr::null_mut, time::Duration
|
||||
};
|
||||
|
||||
use alloc::{
|
||||
@@ -34,14 +34,13 @@ use wdk_mutex::{
|
||||
};
|
||||
use wdk_sys::{
|
||||
ntddk::{
|
||||
IoGetCurrentProcess, KeDelayExecutionThread, KeQuerySystemTimePrecise,
|
||||
ObOpenObjectByPointer, ObReferenceObjectByHandle, PsCreateSystemThread, PsGetProcessId,
|
||||
}, PsProcessType, HANDLE, LARGE_INTEGER, LIST_ENTRY, PROCESS_ALL_ACCESS, PROCESS_BASIC_INFORMATION, STATUS_SUCCESS, THREAD_ALL_ACCESS, TRUE, _EPROCESS, _LARGE_INTEGER, _MODE::KernelMode
|
||||
IoGetCurrentProcess, KeDelayExecutionThread, KeQuerySystemTimePrecise, MmIsAddressValid, ObOpenObjectByPointer, ObReferenceObjectByHandle, PsCreateSystemThread, PsGetCurrentThreadTeb, PsGetProcessId
|
||||
}, PsProcessType, FALSE, HANDLE, LARGE_INTEGER, LIST_ENTRY, PROCESS_ALL_ACCESS, PROCESS_BASIC_INFORMATION, STATUS_SUCCESS, THREAD_ALL_ACCESS, TRUE, _EPROCESS, _LARGE_INTEGER, _MODE::KernelMode, _PEB
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ffi::NtQueryInformationProcess,
|
||||
utils::{DriverError, eprocess_to_process_name},
|
||||
ffi::{NtQueryInformationProcess, IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, IMAGE_NT_HEADERS64},
|
||||
utils::{eprocess_to_process_name, scan_usermode_module_for_function_address, DriverError},
|
||||
};
|
||||
|
||||
/// A `Process` is a Sanctum driver representation of a Windows process so that actions it preforms, and is performed
|
||||
@@ -60,11 +59,70 @@ pub struct Process {
|
||||
pub ghost_hunting_timers: Vec<GhostHuntingTimer>,
|
||||
targeted_by_apis: Vec<ProcessTargetedApis>,
|
||||
marked_for_deletion: bool,
|
||||
// Note: It is possible atm for any processes started before the EDR was switched on that
|
||||
// we don't readily have this data. If the driver is loaded as ELAM then this wouldn't be such
|
||||
// a problem.
|
||||
// todo fix
|
||||
loaded_modules: Option<LoadedModules>,
|
||||
}
|
||||
|
||||
/// A BTreeMap of loaded modules in the process, with:
|
||||
///
|
||||
/// - `key`: Module name
|
||||
/// `value`: [`LoadedModule`]
|
||||
#[derive(Debug)]
|
||||
pub struct LoadedModules {
|
||||
inner: BTreeMap<String, LoadedModule>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SensitiveAPI {
|
||||
LdrLoadDLL,
|
||||
LoadLibraryA,
|
||||
LoadLibraryW,
|
||||
}
|
||||
|
||||
/// A representation of a module loaded into a process.
|
||||
#[derive(Debug)]
|
||||
pub struct LoadedModule {
|
||||
image_base: *const c_void,
|
||||
image_sz: usize,
|
||||
ntdll_addresses: Option<NtdllAddresses>,
|
||||
kernel32_addresses: Option<Kernel32Addresses>,
|
||||
}
|
||||
|
||||
impl LoadedModule {
|
||||
pub fn new(
|
||||
image_base: *const c_void,
|
||||
image_sz: usize,
|
||||
ntdll_addresses: Option<NtdllAddresses>,
|
||||
kernel32_addresses: Option<Kernel32Addresses>,
|
||||
) -> Self {
|
||||
Self {
|
||||
image_base,
|
||||
image_sz,
|
||||
ntdll_addresses,
|
||||
kernel32_addresses,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Addresses within NTDLL that we care about detecting access to.
|
||||
#[derive(Debug)]
|
||||
pub struct NtdllAddresses {
|
||||
pub LdrLoadDLL: *const c_void,
|
||||
}
|
||||
|
||||
/// Addresses within NTDLL that we care about detecting access to.
|
||||
#[derive(Debug)]
|
||||
pub struct Kernel32Addresses {
|
||||
pub LoadLibraryW: *const c_void,
|
||||
pub LoadLibraryA: *const c_void,
|
||||
}
|
||||
|
||||
// todo needs implementing
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ProcessTargetedApis {}
|
||||
pub struct ProcessTargetedApis;
|
||||
|
||||
/// A `GhostHuntingTimer` is the timer metadata associated with the Ghost Hunting technique on my blog:
|
||||
/// https://fluxsec.red/edr-syscall-hooking
|
||||
@@ -148,11 +206,78 @@ impl ProcessMonitor {
|
||||
ghost_hunting_timers: Vec::new(),
|
||||
targeted_by_apis: Vec::new(),
|
||||
marked_for_deletion: false,
|
||||
// Setting this to None should be ok now as the module data should come in once the modules
|
||||
// load into the process.
|
||||
loaded_modules: None,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_loaded_module(
|
||||
mut lm: LoadedModule,
|
||||
image_name: &String,
|
||||
pid: u32,
|
||||
) {
|
||||
|
||||
// Lookup any relevant addresses in NTDLL and Kernel32.dll that we want to look for at runtime
|
||||
// with the EDR whilst spending time on a kernel thread. If we make this lookup once per image, it
|
||||
// should be in the long run, less expensive.
|
||||
get_monitored_dll_address_fn_addrs(&mut lm, image_name);
|
||||
|
||||
let mut process_lock = ProcessMonitor::get_mtx_inner();
|
||||
|
||||
let process = match process_lock.get_mut(&pid) {
|
||||
Some(process) => process,
|
||||
None => {
|
||||
println!("[sanctum] [-] PID {pid} not found in active processes when trying to add image load info.");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(process_loaded_mods) = process.loaded_modules.as_mut() {
|
||||
let _ = process_loaded_mods.inner.insert(image_name.clone(), lm);
|
||||
} else {
|
||||
let mut b = BTreeMap::new();
|
||||
b.insert(image_name.clone(), lm);
|
||||
process.loaded_modules = Some(LoadedModules { inner: b });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fn_pointer_to_sensitive_address(pid: u32, requested_addr: *const c_void) -> Option<SensitiveAPI> {
|
||||
let process_lock = ProcessMonitor::get_mtx_inner();
|
||||
|
||||
if let Some(process ) = process_lock.get(&pid) {
|
||||
match &process.loaded_modules {
|
||||
Some(lm) => {
|
||||
for (_, module_info) in &lm.inner {
|
||||
if let Some(info) = &module_info.kernel32_addresses {
|
||||
if info.LoadLibraryA == requested_addr {
|
||||
return Some(SensitiveAPI::LoadLibraryA);
|
||||
}
|
||||
if info.LoadLibraryW == requested_addr {
|
||||
return Some(SensitiveAPI::LoadLibraryW);
|
||||
}
|
||||
} else if let Some(info) = &module_info.ntdll_addresses {
|
||||
if info.LdrLoadDLL == requested_addr {
|
||||
return Some(SensitiveAPI::LdrLoadDLL);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
println!("[sanctum] [-] process.loaded_modules was none.");
|
||||
println!("{process:#?}");
|
||||
return None
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
println!("[sanctum] [-] Could not lock process monitor for fn_pointer_to_sensitive_address");
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// todo need to remove processes from the monitor once they are terminated
|
||||
pub fn remove_process(pid: u32) {
|
||||
let mut process_lock = ProcessMonitor::get_mtx_inner();
|
||||
@@ -567,5 +692,50 @@ fn extract_process_details<'a>(process: *mut _EPROCESS, pid: usize) -> Result<Pr
|
||||
ghost_hunting_timers: Vec::new(),
|
||||
targeted_by_apis: Vec::new(),
|
||||
marked_for_deletion: false,
|
||||
// todo - do we need to grab these?
|
||||
loaded_modules: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Lookup the addresses of functions we wish to monitor abuse against, populating the [`LoadedModule`]
|
||||
/// struct mutably.
|
||||
///
|
||||
/// `image_name` is the name of the image being loaded.
|
||||
fn get_monitored_dll_address_fn_addrs(lm: &mut LoadedModule, image_name: &String) {
|
||||
|
||||
if image_name.to_lowercase().contains(r"\windows\system32\ntdll.dll") {
|
||||
let ldr_ld_dll = scan_usermode_module_for_function_address(
|
||||
lm.image_base,
|
||||
"LdrLoadDLL"
|
||||
);
|
||||
|
||||
if ldr_ld_dll.is_ok() {
|
||||
lm.ntdll_addresses = Some(
|
||||
NtdllAddresses {
|
||||
LdrLoadDLL: ldr_ld_dll.unwrap(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if image_name.to_lowercase().contains(r"\windows\system32\kernel32.dll") {
|
||||
let lla = scan_usermode_module_for_function_address(
|
||||
lm.image_base,
|
||||
"LoadLibraryA"
|
||||
);
|
||||
let llw = scan_usermode_module_for_function_address(
|
||||
lm.image_base,
|
||||
"LoadLibraryW"
|
||||
);
|
||||
|
||||
if lla.is_ok() && llw.is_ok() {
|
||||
lm.kernel32_addresses = Some(
|
||||
Kernel32Addresses {
|
||||
LoadLibraryW: llw.unwrap(),
|
||||
LoadLibraryA: lla.unwrap(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
use core::{
|
||||
ffi::c_void,
|
||||
mem::{self, MaybeUninit},
|
||||
mem::{self},
|
||||
ptr::null_mut,
|
||||
sync::atomic::{AtomicBool, AtomicPtr, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use alloc::{collections::vec_deque::VecDeque, string::ToString, vec::Vec};
|
||||
use shared_no_std::ghost_hunting::{NtAllocateVirtualMemoryData, NtFunction, NtOpenProcessData, NtWriteVirtualMemoryData, Syscall};
|
||||
use alloc::{collections::vec_deque::VecDeque, string::ToString};
|
||||
use shared_no_std::ghost_hunting::{NtAllocateVirtualMemoryData, NtCreateThreadExData, NtFunction, NtOpenProcessData, NtWriteVirtualMemoryData, Syscall};
|
||||
use wdk::{nt_success, println};
|
||||
use wdk_mutex::{
|
||||
fast_mutex::FastMutexGuard,
|
||||
@@ -21,7 +21,7 @@ use wdk_sys::{
|
||||
}, CLIENT_ID, FALSE, HANDLE, KTRAP_FRAME, LARGE_INTEGER, STATUS_SUCCESS, THREAD_ALL_ACCESS, _KWAIT_REASON::Executive, _MODE::KernelMode
|
||||
};
|
||||
|
||||
use crate::{alt_syscalls::{SSN_NT_ALLOCATE_VIRTUAL_MEMORY, SSN_NT_OPEN_PROCESS, SSN_NT_WRITE_VM}, core::process_monitor::ProcessMonitor, utils::{handle_to_pid, DriverError}};
|
||||
use crate::{alt_syscalls::{SSN_NT_ALLOCATE_VIRTUAL_MEMORY, SSN_NT_CREATE_THREAD_EX, SSN_NT_OPEN_PROCESS, SSN_NT_WRITE_VM}, core::process_monitor::ProcessMonitor, utils::{handle_to_pid, DriverError}};
|
||||
|
||||
/// Returns a borrowed view over stack argument slots saved in a `_KTRAP_FRAME`.
|
||||
/// The slice elements are interpreted as raw pointer-width values (`*const c_void`)
|
||||
@@ -107,6 +107,7 @@ impl KernelSyscallIntercept {
|
||||
SSN_NT_ALLOCATE_VIRTUAL_MEMORY => Self::nt_allocate_vm(ktrap_frame),
|
||||
SSN_NT_OPEN_PROCESS => Self::nt_open_process(ktrap_frame),
|
||||
SSN_NT_WRITE_VM => Self::nt_write_vm(ktrap_frame),
|
||||
SSN_NT_CREATE_THREAD_EX => Self::nt_create_thread_ex(ktrap_frame),
|
||||
_ => {
|
||||
println!("[-] [sanctum] Unknown SSN received, {:?}", ktrap_frame.Rax as u32);
|
||||
None
|
||||
@@ -119,6 +120,67 @@ impl KernelSyscallIntercept {
|
||||
}
|
||||
}
|
||||
|
||||
fn nt_create_thread_ex(
|
||||
ktrap_frame: KTRAP_FRAME,
|
||||
) -> Option<Syscall> {
|
||||
|
||||
let current_pid = unsafe { PsGetCurrentProcessId() } as u32;
|
||||
let dest_pid = handle_to_pid(ktrap_frame.R9 as *mut c_void);
|
||||
|
||||
// For now, we are not interested in self thread creates
|
||||
if current_pid == dest_pid {
|
||||
return None;
|
||||
}
|
||||
|
||||
let stack_args = unsafe { ktrap_get_stack_args(&ktrap_frame, 2) };
|
||||
// SAFETY: Reading two arguments, within limits from the call to ktrap_get_stack_args
|
||||
let start_address = stack_args[0];
|
||||
let argument = stack_args[1];
|
||||
|
||||
//
|
||||
// With the NtCreateThread API, before permitting the syscall to continue, we want to do some sanitisation
|
||||
// at this point. I accept this is perhaps not a realistic approach, and we would do this on the telemetry server
|
||||
// as post-processing; BUT this is a POC and this EDR is designed for 'paranoid mode'. Perhaps in the future we can have
|
||||
// different settings on the EDR: Paranoid, Casual Blocking, Report Only, etc.
|
||||
//
|
||||
// First - we need to check there are no outstanding Ghost Hunt's on the process from within the driver; this is to outright
|
||||
// prevent Hell's Gate type syscall malware behaviour. There is no realistic valid reason for a process to be doing direct
|
||||
// / indirect syscalls..
|
||||
//
|
||||
// Second, we need to determine what the thread is pointing to, is it to:
|
||||
// - Classic library loading API's?
|
||||
// - To a shellcode stub?
|
||||
// - To a valid routine within the process image's .text section?
|
||||
//
|
||||
// We can access the loaded modules through the image callback in the driver
|
||||
//
|
||||
// Whilst the second listed checks above wont be slow in isolation, at scale, this could impact performance. An interesting debate
|
||||
// for the future. For now, I want to do these checks at 'syscall-time'.
|
||||
//
|
||||
|
||||
if let Some(resolved) = ProcessMonitor::fn_pointer_to_sensitive_address(
|
||||
dest_pid,
|
||||
start_address
|
||||
) {
|
||||
println!("**** WARNING: Sensitive API detected in start thread!! {resolved:?}");
|
||||
}
|
||||
|
||||
|
||||
println!("Start: {start_address:p}, arg: {argument:p}");
|
||||
|
||||
let data = Syscall::from_kernel(
|
||||
current_pid,
|
||||
NtFunction::NtCreateThreadEx(NtCreateThreadExData {
|
||||
target_pid: dest_pid,
|
||||
start_routine: start_address as usize,
|
||||
argument: argument as usize,
|
||||
}));
|
||||
|
||||
println!("Data from NtCreateThreadEx: {data:?}");
|
||||
|
||||
Some(data)
|
||||
}
|
||||
|
||||
fn nt_write_vm(
|
||||
ktrap_frame: KTRAP_FRAME,
|
||||
) -> Option<Syscall> {
|
||||
|
||||
@@ -86,4 +86,102 @@ unsafe extern "system" {
|
||||
flags: ULONG,
|
||||
new_thread_handle: PHANDLE,
|
||||
) -> NTSTATUS;
|
||||
}
|
||||
|
||||
#[repr(C, packed(2))]
|
||||
pub struct IMAGE_DOS_HEADER {
|
||||
|
||||
pub e_magic: u16,
|
||||
pub e_cblp: u16,
|
||||
pub e_cp: u16,
|
||||
pub e_crlc: u16,
|
||||
pub e_cparhdr: u16,
|
||||
pub e_minalloc: u16,
|
||||
pub e_maxalloc: u16,
|
||||
pub e_ss: u16,
|
||||
pub e_sp: u16,
|
||||
pub e_csum: u16,
|
||||
pub e_ip: u16,
|
||||
pub e_cs: u16,
|
||||
pub e_lfarlc: u16,
|
||||
pub e_ovno: u16,
|
||||
pub e_res: [u16; 4],
|
||||
pub e_oemid: u16,
|
||||
pub e_oeminfo: u16,
|
||||
pub e_res2: [u16; 10],
|
||||
pub e_lfanew: i32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct IMAGE_FILE_HEADER {
|
||||
pub Machine: u16,
|
||||
pub NumberOfSections: u16,
|
||||
pub TimeDateStamp: u32,
|
||||
pub PointerToSymbolTable: u32,
|
||||
pub NumberOfSymbols: u32,
|
||||
pub SizeOfOptionalHeader: u16,
|
||||
pub Characteristics: u16,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct IMAGE_DATA_DIRECTORY {
|
||||
pub VirtualAddress: u32,
|
||||
pub Size: u32,
|
||||
}
|
||||
|
||||
#[repr(C, packed(4))]
|
||||
pub struct IMAGE_OPTIONAL_HEADER64 {
|
||||
|
||||
pub Magic: u16,
|
||||
pub MajorLinkerVersion: u8,
|
||||
pub MinorLinkerVersion: u8,
|
||||
pub SizeOfCode: u32,
|
||||
pub SizeOfInitializedData: u32,
|
||||
pub SizeOfUninitializedData: u32,
|
||||
pub AddressOfEntryPoint: u32,
|
||||
pub BaseOfCode: u32,
|
||||
pub ImageBase: u64,
|
||||
pub SectionAlignment: u32,
|
||||
pub FileAlignment: u32,
|
||||
pub MajorOperatingSystemVersion: u16,
|
||||
pub MinorOperatingSystemVersion: u16,
|
||||
pub MajorImageVersion: u16,
|
||||
pub MinorImageVersion: u16,
|
||||
pub MajorSubsystemVersion: u16,
|
||||
pub MinorSubsystemVersion: u16,
|
||||
pub Win32VersionValue: u32,
|
||||
pub SizeOfImage: u32,
|
||||
pub SizeOfHeaders: u32,
|
||||
pub CheckSum: u32,
|
||||
pub Subsystem: u16,
|
||||
pub DllCharacteristics: u16,
|
||||
pub SizeOfStackReserve: u64,
|
||||
pub SizeOfStackCommit: u64,
|
||||
pub SizeOfHeapReserve: u64,
|
||||
pub SizeOfHeapCommit: u64,
|
||||
pub LoaderFlags: u32,
|
||||
pub NumberOfRvaAndSizes: u32,
|
||||
pub DataDirectory: [IMAGE_DATA_DIRECTORY; 16],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct IMAGE_NT_HEADERS64 {
|
||||
pub Signature: u32,
|
||||
pub FileHeader: IMAGE_FILE_HEADER,
|
||||
pub OptionalHeader: IMAGE_OPTIONAL_HEADER64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct IMAGE_EXPORT_DIRECTORY {
|
||||
pub Characteristics: u32,
|
||||
pub TimeDateStamp: u32,
|
||||
pub MajorVersion: u16,
|
||||
pub MinorVersion: u16,
|
||||
pub Name: u32,
|
||||
pub Base: u32,
|
||||
pub NumberOfFunctions: u32,
|
||||
pub NumberOfNames: u32,
|
||||
pub AddressOfFunctions: u32,
|
||||
pub AddressOfNames: u32,
|
||||
pub AddressOfNameOrdinals: u32,
|
||||
}
|
||||
+71
-3
@@ -12,13 +12,12 @@ use shared_no_std::constants::SanctumVersion;
|
||||
use wdk::println;
|
||||
use wdk_sys::{
|
||||
ntddk::{
|
||||
IoThreadToProcess, KeGetCurrentIrql, ObReferenceObjectByHandle, ObfDereferenceObject, PsGetProcessId, RtlInitUnicodeString, RtlUnicodeStringToAnsiString, ZwClose, ZwCreateFile, ZwWriteFile
|
||||
IoThreadToProcess, KeGetCurrentIrql, MmIsAddressValid, ObReferenceObjectByHandle, ObfDereferenceObject, PsGetProcessId, RtlInitUnicodeString, RtlUnicodeStringToAnsiString, ZwClose, ZwCreateFile, ZwWriteFile
|
||||
}, PsProcessType, DRIVER_OBJECT, FALSE, FILE_APPEND_DATA, FILE_ATTRIBUTE_NORMAL, FILE_OPEN_IF, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SYNCHRONOUS_IO_NONALERT, GENERIC_WRITE, HANDLE, IO_STATUS_BLOCK, LIST_ENTRY, OBJECT_ATTRIBUTES, OBJ_CASE_INSENSITIVE, OBJ_KERNEL_HANDLE, PASSIVE_LEVEL, PETHREAD, PHANDLE, POBJECT_ATTRIBUTES, PROCESS_ALL_ACCESS, PVOID, STATUS_SUCCESS, STRING, ULONG, UNICODE_STRING, _EPROCESS, _KPROCESS, _KTHREAD, _MODE::KernelMode
|
||||
};
|
||||
|
||||
use crate::{
|
||||
DRIVER_MESSAGES,
|
||||
ffi::{InitializeObjectAttributes, PsGetProcessImageFileName},
|
||||
ffi::{InitializeObjectAttributes, PsGetProcessImageFileName, IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, IMAGE_NT_HEADERS64}, DRIVER_MESSAGES
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -36,6 +35,7 @@ pub enum DriverError {
|
||||
ImageSizeNotFound,
|
||||
ResourceStateInvalid,
|
||||
MutexError,
|
||||
UnexpectedSignature(String),
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
@@ -495,4 +495,72 @@ pub fn get_process_name() -> String {
|
||||
}
|
||||
|
||||
current_process_thread_name
|
||||
}
|
||||
|
||||
/// Scan a module by its in memory base address for function offsets. The target param should NOT be null
|
||||
/// terminated.
|
||||
pub fn scan_usermode_module_for_function_address(
|
||||
base: *const c_void,
|
||||
target: &str,
|
||||
) -> Result<*const c_void, DriverError>{
|
||||
// The memory should always be valid.. but.. Cannot use ProbeForRead as we don't
|
||||
// have access to __try :( this is as close as I can get right now I think
|
||||
if unsafe { MmIsAddressValid(base as _) } == FALSE as u8 {
|
||||
println!("[sanctum [-] Address of ntdll not valid.");
|
||||
return Err(DriverError::ResourceStateInvalid);
|
||||
}
|
||||
|
||||
let dos = unsafe { &*(base as *const IMAGE_DOS_HEADER) };
|
||||
if dos.e_magic != 0x5A4D {
|
||||
return Err(DriverError::UnexpectedSignature("DOS Header".into()));
|
||||
}
|
||||
|
||||
let nth = unsafe { &*(base.add(dos.e_lfanew as usize) as *const IMAGE_NT_HEADERS64) };
|
||||
if nth.Signature != 0x00004550 {
|
||||
return Err(DriverError::UnexpectedSignature("NT Signarue".into()));
|
||||
}
|
||||
if nth.OptionalHeader.Magic != 0x20B {
|
||||
return Err(DriverError::UnexpectedSignature("NT Magic".into()));
|
||||
}
|
||||
|
||||
unsafe {
|
||||
const IMAGE_DIRECTORY_ENTRY_EXPORT: usize = 0;
|
||||
let dir = &nth.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
if dir.VirtualAddress == 0 || dir.Size < size_of::<IMAGE_EXPORT_DIRECTORY>() as u32 {
|
||||
return Err(DriverError::Unknown("Invalid length".into()));
|
||||
}
|
||||
let exp = &*(rva(base as *const _, dir.VirtualAddress) as *const IMAGE_EXPORT_DIRECTORY);
|
||||
|
||||
let names = rva(base as *const _, exp.AddressOfNames) as *const u32;
|
||||
let ords = rva(base as *const _, exp.AddressOfNameOrdinals) as *const u16;
|
||||
let funcs = rva(base as *const _, exp.AddressOfFunctions) as *const u32;
|
||||
|
||||
for i in 0..exp.NumberOfNames {
|
||||
let name_ptr = rva(base as *const _, *names.add(i as usize));
|
||||
// compare ascii of the function name
|
||||
let mut p = name_ptr;
|
||||
let mut ok = true;
|
||||
for b in target.as_bytes() {
|
||||
if *p != *b { ok = false; break; }
|
||||
p = p.add(1);
|
||||
}
|
||||
if ok && *p == 0 {
|
||||
let ord = *ords.add(i as usize) as usize;
|
||||
let rva_fn = *funcs.add(ord) as usize;
|
||||
let fn_ptr = (base as *const u8).add(rva_fn) as *const u8;
|
||||
let fn_rva = rva_fn as u32;
|
||||
if fn_rva >= dir.VirtualAddress && fn_rva < dir.VirtualAddress + dir.Size {
|
||||
continue;
|
||||
}
|
||||
return Ok(fn_ptr as *const c_void);
|
||||
}
|
||||
}
|
||||
|
||||
return Err(DriverError::FunctionNotFoundInModule);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
unsafe fn rva<'a>(base: *const u8, off: u32) -> *const u8 {
|
||||
unsafe { base.add(off as usize) }
|
||||
}
|
||||
@@ -64,6 +64,7 @@ pub enum NtFunction {
|
||||
NtOpenProcess(NtOpenProcessData),
|
||||
NtWriteVirtualMemory(NtWriteVirtualMemoryData),
|
||||
NtAllocateVirtualMemory(NtAllocateVirtualMemoryData),
|
||||
NtCreateThreadEx(NtCreateThreadExData),
|
||||
}
|
||||
|
||||
/// todo docs
|
||||
@@ -81,7 +82,6 @@ pub struct NtWriteVirtualMemoryData {
|
||||
pub buf_len: usize,
|
||||
}
|
||||
|
||||
|
||||
unsafe impl Send for Syscall {}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
@@ -94,7 +94,8 @@ pub struct NtAllocateVirtualMemoryData {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct NtOpenProcess {
|
||||
pub struct NtCreateThreadExData {
|
||||
pub target_pid: u32,
|
||||
pub acces_mask: u32,
|
||||
pub start_routine: usize,
|
||||
pub argument: usize,
|
||||
}
|
||||
Reference in New Issue
Block a user