### Initialize Primary and Secondary EPTs (Rust) Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index Initializes two separate EPT contexts, primary and secondary, with full READ_WRITE_EXECUTE permissions for 2MB pages. This setup mirrors guest memory, allowing the primary EPT to provide a clean view and the secondary EPT to host shadowed pages for hooks. ```rust primary_ept.identity_2mb(AccessType::READ_WRITE_EXECUTE)?; secondary_ept.identity_2mb(AccessType::READ_WRITE_EXECUTE)?; ``` -------------------------------- ### Map Guest Page to Shadow Page in Rust Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index Maps a guest page to a shadow page, allocating from a pre-allocated pool if one doesn't already exist. This process ensures that hooks are not redundantly installed and that a reliable shadow page is available for retrieval during VM-exits. It involves checking if the guest page has already been processed and uses the memory manager to perform the mapping. ```rust if !self.memory_manager.is_guest_page_processed(guest_page_pa.as_u64()) { self.memory_manager.map_guest_to_shadow_page( guest_page_pa.as_u64(), guest_function_va, guest_function_pa.as_u64(), ept_hook_type, function_hash, )?; } ``` -------------------------------- ### Setup CPUID EPT Hooks with Cache Information Check (Rust) Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index This Rust code snippet demonstrates how to set up an EPT hook for the CPUID instruction, specifically targeting the Cache Information leaf (0x2). It utilizes a flag `has_cpuid_cache_info_been_called` to ensure the hook is only applied once after SSDT initialization, preventing redundant VM-exits and hook setup. ```Rust match leaf { leaf if leaf == CpuidLeaf::CacheInformation as u32 => { trace!("CPUID leaf 0x2 detected (Cache Information)."); if !hook_manager.has_cpuid_cache_info_been_called { hook_manager.manage_kernel_ept_hook( vm, crate::windows::nt::pe::djb2_hash("NtCreateFile".as_bytes()), 0x0055, crate::intel::hooks::manager::EptHookType::Function( crate::intel::hooks::inline::InlineHookType::Vmcall ), true, )?; hook_manager.has_cpuid_cache_info_been_called = true; } } } ``` -------------------------------- ### Install Inline Hook with VMCALL in Rust Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index This Rust code snippet calculates the offset of a target function within a host shadow page and installs an inline hook using a VMCALL opcode. It relies on a `PAddr` type for physical addresses and an `InlineHook` struct with a `detour64` method. This approach keeps hook logic on the host side. ```rust let shadow_function_pa = PAddr::from(Self::calculate_function_offset_in_host_shadow_page(shadow_page_pa, guest_function_pa)); InlineHook::new(shadow_function_pa.as_u64() as *mut u8, inline_hook_type).detour64(); ``` -------------------------------- ### Build Identity Map (Rust) Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index The `build_identity()` function sets up an Extended Page Table (EPT) to create a 1:1 identity map between guest and host physical addresses. This mapping allows the hypervisor to control memory access at the page level without interfering with the guest's page tables. It uses 4KB and 2MB page mappings for memory management and compatibility. ```Rust /// Represents the entire Extended Page Table structure. /// /// EPT is a set of nested page tables similar to the standard x86-64 paging mechanism. /// It consists of 4 levels: PML4, PDPT, PD, and PT. /// /// Reference: IntelĀ® 64 and IA-32 Architectures Software Developer's Manual: 29.3.2 EPT Translation Mechanism #[repr(C, align(4096))] pub struct Ept { /// Page Map Level 4 (PML4) Table. pml4: Pml4, /// Page Directory Pointer Table (PDPT). pdpt: Pdpt, /// Array of Page Directory Table (PDT). pd: [Pd; 512], /// Page Table (PT). pt: Pt, } pub fn build_identity(&mut self) -> Result<(), HypervisorError> { let mut mtrr = Mtrr::new(); trace!("{mtrr:#x?}"); trace!("Initializing EPTs"); let mut pa = 0u64; self.pml4.0.entries[0].set_readable(true); self.pml4.0.entries[0].set_writable(true); self.pml4.0.entries[0].set_executable(true); self.pml4.0.entries[0].set_pfn(addr_of!(self.pdpt) as u64 >> BASE_PAGE_SHIFT); for (i, pdpte) in self.pdpt.0.entries.iter_mut().enumerate() { pdpte.set_readable(true); pdpte.set_writable(true); pdpte.set_executable(true); pdpte.set_pfn(addr_of!(self.pd[i]) as u64 >> BASE_PAGE_SHIFT); for pde in &mut self.pd[i].0.entries { if pa == 0 { pde.set_readable(true); pde.set_writable(true); pde.set_executable(true); pde.set_pfn(addr_of!(self.pt) as u64 >> BASE_PAGE_SHIFT); for pte in &mut self.pt.0.entries { let memory_type = mtrr .find(pa..pa + BASE_PAGE_SIZE as u64) .ok_or(HypervisorError::MemoryTypeResolutionError)?; pte.set_readable(true); pte.set_writable(true); pte.set_executable(true); pte.set_memory_type(memory_type as u64); pte.set_pfn(pa >> BASE_PAGE_SHIFT); pa += BASE_PAGE_SIZE as u64; } } else { let memory_type = mtrr .find(pa..pa + LARGE_PAGE_SIZE as u64) .ok_or(HypervisorError::MemoryTypeResolutionError)?; pde.set_readable(true); pde.set_writable(true); pde.set_executable(true); pde.set_memory_type(memory_type as u64); pde.set_large(true); pde.set_pfn(pa >> BASE_PAGE_SHIFT); pa += LARGE_PAGE_SIZE as u64; } } } Ok(()) } ``` -------------------------------- ### Copy Guest Page to Shadow Page in Rust Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index Clones the original 4KB guest page into its corresponding shadow page. This byte-for-byte copy allows for safe modifications to the code without affecting the original guest memory, thus evading detection mechanisms like PatchGuard and preserving the original code for potential restoration. It requires obtaining a pointer to the shadow page and then performing the copy. ```rust let shadow_page_pa = PAddr::from( self.memory_manager .get_shadow_page_as_ptr(guest_page_pa.as_u64()) .ok_or(HypervisorError::ShadowPageNotFound)?, ); Self::unsafe_copy_guest_to_shadow(guest_page_pa, shadow_page_pa); ``` -------------------------------- ### Creating Function Hooks with Shadow Copies and Trampolines (Rust) Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index This code illustrates the process of creating a function hook by copying the target function's page into a shadow region, calculating the offset, and inserting an INT3 breakpoint. It then constructs a trampoline to ensure a clean return to the original function's execution flow. This method avoids modifying guest memory directly. ```rust let original_pa = PhysicalAddress::from_va(function_ptr); let page = Self::copy_page(function_ptr)?; let page_va = page.as_ptr() as *mut u64 as u64; let page_pa = PhysicalAddress::from_va(page_va); let hook_va = Self::address_in_page(page_va, function_ptr); let hook_pa = PhysicalAddress::from_va(hook_va); let inline_hook = FunctionHook::new(function_ptr, hook_va, handler)?; ``` -------------------------------- ### Dual-EPT Remapping for Shadow Execution (Rust) Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index This snippet configures dual-EPT mappings to enable shadow execution. It splits the original 2MB page into 4KB entries, sets the primary EPT to read-write (no execute) and the secondary EPT to execute-only, remapping it to the shadow copy. This allows read/write operations to use the original mapping and instruction fetches to trigger execution from the shadow page. ```rust primary_ept.split_2mb_to_4kb(original_page, AccessType::READ_WRITE_EXECUTE)?; secondary_ept.split_2mb_to_4kb(original_page, AccessType::READ_WRITE_EXECUTE)?; primary_ept.change_page_flags(original_page, AccessType::READ_WRITE)?; secondary_ept.change_page_flags(original_page, AccessType::EXECUTE)?; secondary_ept.remap_page(original_page, hooked_copy_page, AccessType::EXECUTE)?; ``` -------------------------------- ### Determine Kernel Image Base Address and Size Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index This code segment calculates the virtual and physical base addresses, as well as the size, of the kernel image (`ntoskrnl.exe`). It first obtains the kernel's virtual base address by scanning backwards from a given address for the 'MZ' signature. Then, it translates this virtual address to a physical address using the guest's CR3 register and retrieves the image size from the PE optional header. These operations are marked as unsafe due to their critical nature and potential to cause system instability if incorrect values are processed. ```rust self.ntoskrnl_base_va = unsafe { get_image_base_address(guest_va)? }; self.ntoskrnl_base_pa = PhysicalAddress::pa_from_va_with_current_cr3(self.ntoskrnl_base_va)?; self.ntoskrnl_size = unsafe { get_size_of_image(self.ntoskrnl_base_pa as _).ok_or(HypervisorError::FailedToGetKernelSize)? } as u64; ``` -------------------------------- ### Handle VMCALL Hooks in Rust Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index Handles VMCALL instructions generated by inline hooks to detect function calls within the guest. It resolves guest physical pages, checks against shadow mappings, and prepares for introspection or state restoration. If no shadow mapping is found, it injects an undefined instruction exception. ```rust let exit_type = if let Some(shadow_page_pa) = hook_manager.memory_manager.get_shadow_page_as_ptr(guest_page_pa.as_u64()) { let pre_alloc_pt = hook_manager .memory_manager .get_page_table_as_mut(guest_large_page_pa.as_u64()) .ok_or(HypervisorError::PageTableNotFound)?; // ... (rest of the handling logic) } else { // Handle invalid VMCALL by injecting #UD exception // ... }; // Restore original page and permissions before single-stepping vm.primary_ept.swap_page(guest_page_pa.as_u64(), guest_page_pa.as_u64(), AccessType::READ_WRITE_EXECUTE, pre_alloc_pt)?; // Setup for single-stepping with MTF let instruction_count = HookManager::calculate_instruction_count(...); vm.mtf_counter = Some(instruction_count); set_monitor_trap_flag(true); update_guest_interrupt_flag(vm, false)?; ``` -------------------------------- ### Return to Original Guest Function via Trampoline (Rust) Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index This code demonstrates returning execution to the original guest function using a trampoline. It retrieves the preserved entry point from a global atomic variable, safely casts it to the correct function signature, and invokes it. This ensures the guest continues execution as if uninterrupted. ```Rust let fn_ptr = MM_IS_ADDRESS_VALID_ORIGINAL.load(Ordering::Relaxed); let fn_ptr = unsafe { mem::transmute::<_, MmIsAddressValidType>(fn_ptr) }; fn_ptr(virtual_address as _) ``` ```Rust let fn_ptr = NT_CREATE_FILE_ORIGINAL.load(Ordering::Relaxed); let fn_ptr = unsafe { mem::transmute::<_, NtCreateFileType>(fn_ptr) }; fn_ptr(...) ``` -------------------------------- ### Retrieve Shadow Page Mapping (Rust) Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index Retrieves the corresponding shadow page for a given guest page address. This shadow page contains modified code with a detour, such as a VMCALL. Failure to find a shadow page is treated as a fatal error, indicating unexpected guest behavior. ```rust let shadow_page_pa = PAddr::from( hook_manager .memory_manager .get_shadow_page_as_ptr(guest_page_pa.as_u64()) .ok_or(HypervisorError::ShadowPageNotFound)? ); ``` -------------------------------- ### Identify Faulting Guest Page Address (Rust) Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index Reads the faulting Guest Physical Address (GPA) from the VMCS and aligns it to the 4KB and 2MB page boundaries to identify the specific page accessed. This is a crucial first step in handling EPT violations. ```rust let guest_pa = vmread(vmcs::ro::GUEST_PHYSICAL_ADDR_FULL); let guest_page_pa = PAddr::from(guest_pa).align_down_to_base_page(); let guest_large_page_pa = guest_page_pa.align_down_to_large_page(); ``` -------------------------------- ### Redirect Execution with Shadow Page (Rust) Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index If an EPT violation occurs due to an attempt to execute a non-executable page, this code swaps in the prepared shadow page and marks it as execute-only. This redirects guest execution to the modified memory containing the hook, allowing the hypervisor to gain control. ```rust if ept_violation_qualification.readable && ept_violation_qualification.writable && !ept_violation_qualification.executable { vm.primary_ept.swap_page(guest_page_pa.as_u64(), shadow_page_pa.as_u64(), AccessType::EXECUTE, pre_alloc_pt)?; } ``` -------------------------------- ### VMCS Configuration for Breakpoint VM-Exits (Rust) Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index This code configures the VMCS to trap INT3 instructions by setting the EXCEPTION_BITMAP. It also loads the primary EPT pointer into the EPTP_FULL field, establishing the initial read/write view of guest memory before any EPT violations occur. ```rust vmwrite(vmcs::control::EXCEPTION_BITMAP, 1u64 << (ExceptionInterrupt::Breakpoint as u32)); vmwrite(vmcs::control::EPTP_FULL, shared_data.primary_eptp); ``` -------------------------------- ### Map Large Page to Page Table in Rust Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index Maps a 2MB large page to a pre-allocated page table within the hypervisor's memory management structures. This establishes an association between Guest Physical Addresses (GPA) and Host Physical Addresses (HPA), enabling controlled manipulation and permission enforcement. It relies on the hypervisor's memory manager and assumes a pre-allocated page table pool. ```rust self.memory_manager.map_large_page_to_pt(guest_large_page_pa.as_u64())?; ``` -------------------------------- ### Hooking Kernel Functions with Trampolines (Rust) Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index This snippet demonstrates how to hook kernel functions using both name resolution from the export table and raw function pointers obtained from the SSDT. It sets up trampolines to preserve the original function prologue and stores the trampoline address for later use. Dependencies include the 'hook' and 'SsdtHook' modules. ```rust 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 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]); ``` -------------------------------- ### Intercept IA32_LSTAR MSR Write Access During Initialization Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index This snippet demonstrates how to configure MSR interception for write access to the IA32_LSTAR MSR during hypervisor initialization. It uses a hook manager to modify the MSR bitmap, ensuring that subsequent writes to this MSR trigger a VM-exit. This is a crucial step for dynamically determining kernel information. ```rust trace!("Modifying MSR interception for LSTAR MSR write access"); hook_manager .msr_bitmap .modify_msr_interception(msr::IA32_LSTAR, MsrAccessType::Write, MsrOperation::Hook); ``` -------------------------------- ### Handle WRMSR to IA32_LSTAR: Unhook and Resolve Kernel Base Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index This function handles VM-exits triggered by writes to the IA32_LSTAR MSR. Upon detecting such an event, it first unhooks the MSR to prevent further interruptions and then calls `set_kernel_base_and_size` to resolve the kernel's virtual and physical base addresses and its size. This process relies on the MSR value provided during the write operation. ```rust if msr_id == msr::IA32_LSTAR { trace!("IA32_LSTAR write attempted with MSR value: {:#x}", msr_value); hook_manager.msr_bitmap.modify_msr_interception( msr::IA32_LSTAR, MsrAccessType::Write, MsrOperation::Unhook, ); hook_manager.set_kernel_base_and_size(msr_value)?; } ``` -------------------------------- ### Single-Step Displaced Instructions with MTF in Rust Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index Manages the single-stepping process using the Monitor Trap Flag (MTF) after a VMCALL hook. It decrements a replay counter for each instruction executed, restores the shadow page, and re-enables guest interrupts upon completion. ```rust *counter = counter.saturating_sub(1); // ... (logic after counter reaches zero) // Reapply the hook and restore execute-only permissions vm.primary_ept.swap_page(guest_pa.align_down_to_base_page().as_u64(), shadow_page_pa.as_u64(), AccessType::EXECUTE, pre_alloc_pt)?; // Disable MTF and re-enable guest interrupts // set_monitor_trap_flag(false) is implicitly handled by not calling set_monitor_trap_flag(true) restore_guest_interrupt_flag(vm)?; ``` -------------------------------- ### Redirect Execution via Breakpoint Handlers (Rust) Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index This code handles breakpoint exceptions triggered by the INT3 instruction. It resolves the guest's RIP, checks for registered hooks, and redirects RIP to a hook handler if a match is found. This allows for inspection of arguments, logging, or introspection of guest memory before returning to the original function via a trampoline. ```Rust if let Some(Some(handler)) = hook_manager.find_hook_by_address(guest_registers.rip).map(|hook| hook.handler_address()) { guest_registers.rip = handler; vmwrite(vmcs::guest::RIP, guest_registers.rip); } ``` -------------------------------- ### Manage Kernel EPT Hook Installation/Removal (Rust) Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index This Rust function, `manage_kernel_ept_hook`, handles the enabling or disabling of EPT hooks on target kernel functions. It resolves the function's virtual address by first attempting to use `get_export_by_hash` and falling back to the SSDT if necessary. It then calls `ept_hook_function` or `ept_unhook_function` based on the `enable` flag. ```Rust pub fn manage_kernel_ept_hook( &mut self, vm: &mut Vm, function_hash: u32, syscall_number: u16, ept_hook_type: EptHookType, enable: bool, ) -> Result<(), HypervisorError> { let action = if enable { "Enabling" } else { "Disabling" }; debug!("{} EPT hook for function: {:#x}", action, function_hash); trace!("Ntoskrnl base VA: {:#x}", self.ntoskrnl_base_va); trace!("Ntoskrnl base PA: {:#x}", self.ntoskrnl_base_pa); trace!("Ntoskrnl size: {:#x}", self.ntoskrnl_size); let function_va = unsafe { if let Some(va) = get_export_by_hash(self.ntoskrnl_base_pa as _, self.ntoskrnl_base_va as _, function_hash) { va } else { let ssdt_function_address = SsdtHook::find_ssdt_function_address(syscall_number as _, false, self.ntoskrnl_base_pa as _, self.ntoskrnl_size as _); match ssdt_function_address { Ok(ssdt_hook) => ssdt_hook.guest_function_va as *mut u8, Err(_) => return Err(HypervisorError::FailedToGetExport), } } }; if enable { self.ept_hook_function(vm, function_va as _, function_hash, ept_hook_type)?; } else { self.ept_unhook_function(vm, function_va as _, ept_hook_type)?; } Ok(()) } ``` -------------------------------- ### Split 2MB Large Page to 4KB Pages in Rust Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index Splits a 2MB large page into 512 individual 4KB pages if the target function resides within it. This is crucial for applying granular permissions and isolating hooks, preventing unintended side effects on unrelated code. It requires checking if the page is a large page and obtaining a mutable reference to a pre-allocated page table. ```rust if vm.primary_ept.is_large_page(guest_page_pa.as_u64()) { let pre_alloc_pt = self .memory_manager .get_page_table_as_mut(guest_large_page_pa.as_u64()) .ok_or(HypervisorError::PageTableNotFound)?; vm.primary_ept.split_2mb_to_4kb(guest_large_page_pa.as_u64(), pre_alloc_pt)?; } ``` -------------------------------- ### Handle EPT Violations with Dynamic EPTP Switching (Rust) Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index This code handles EPT violations by dynamically switching between primary and secondary EPTPs. It detects execution violations and switches to a secondary EPTP for detour execution, then switches back to the primary EPTP for read/write operations. This implementation does not support mixed access patterns like RWX or RX within the same page. ```Rust let guest_physical_address = vmread(vmcs::ro::GUEST_PHYSICAL_ADDR_FULL); let exit_qualification_value = vmread(vmcs::ro::EXIT_QUALIFICATION); let ept_violation_qualification = EptViolationExitQualification::from_exit_qualification(exit_qualification_value); if ept_violation_qualification.readable && ept_violation_qualification.writable && !ept_violation_qualification.executable { let secondary_eptp = unsafe { vmx.shared_data.as_mut().secondary_eptp }; vmwrite(vmcs::control::EPTP_FULL, secondary_eptp); } if !ept_violation_qualification.readable && !ept_violation_qualification.writable && ept_violation_qualification.executable { let primary_eptp = unsafe { vmx.shared_data.as_mut().primary_eptp }; vmwrite(vmcs::control::EPTP_FULL, primary_eptp); } ``` -------------------------------- ### Handle EPT Read/Write Violations in Rust Source: https://secret.club/2025/06/02/hypervisors-for-memory-introspection-and-reverse-engineering.html/index Handles EPT violations caused by read/write attempts on execute-only pages. It temporarily restores full access, executes the instruction, and then re-applies the hook, ensuring stability and stealth. This prevents VM-exit loops and system crashes. ```rust if ept_violation_qualification.executable && !ept_violation_qualification.readable && !ept_violation_qualification.writable { vm.primary_ept.swap_page(guest_page_pa.as_u64(), guest_page_pa.as_u64(), AccessType::READ_WRITE_EXECUTE, pre_alloc_pt)?; vm.mtf_counter = Some(1); set_monitor_trap_flag(true); update_guest_interrupt_flag(vm, false)?; } ```