Skip to content
This repository has been archived by the owner on Jul 11, 2024. It is now read-only.

Commit

Permalink
Added EPT Syscall Hooks via SSDT Entry
Browse files Browse the repository at this point in the history
  • Loading branch information
memN0ps committed Jan 28, 2024
1 parent aa0cfbb commit 6bf11a3
Show file tree
Hide file tree
Showing 10 changed files with 463 additions and 15 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ A lightweight, memory-safe, and blazingly fast Rust-based type-2 research hyperv
- :white_check_mark: **Extended Page Tables (EPT)**: Support for Memory Type Range Registers (MTRR).
- :white_check_mark: **VM Exit Handling**: Handling of `ExceptionOrNmi (#GP, #PF, #BP, #UD)`, `Cpuid`, `Getsec`, `Vmcall`, `Vmclear`, `Vmlaunch`, `Vmptrld`, `Vmptrst`, `Vmresume`, `Vmxon`, `Vmxoff` `Rdmsr`, `Wrmsr`, `Invd`, `Rdtsc`, `EptViolation`, `EptMisconfiguration`, `Invept`, `Invvpid`, `Xsetbv`.
- :white_check_mark: **Kernel Inline Hooks**: PatchGuard-compatible breakpoint (`int3`) hooks.
- :x: **System Call (Syscall) Hooks (TODO)**: PatchGuard-compatible hooks for System Service Descriptor Table (SSDT) function entries.
- :white_check_mark: **System Call (Syscall) Hooks (Testing)**: PatchGuard-compatible hooks for System Service Descriptor Table (SSDT) function entries.

## Planned Enhancements

Expand Down
82 changes: 75 additions & 7 deletions driver/src/hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@
//! ensuring that any calls to check memory validity are routed through this custom implementation.
//! Credits to Matthias: https://github.com/not-matthias/amd_hypervisor/blob/main/driver/src/hook.rs
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]

use core::{
mem, ptr,
sync::atomic::{AtomicPtr, Ordering},
};
use wdk_sys::{
ACCESS_MASK, NTSTATUS, PHANDLE, PIO_STATUS_BLOCK, PLARGE_INTEGER, POBJECT_ATTRIBUTES, PVOID,
ULONG,
};

// Extern block for interfacing with LLVM intrinsic for getting the return address.
extern "C" {
Expand All @@ -18,10 +26,10 @@ extern "C" {

/// A global atomic pointer to hold the original `mm_is_address_valid` function.
/// It's initialized to a null mutable pointer and will be set during runtime to the actual function.
pub static ORIGINAL: AtomicPtr<u64> = AtomicPtr::new(ptr::null_mut());
pub static MM_IS_ADDRESS_VALID_ORIGINAL: AtomicPtr<u64> = AtomicPtr::new(ptr::null_mut());

/// The type of the `MmIsAddressValid` function.
type MmIsAddressValidType = extern "C" fn(u64) -> bool;
type MmIsAddressValidType = extern "C" fn(VirtualAddress: PVOID) -> bool;

/// A safe wrapper around the `MmIsAddressValid` function.
///
Expand All @@ -34,19 +42,79 @@ type MmIsAddressValidType = extern "C" fn(u64) -> bool;
/// ## Safety
/// This function assumes that the original `MmIsAddressValid` function is correctly set and points to a valid function.
/// The caller must ensure this is the case to avoid undefined behavior.
pub extern "C" fn mm_is_address_valid(ptr: u64) -> bool {
pub extern "C" fn mm_is_address_valid(virtual_address: u64) -> bool {
// Log the address from which `MmIsAddressValid` was called.
log::trace!("MmIsAddressValid called from {:#x}", unsafe {
return_address().read_volatile() // Reads the return address in a volatile manner to prevent optimizations.
});

log::debug!("First Parameter Value: {:x}", ptr);
log::debug!("First Parameter Value: {:x}", virtual_address);

// Load the original function pointer from the global atomic pointer.
let fn_ptr = ORIGINAL.load(Ordering::Relaxed); // Using relaxed ordering for atomic loading.
// Transmute the function pointer to the expected function type.
let fn_ptr = MM_IS_ADDRESS_VALID_ORIGINAL.load(Ordering::Relaxed); // Using relaxed ordering for atomic loading.

// Transmute the function pointer to the expected function type.
let fn_ptr = unsafe { mem::transmute::<_, MmIsAddressValidType>(fn_ptr) };

// Call the original `MmIsAddressValid` function with the provided pointer.
fn_ptr(ptr)
fn_ptr(virtual_address as _)
}

/// A global atomic pointer to hold the original `nt_create_file` function.
/// It's initialized to a null mutable pointer and will be set during runtime to the actual function.
pub static NT_CREATE_FILE_ORIGINAL: AtomicPtr<u64> = AtomicPtr::new(ptr::null_mut());

type NtCreateFileType = extern "C" fn(
FileHandle: PHANDLE,
DesiredAccess: ACCESS_MASK,
ObjectAttributes: POBJECT_ATTRIBUTES,
IoStatusBlock: PIO_STATUS_BLOCK,
AllocationSize: PLARGE_INTEGER,
FileAttributes: ULONG,
ShareAccess: ULONG,
CreateDisposition: ULONG,
CreateOptions: ULONG,
EaBuffer: PVOID,
EaLength: ULONG,
) -> NTSTATUS;

pub extern "C" fn nt_create_file(
file_handle: PHANDLE,
desired_access: ACCESS_MASK,
object_attributes: POBJECT_ATTRIBUTES,
io_status_block: PIO_STATUS_BLOCK,
allocation_size: PLARGE_INTEGER,
file_attributes: ULONG,
share_access: ULONG,
create_disposition: ULONG,
create_options: ULONG,
ea_buffer: PVOID,
ea_length: ULONG,
) -> NTSTATUS {
log::debug!("NtCreateFile called from {:#x}", unsafe {
return_address().read_volatile() // Reads the return address in a volatile manner to prevent optimizations.
});

log::debug!("First Parameter Value: {:x}", file_handle as u64);

// Load the original function pointer from the global atomic pointer.
let fn_ptr = NT_CREATE_FILE_ORIGINAL.load(Ordering::Relaxed); // Using relaxed ordering for atomic loading.

// Transmute the function pointer to the expected function type.
let fn_ptr = unsafe { mem::transmute::<_, NtCreateFileType>(fn_ptr) };

// Call the original `NtCreateFile` function with the provided pointer.
fn_ptr(
file_handle,
desired_access,
object_attributes,
io_status_block,
allocation_size,
file_attributes,
share_access,
create_disposition,
create_options,
ea_buffer,
ea_length,
)
}
36 changes: 29 additions & 7 deletions driver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use {
},
vmm::Hypervisor,
},
utils::{alloc::PhysicalAllocator, nt::update_ntoskrnl_cr3},
utils::{alloc::PhysicalAllocator, nt::update_ntoskrnl_cr3, ssdt::ssdt_hook::SsdtHook},
},
log::LevelFilter,
log::{self},
Expand Down Expand Up @@ -138,17 +138,39 @@ static mut HYPERVISOR: Option<Hypervisor> = None;
///
/// Credits: Jess / jessiep_
fn virtualize_system() -> Result<(), HypervisorError> {
// Initialize the hook and hook manager
// Example 1: Normal EPT Hook MmIsAddressValid
//
let hook = Hook::hook_function("MmIsAddressValid", hook::mm_is_address_valid as *const ())
.ok_or(HypervisorError::HookError)?;
if let HookType::Function { ref inline_hook } = hook.hook_type {
hook::ORIGINAL.store(inline_hook.trampoline_address(), Ordering::Relaxed);
//

let mm_is_address_valid =
Hook::hook_function("MmIsAddressValid", hook::mm_is_address_valid as *const ())
.ok_or(HypervisorError::HookError)?;

if let HookType::Function { ref inline_hook } = mm_is_address_valid.hook_type {
hook::MM_IS_ADDRESS_VALID_ORIGINAL
.store(inline_hook.trampoline_address(), Ordering::Relaxed);
}
let hook_manager = HookManager::new(vec![hook]);

// Example 2: Syscall EPT Hook NtCreateFile via SSDT Function Entry
//
//
let ssdt_nt_create_file_addy = SsdtHook::find_ssdt_function_address(0x0055, false)?;

let nt_create_file_syscall_hook = Hook::hook_function_ptr(
ssdt_nt_create_file_addy.function_address as _,
hook::nt_create_file as *const (),
)
.ok_or(HypervisorError::HookError)?;

if let HookType::Function { ref inline_hook } = nt_create_file_syscall_hook.hook_type {
hook::NT_CREATE_FILE_ORIGINAL.store(inline_hook.trampoline_address(), Ordering::Relaxed);
}

let hook_manager = HookManager::new(vec![mm_is_address_valid, nt_create_file_syscall_hook]);

let mut primary_ept: Box<Ept, PhysicalAllocator> =
unsafe { Box::try_new_zeroed_in(PhysicalAllocator)?.assume_init() };

let mut secondary_ept: Box<Ept, PhysicalAllocator> =
unsafe { Box::try_new_zeroed_in(PhysicalAllocator)?.assume_init() };

Expand Down
1 change: 1 addition & 0 deletions hypervisor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ log = "0.4.20" # https://crates.io/crates/log
kernel-log = "0.1.2" # https://crates.io/crates/kernel-log
com_logger = "0.1.1" # https://crates.io/crates/com_logger
iced-x86 = { version = "1.20.0", default-features = false, features = ["no_std", "decoder", "block_encoder", "instr_info", "no_d3now", "no_evex", "no_vex", "no_xop"] } # https://crates.io/crates/iced-x86
bstr = { version = "1.9.0", default-features = false}

[build-dependencies]
wdk-build = "0.1.0"
22 changes: 22 additions & 0 deletions hypervisor/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use alloc::ffi::NulError;
use thiserror_no_std::Error;

#[derive(Error, Debug)]
Expand Down Expand Up @@ -133,4 +134,25 @@ pub enum HypervisorError {

#[error("Hook manager not provided")]
HookManagerNotProvided,

#[error("NtQuerySystemInformation failed")]
NtQuerySystemInformationFailed,

#[error("ExAllocatePoolFailed failed")]
ExAllocatePoolFailed,

#[error("Pattern not found")]
PatternNotFound,

#[error("SSDT not found")]
SsdtNotFound,

#[error("Failed create a C String")]
FailedToCreateCString(#[from] NulError),

#[error("Failed to get kernel base")]
GetKernelBaseFailed,

#[error("Failed to parse hexadecimal string")]
HexParseError,
}
1 change: 1 addition & 0 deletions hypervisor/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ pub mod function_hook;
pub mod instructions;
pub mod nt;
pub mod processor;
pub mod ssdt;
3 changes: 3 additions & 0 deletions hypervisor/src/utils/ssdt/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod ssdt_find;
pub mod ssdt_hook;
pub mod sys_info;
126 changes: 126 additions & 0 deletions hypervisor/src/utils/ssdt/ssdt_find.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use crate::error::HypervisorError;
use crate::utils::ssdt::sys_info::Sysinfo;
use alloc::vec::Vec;

pub struct SsdtFind {
pub nt_table: *const u64,
pub win32k_table: *const u64,
}

impl SsdtFind {
pub fn find_ssdt() -> Result<Self, HypervisorError> {
let (kernel_base, kernel_size) = Self::get_kernel_base()?;
log::debug!("Kernel base address: {:p}", kernel_base);
log::debug!("Kernel size: {}", kernel_size);

/*
14042ba50 uint64_t KiSystemServiceStart(int64_t arg1, int64_t arg2, uint64_t arg3, int64_t arg4, int32_t arg5 @ rax, uint64_t arg6 @ rbx, int128_t* arg7 @ rbp, uint64_t arg8 @ ssp)
14042ba50 4889a390000000 mov qword [rbx+0x90], rsp {__return_addr}
14042ba57 8bf8 mov edi, eax
14042ba59 c1ef07 shr edi, 0x7
14042ba5c 83e720 and edi, 0x20
14042ba5f 25ff0f0000 and eax, 0xfff
14042ba64 4c8d15555e9d00 lea r10, [rel KeServiceDescriptorTable]
14042ba6b 4c8d1d8e368f00 lea r11, [rel KeServiceDescriptorTableShadow]
*/
let ki_service_system_start_pattern = "8B F8 C1 EF 07 83 E7 20 25 FF 0F 00 00";
let signature_size = 13;

// Read Windows Kernel (ntoskrnl.exe) from memory
let ntoskrnl_data =
unsafe { core::slice::from_raw_parts(kernel_base as *const u8, kernel_size as usize) };

// Find the KiServiceSystemStart signature
let offset = Self::pattern_scan(ntoskrnl_data, ki_service_system_start_pattern)?
.ok_or(HypervisorError::PatternNotFound)?;

// Calculate the address of KiServiceSystemStart using .add(),
// which is, `14042ba57 8bf8 mov edi, eax` in this case.
let ki_service_system_start = unsafe { kernel_base.add(offset) };
log::info!(
"KiServiceSystemStart address: {:p}",
ki_service_system_start
);

// Address of the 'lea r10, [rel KeServiceDescriptorTable]' instruction
let lea_r10_address = unsafe { ki_service_system_start.add(signature_size) };

// Address of the 'lea r11, [rel KeServiceDescriptorTableShadow]' instruction
let lea_r11_address = unsafe { lea_r10_address.add(7) }; // 7 bytes after lea r10

// Reading the 4-byte relative offset for KeServiceDescriptorTableShadow
let relative_offset = unsafe { *(lea_r11_address.add(3) as *const i32) }; // 3 bytes after the opcode

log::info!("Relative offset: {:x}", relative_offset);

// Compute the absolute address of KeServiceDescriptorTableShadow
let ke_service_descriptor_table_shadow =
unsafe { lea_r11_address.add(7).offset(relative_offset as isize) };

// Extracting nt!KiServiceTable and win32k!W32pServiceTable addresses
let shadow = ke_service_descriptor_table_shadow;

// NtTable Address of Nt Syscall Table
let nt_table = shadow as *const u64;

// Win32kTable Address of Win32k Syscall Table
let win32k_table = unsafe { shadow.offset(0x20) as *const u64 };

log::info!("NtTable address: {:p}", nt_table);
log::info!("Win32kTable address: {:p}", win32k_table);

Ok(Self {
nt_table,
win32k_table,
})
}

/// Gets the base address and size of the kernel module.
///
/// # Returns
///
/// A tuple with the base address and size of the kernel module.
pub fn get_kernel_base() -> Result<(*mut u8, u32), HypervisorError> {
let mut sys_info = Sysinfo::new()?;

let (kernel_base, kernel_size) = sys_info
.get_module_base("ntoskrnl.exe\0")
.ok_or(HypervisorError::GetKernelBaseFailed)?;

Ok((kernel_base as _, kernel_size))
}

/// Convert a combo pattern to bytes without wildcards
pub fn get_bytes_as_hex(pattern: &str) -> Result<Vec<Option<u8>>, HypervisorError> {
let mut pattern_bytes = Vec::new();

for x in pattern.split_whitespace() {
match x {
"?" => pattern_bytes.push(None),
_ => pattern_bytes.push(
u8::from_str_radix(x, 16)
.map(Some)
.map_err(|_| HypervisorError::HexParseError)?,
),
}
}

Ok(pattern_bytes)
}

/// Pattern or Signature scan a region of memory
pub fn pattern_scan(data: &[u8], pattern: &str) -> Result<Option<usize>, HypervisorError> {
let pattern_bytes = Self::get_bytes_as_hex(pattern)?;

let offset = data.windows(pattern_bytes.len()).position(|window| {
window
.iter()
.zip(&pattern_bytes)
.all(|(byte, pattern_byte)| pattern_byte.map_or(true, |b| *byte == b))
});

Ok(offset)
}
}
Loading

0 comments on commit 6bf11a3

Please sign in to comment.