diff --git a/src/driver/assembly.asm b/src/driver/assembly.asm index 872e1a8..9ca03e3 100644 --- a/src/driver/assembly.asm +++ b/src/driver/assembly.asm @@ -23,6 +23,13 @@ LEAF_ENTRY __lgdt, _TEXT$00 ret LEAF_END __lgdt, _TEXT$00 +; ----------------------------------------------------- + +LEAF_ENTRY __invept, _TEXT$00 + invept rcx, OWORD PTR [rdx] + ret +LEAF_END __invept, _TEXT$00 + ; ----------------------------------------------------- LEAF_ENTRY restore_context, _TEXT$00 diff --git a/src/driver/assembly.hpp b/src/driver/assembly.hpp index 34a1f95..878ada6 100644 --- a/src/driver/assembly.hpp +++ b/src/driver/assembly.hpp @@ -10,6 +10,8 @@ void __lgdt(void* gdtr); void _sgdt(void*); +void __invept(size_t type, invept_descriptor* descriptor); + [[ noreturn ]] void vm_launch(); [[ noreturn ]] void vm_exit(); [[ noreturn ]] void restore_context(CONTEXT* context); diff --git a/src/driver/driver_main.cpp b/src/driver/driver_main.cpp index be23467..59ae866 100644 --- a/src/driver/driver_main.cpp +++ b/src/driver/driver_main.cpp @@ -4,10 +4,159 @@ #include "irp.hpp" #include "exception.hpp" #include "hypervisor.hpp" +#include "memory.hpp" +#include "process.hpp" #define DOS_DEV_NAME L"\\DosDevices\\HelloDev" #define DEV_NAME L"\\Device\\HelloDev" +namespace +{ + void log_current_process_name() + { + const auto process = process::get_current_process(); + const auto* name = process.get_image_filename(); + + if (name) + { + debug_log("Denied for process: %s\n", name); + } + } + + NTSTATUS (*NtCreateFileOrig)( + PHANDLE FileHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + PIO_STATUS_BLOCK IoStatusBlock, + PLARGE_INTEGER AllocationSize, + ULONG FileAttributes, + ULONG ShareAccess, + ULONG CreateDisposition, + ULONG CreateOptions, + PVOID EaBuffer, + ULONG EaLength + ); + + NTSTATUS NtCreateFileHook( + PHANDLE FileHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + PIO_STATUS_BLOCK IoStatusBlock, + PLARGE_INTEGER AllocationSize, + ULONG FileAttributes, + ULONG ShareAccess, + ULONG CreateDisposition, + ULONG CreateOptions, + PVOID EaBuffer, + ULONG EaLength + ) + { + static WCHAR BlockedFileName[] = L"test.txt"; + static SIZE_T BlockedFileNameLength = (sizeof(BlockedFileName) / sizeof(BlockedFileName[0])) - 1; + + PWCH NameBuffer; + USHORT NameLength; + + __try + { + ProbeForRead(ObjectAttributes, sizeof(OBJECT_ATTRIBUTES), 1); + ProbeForRead(ObjectAttributes->ObjectName, sizeof(UNICODE_STRING), 1); + + NameBuffer = ObjectAttributes->ObjectName->Buffer; + NameLength = ObjectAttributes->ObjectName->Length; + + ProbeForRead(NameBuffer, NameLength, 1); + + /* Convert to length in WCHARs */ + NameLength /= sizeof(WCHAR); + + /* Does the file path (ignoring case and null terminator) end with our blocked file name? */ + if (NameLength >= BlockedFileNameLength && + _wcsnicmp(&NameBuffer[NameLength - BlockedFileNameLength], BlockedFileName, + BlockedFileNameLength) == 0) + { + log_current_process_name(); + return STATUS_ACCESS_DENIED; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + NOTHING; + } + + return NtCreateFileOrig(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, AllocationSize, + FileAttributes, + ShareAccess, CreateDisposition, CreateOptions, EaBuffer, EaLength); + } + + VOID HvEptHookWriteAbsoluteJump(uint8_t* TargetBuffer, SIZE_T TargetAddress) + { + /** + * Use 'push ret' instead of 'jmp qword[rip+0]', + * Because 'jmp qword[rip+0]' will read hooked page 8bytes. + * + * 14 bytes hook: + * 0x68 0x12345678 ......................push 'low 32bit of TargetAddress' + * 0xC7 0x44 0x24 0x04 0x12345678........mov dword[rsp + 4], 'high 32bit of TargetAddress' + * 0xC3..................................ret + */ + + UINT32 Low32; + UINT32 High32; + + Low32 = (UINT32)TargetAddress; + High32 = (UINT32)(TargetAddress >> 32); + + /* push 'low 32bit of TargetAddress' */ + TargetBuffer[0] = 0x68; + *((UINT32*)&TargetBuffer[1]) = Low32; + + /* mov dword[rsp + 4], 'high 32bit of TargetAddress' */ + *((UINT32*)&TargetBuffer[5]) = 0x042444C7; + *((UINT32*)&TargetBuffer[9]) = High32; + + /* ret */ + TargetBuffer[13] = 0xC3; + } + + void* HookCreateFile(hypervisor& hypervisor) + { + const uint8_t fixup[] = { + 0x48, 0x81, 0xEC, 0x88, 0x00, 0x00, 0x00, 0x33, 0xC0, 0x48, 0x89, 0x44, 0x24, 0x78 + }; + + auto* target = reinterpret_cast(&NtCreateFile); + if (memcmp(target, fixup, sizeof(fixup))) + { + debug_log("Fixup is invalid\n"); + return nullptr; + } + + auto* trampoline = static_cast(memory::allocate_non_paged_memory(sizeof(fixup) + 14)); + if (!trampoline) + { + debug_log("Failed to allocate trampoline\n"); + return nullptr; + } + + memcpy(trampoline, fixup, sizeof(fixup)); + + + HvEptHookWriteAbsoluteJump(trampoline + sizeof(fixup), + size_t(target) + sizeof(fixup)); + + /* Let the hook function call the original function */ + NtCreateFileOrig = reinterpret_cast(trampoline); + + /* Write the absolute jump to our shadow page memory to jump to our hook. */ + uint8_t hook[14]; + HvEptHookWriteAbsoluteJump(hook, reinterpret_cast(NtCreateFileHook)); + + hypervisor.install_ept_hook(target, hook, sizeof(hook)); + return trampoline; + } +} + class global_driver { public: @@ -19,11 +168,14 @@ public: , irp_(driver_object, DEV_NAME, DOS_DEV_NAME) { debug_log("Driver started\n"); + this->trampoline = HookCreateFile(this->hypervisor_); } ~global_driver() { debug_log("Unloading driver\n"); + this->hypervisor_.disable_all_ept_hooks(); + memory::free_non_paged_memory(this->trampoline); } global_driver(global_driver&&) noexcept = delete; @@ -41,6 +193,7 @@ private: hypervisor hypervisor_{}; sleep_callback sleep_callback_{}; irp irp_{}; + void* trampoline{nullptr}; void sleep_notification(const sleep_callback::type type) { diff --git a/src/driver/ept.cpp b/src/driver/ept.cpp index 689632d..e7c76e9 100644 --- a/src/driver/ept.cpp +++ b/src/driver/ept.cpp @@ -1,7 +1,7 @@ #include "std_include.hpp" #include "ept.hpp" -#include "logging.hpp" +#include "assembly.hpp" #include "memory.hpp" #include "vmx.hpp" @@ -104,151 +104,6 @@ namespace vmx return candidate_memory_type; } - - NTSTATUS (*NtCreateFileOrig)( - PHANDLE FileHandle, - ACCESS_MASK DesiredAccess, - POBJECT_ATTRIBUTES ObjectAttributes, - PIO_STATUS_BLOCK IoStatusBlock, - PLARGE_INTEGER AllocationSize, - ULONG FileAttributes, - ULONG ShareAccess, - ULONG CreateDisposition, - ULONG CreateOptions, - PVOID EaBuffer, - ULONG EaLength - ); - - NTSTATUS NtCreateFileHook( - PHANDLE FileHandle, - ACCESS_MASK DesiredAccess, - POBJECT_ATTRIBUTES ObjectAttributes, - PIO_STATUS_BLOCK IoStatusBlock, - PLARGE_INTEGER AllocationSize, - ULONG FileAttributes, - ULONG ShareAccess, - ULONG CreateDisposition, - ULONG CreateOptions, - PVOID EaBuffer, - ULONG EaLength - ) - { - static WCHAR BlockedFileName[] = L"test.txt"; - static SIZE_T BlockedFileNameLength = (sizeof(BlockedFileName) / sizeof(BlockedFileName[0])) - 1; - - PWCH NameBuffer; - USHORT NameLength; - - __try - { - ProbeForRead(ObjectAttributes, sizeof(OBJECT_ATTRIBUTES), 1); - ProbeForRead(ObjectAttributes->ObjectName, sizeof(UNICODE_STRING), 1); - - NameBuffer = ObjectAttributes->ObjectName->Buffer; - NameLength = ObjectAttributes->ObjectName->Length; - - ProbeForRead(NameBuffer, NameLength, 1); - - /* Convert to length in WCHARs */ - NameLength /= sizeof(WCHAR); - - /* Does the file path (ignoring case and null terminator) end with our blocked file name? */ - if (NameLength >= BlockedFileNameLength && - _wcsnicmp(&NameBuffer[NameLength - BlockedFileNameLength], BlockedFileName, - BlockedFileNameLength) == 0) - { - return STATUS_ACCESS_DENIED; - } - } - __except (EXCEPTION_EXECUTE_HANDLER) - { - NOTHING; - } - - return NtCreateFileOrig(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, AllocationSize, - FileAttributes, - ShareAccess, CreateDisposition, CreateOptions, EaBuffer, EaLength); - } - - VOID HvEptHookWriteAbsoluteJump(uint8_t* TargetBuffer, SIZE_T TargetAddress) - { - /** - * Use 'push ret' instead of 'jmp qword[rip+0]', - * Because 'jmp qword[rip+0]' will read hooked page 8bytes. - * - * 14 bytes hook: - * 0x68 0x12345678 ......................push 'low 32bit of TargetAddress' - * 0xC7 0x44 0x24 0x04 0x12345678........mov dword[rsp + 4], 'high 32bit of TargetAddress' - * 0xC3..................................ret - */ - - UINT32 Low32; - UINT32 High32; - - Low32 = (UINT32)TargetAddress; - High32 = (UINT32)(TargetAddress >> 32); - - /* push 'low 32bit of TargetAddress' */ - TargetBuffer[0] = 0x68; - *((UINT32*)&TargetBuffer[1]) = Low32; - - /* mov dword[rsp + 4], 'high 32bit of TargetAddress' */ - *((UINT32*)&TargetBuffer[5]) = 0x042444C7; - *((UINT32*)&TargetBuffer[9]) = High32; - - /* ret */ - TargetBuffer[13] = 0xC3; - } - - bool HvEptHookInstructionMemory(ept_hook* Hook, PVOID TargetFunction, PVOID HookFunction, - PVOID* OrigFunction) - { - SIZE_T OffsetIntoPage; - - OffsetIntoPage = ADDRMASK_EPT_PML1_OFFSET((SIZE_T)TargetFunction); - - if ((OffsetIntoPage + 14) > PAGE_SIZE - 1) - { - debug_log( - "Function extends past a page boundary. We just don't have the technology to solve this.....\n"); - return FALSE; - } - - const uint8_t fixup[] = { - 0x48, 0x81, 0xEC, 0x88, 0x00, 0x00, 0x00, 0x33, 0xC0, 0x48, 0x89, 0x44, 0x24, 0x78 - }; - - //HvUtilLogDebug("Number of bytes of instruction mem: %d\n", SizeOfHookedInstructions); - - /* Build a trampoline */ - - /* Allocate some executable memory for the trampoline */ - Hook->trampoline = (uint8_t*)memory::allocate_non_paged_memory(sizeof(fixup) + 14); - - if (!Hook->trampoline) - { - debug_log("Could not allocate trampoline function buffer.\n"); - return FALSE; - } - - /* Copy the trampoline instructions in. */ - RtlCopyMemory(Hook->trampoline, TargetFunction, sizeof(fixup)); - - /* Add the absolute jump back to the original function. */ - HvEptHookWriteAbsoluteJump((&Hook->trampoline[sizeof(fixup)]), - (SIZE_T)TargetFunction + sizeof(fixup)); - - debug_log("Trampoline: 0x%llx\n", Hook->trampoline); - debug_log("HookFunction: 0x%llx\n", HookFunction); - - /* Let the hook function call the original function */ - *OrigFunction = Hook->trampoline; - - /* Write the absolute jump to our shadow page memory to jump to our hook. */ - HvEptHookWriteAbsoluteJump(&Hook->fake_page[OffsetIntoPage], (SIZE_T)HookFunction); - - return TRUE; - } } ept::ept() @@ -270,100 +125,47 @@ namespace vmx { auto* current_hook = hook; hook = hook->next_hook; - memory::free_non_paged_memory(current_hook->trampoline); memory::free_aligned_object(current_hook); } } - void ept::install_hook(PVOID TargetFunction, PVOID HookFunction, PVOID* OrigFunction) + void ept::install_page_hook(void* destination, const void* source, const size_t length) { - const auto VirtualTarget = PAGE_ALIGN(TargetFunction); - const auto PhysicalAddress = memory::get_physical_address(VirtualTarget); + auto* hook = this->get_or_create_ept_hook(destination); - if (!PhysicalAddress) + const auto page_offset = ADDRMASK_EPT_PML1_OFFSET(reinterpret_cast(destination)); + memcpy(hook->fake_page + page_offset, source, length); + } + + void ept::install_hook(void* destination, const void* source, const size_t length) + { + auto current_destination = reinterpret_cast(destination); + auto current_source = reinterpret_cast(source); + auto current_length = length; + + while (current_length != 0) { - debug_log("HvEptAddPageHook: Target address could not be mapped to physical memory!\n"); - return; + const auto page_offset = ADDRMASK_EPT_PML1_OFFSET(current_destination); + const auto page_remaining = PAGE_SIZE - page_offset; + const auto data_to_write = min(page_remaining, current_length); + + this->install_page_hook(reinterpret_cast(current_destination), + reinterpret_cast(current_source), data_to_write); + + current_length -= data_to_write; + current_destination += data_to_write; + current_source += data_to_write; } + } - /* Create a hook object*/ - auto* NewHook = this->allocate_ept_hook(); - - if (!NewHook) + void ept::disable_all_hooks() const + { + auto* hook = this->ept_hooks; + while (hook) { - debug_log("HvEptAddPageHook: Could not allocate memory for new hook.\n"); - return; + hook->target_page->flags = hook->original_entry.flags; + hook = hook->next_hook; } - - /* - * Ensure the page is split into 512 4096 byte page entries. We can only hook a 4096 byte page, not a 2MB page. - * This is due to performance hit we would get from hooking a 2MB page. - */ - this->split_large_page(PhysicalAddress); - RtlCopyMemory(&NewHook->fake_page[0], VirtualTarget, PAGE_SIZE); - - /* Base address of the 4096 page. */ - NewHook->physical_base_address = (SIZE_T)PAGE_ALIGN(PhysicalAddress); - - /* Pointer to the page entry in the page table. */ - NewHook->target_page = this->get_pml1_entry(PhysicalAddress); - - /* Ensure the target is valid. */ - if (!NewHook->target_page) - { - debug_log("HvEptAddPageHook: Failed to get PML1 entry for target address.\n"); - return; - } - - /* Save the original permissions of the page */ - NewHook->original_entry = *NewHook->target_page; - auto OriginalEntry = *NewHook->target_page; - - /* Setup the new fake page table entry */ - pml1 FakeEntry{}; - FakeEntry.flags = 0; - - /* We want this page to raise an EPT violation on RW so we can handle by swapping in the original page. */ - FakeEntry.read_access = 0; - FakeEntry.write_access = 0; - FakeEntry.execute_access = 1; - - /* Point to our fake page we just made */ - FakeEntry.page_frame_number = memory::get_physical_address(&NewHook->fake_page) / PAGE_SIZE; - - /* Save a copy of the fake entry. */ - NewHook->shadow_entry.flags = FakeEntry.flags; - - /* - * Lastly, mark the entry in the table as no execute. This will cause the next time that an instruction is - * fetched from this page to cause an EPT violation exit. This will allow us to swap in the fake page with our - * hook. - */ - OriginalEntry.read_access = 1; - OriginalEntry.write_access = 1; - OriginalEntry.execute_access = 0; - - /* The hooked entry will be swapped in first. */ - NewHook->hooked_entry.flags = OriginalEntry.flags; - - if (!HvEptHookInstructionMemory(NewHook, TargetFunction, HookFunction, OrigFunction)) - { - debug_log("HvEptAddPageHook: Could not build hook.\n"); - return; - } - - /* Apply the hook to EPT */ - NewHook->target_page->flags = OriginalEntry.flags; - - /* - * Invalidate the entry in the TLB caches so it will not conflict with the actual paging structure. - */ - /*if (ProcessorContext->HasLaunched) - { - Descriptor.EptPointer = ProcessorContext->EptPointer.Flags; - Descriptor.Reserved = 0; - __invept(1, &Descriptor); - }*/ } void ept::handle_violation(guest_context& guest_context) const @@ -394,18 +196,24 @@ namespace vmx if (!violation_qualification.ept_executable && violation_qualification.execute_access) { - hook->target_page->flags = hook->shadow_entry.flags; + hook->target_page->flags = hook->execute_entry.flags; guest_context.increment_rip = false; } if (violation_qualification.ept_executable && (violation_qualification.read_access || violation_qualification. write_access)) { - hook->target_page->flags = hook->hooked_entry.flags; + hook->target_page->flags = hook->readwrite_entry.flags; guest_context.increment_rip = false; } } + void ept::handle_misconfiguration(guest_context& guest_context) const + { + guest_context.increment_rip = false; + guest_context.exit_vm = true; + } + void ept::initialize() { mtrr_list mtrr_data{}; @@ -452,18 +260,30 @@ namespace vmx mtrr_data, this->epde[i][j].page_frame_number * 2_mb, MEMORY_TYPE_WRITE_BACK); } } - - this->install_hook((PVOID)NtCreateFile, (PVOID)NtCreateFileHook, (PVOID*)&NtCreateFileOrig); } - ept_pml4* ept::get_pml4() + ept_pointer ept::get_ept_pointer() const { - return this->epml4; + const auto ept_pml4_physical_address = memory::get_physical_address(const_cast(&this->epml4[0])); + + ept_pointer vmx_eptp{}; + vmx_eptp.flags = 0; + vmx_eptp.page_walk_length = 3; + vmx_eptp.memory_type = MEMORY_TYPE_WRITE_BACK; + vmx_eptp.page_frame_number = ept_pml4_physical_address / PAGE_SIZE; + + return vmx_eptp; } - const ept_pml4* ept::get_pml4() const + void ept::invalidate() const { - return this->epml4; + const auto ept_pointer = this->get_ept_pointer(); + + invept_descriptor descriptor{}; + descriptor.ept_pointer = ept_pointer.flags; + descriptor.reserved = 0; + + __invept(1, &descriptor); } pml2* ept::get_pml2_entry(const uint64_t physical_address) @@ -547,6 +367,80 @@ namespace vmx return hook; } + ept_hook* ept::find_ept_hook(const uint64_t physical_address) const + { + auto* hook = this->ept_hooks; + while (hook) + { + if (hook->physical_base_address == physical_address) + { + return hook; + } + + hook = hook->next_hook; + } + + return nullptr; + } + + ept_hook* ept::get_or_create_ept_hook(void* destination) + { + const auto virtual_target = PAGE_ALIGN(destination); + const auto physical_address = memory::get_physical_address(virtual_target); + + if (!physical_address) + { + throw std::runtime_error("No physical address for destination"); + } + + const auto physical_base_address = reinterpret_cast(PAGE_ALIGN(physical_address)); + auto* hook = this->find_ept_hook(physical_base_address); + if (hook) + { + if (hook->target_page->flags == hook->original_entry.flags) + { + hook->target_page->flags = hook->readwrite_entry.flags; + } + + return hook; + } + + hook = this->allocate_ept_hook(); + + if (!hook) + { + throw std::runtime_error("Failed to allocate hook"); + } + + this->split_large_page(physical_address); + + memcpy(&hook->fake_page[0], virtual_target, PAGE_SIZE); + hook->physical_base_address = physical_base_address; + + hook->target_page = this->get_pml1_entry(physical_address); + if (!hook->target_page) + { + throw std::runtime_error("Failed to get PML1 entry for target address"); + } + + hook->original_entry = *hook->target_page; + hook->readwrite_entry = hook->original_entry; + + hook->readwrite_entry.read_access = 1; + hook->readwrite_entry.write_access = 1; + hook->readwrite_entry.execute_access = 0; + + hook->execute_entry.flags = 0; + hook->execute_entry.read_access = 0; + hook->execute_entry.write_access = 0; + hook->execute_entry.execute_access = 1; + hook->execute_entry.page_frame_number = memory::get_physical_address(&hook->fake_page) / PAGE_SIZE; + + hook->target_page->flags = hook->readwrite_entry.flags; + + return hook; + } + void ept::split_large_page(const uint64_t physical_address) { auto* target_entry = this->get_pml2_entry(physical_address); diff --git a/src/driver/ept.hpp b/src/driver/ept.hpp index c130a15..3e91c6f 100644 --- a/src/driver/ept.hpp +++ b/src/driver/ept.hpp @@ -32,10 +32,9 @@ namespace vmx pml1* target_page{}; pml1 original_entry{}; - pml1 shadow_entry{}; - pml1 hooked_entry{}; + pml1 execute_entry{}; + pml1 readwrite_entry{}; - uint8_t* trampoline{nullptr}; ept_hook* next_hook{nullptr}; }; @@ -54,11 +53,14 @@ namespace vmx void initialize(); - void install_hook(PVOID TargetFunction, PVOID HookFunction, PVOID* OrigFunction); - void handle_violation(guest_context& guest_context) const; + void install_hook(void* destination, const void* source, size_t length); + void disable_all_hooks() const; - pml4* get_pml4(); - const pml4* get_pml4() const; + void handle_violation(guest_context& guest_context) const; + void handle_misconfiguration(guest_context& guest_context) const; + + ept_pointer get_ept_pointer() const; + void invalidate() const; private: DECLSPEC_PAGE_ALIGN pml4 epml4[EPT_PML4E_ENTRY_COUNT]{}; @@ -74,7 +76,12 @@ namespace vmx ept_split* allocate_ept_split(); ept_hook* allocate_ept_hook(); + ept_hook* find_ept_hook(uint64_t physical_address) const; + + ept_hook* get_or_create_ept_hook(void* destination); void split_large_page(uint64_t physical_address); + + void install_page_hook(void* destination, const void* source, size_t length); }; } diff --git a/src/driver/hypervisor.cpp b/src/driver/hypervisor.cpp index bc8768d..fe2ea9b 100644 --- a/src/driver/hypervisor.cpp +++ b/src/driver/hypervisor.cpp @@ -159,6 +159,39 @@ bool hypervisor::is_enabled() const return is_hypervisor_present(); } +bool hypervisor::install_ept_hook(void* destination, const void* source, const size_t length) +{ + volatile long failures = 0; + thread::dispatch_on_all_cores([&]() + { + if (!this->try_install_ept_hook_on_core(destination, source, length)) + { + InterlockedIncrement(&failures); + } + }); + + return failures == 0; +} + +void hypervisor::disable_all_ept_hooks() const +{ + thread::dispatch_on_all_cores([&]() + { + auto* vm_state = this->get_current_vm_state(); + if (!vm_state) + { + return; + } + + vm_state->ept.disable_all_hooks(); + + if (this->is_enabled()) + { + vm_state->ept.invalidate(); + } + }); +} + void hypervisor::enable() { const auto cr3 = __readcr3(); @@ -263,7 +296,6 @@ bool enter_root_mode_on_cpu(vmx::state& vm_state) launch_context->vmx_on_physical_address = memory::get_physical_address(&vm_state.vmx_on); launch_context->vmcs_physical_address = memory::get_physical_address(&vm_state.vmcs); launch_context->msr_bitmap_physical_address = memory::get_physical_address(vm_state.msr_bitmap); - launch_context->ept_pml4_physical_address = memory::get_physical_address(vm_state.ept.get_pml4()); // // Update CR0 with the must-be-zero and must-be-one requirements @@ -498,7 +530,7 @@ void vmx_handle_vmx(vmx::guest_context& guest_context) __vmx_vmwrite(VMCS_GUEST_RFLAGS, guest_context.guest_e_flags); } -void vmx_dispatch_vm_exit(vmx::guest_context& guest_context, vmx::state& vm_state) +void vmx_dispatch_vm_exit(vmx::guest_context& guest_context, const vmx::state& vm_state) { // // This is the generic VM-Exit handler. Decode the reason for the exit and @@ -532,6 +564,10 @@ void vmx_dispatch_vm_exit(vmx::guest_context& guest_context, vmx::state& vm_stat break; case VMX_EXIT_REASON_EPT_VIOLATION: vm_state.ept.handle_violation(guest_context); + break; + case VMX_EXIT_REASON_EPT_MISCONFIGURATION: + vm_state.ept.handle_misconfiguration(guest_context); + break; default: break; } @@ -650,12 +686,7 @@ void setup_vmcs_for_cpu(vmx::state& vm_state) // if (launch_context->ept_controls.flags != 0) { - ept_pointer vmx_eptp{}; - vmx_eptp.flags = 0; - vmx_eptp.page_walk_length = 3; - vmx_eptp.memory_type = MEMORY_TYPE_WRITE_BACK; - vmx_eptp.page_frame_number = launch_context->ept_pml4_physical_address / PAGE_SIZE; - + const auto vmx_eptp = vm_state.ept.get_ept_pointer(); __vmx_vmwrite(VMCS_CTRL_EPT_POINTER, vmx_eptp.flags); __vmx_vmwrite(VMCS_CTRL_VIRTUAL_PROCESSOR_IDENTIFIER, 1); } @@ -969,10 +1000,45 @@ void hypervisor::free_vm_states() this->vm_state_count_ = 0; } +bool hypervisor::try_install_ept_hook_on_core(void* destination, const void* source, const size_t length) +{ + try + { + this->install_ept_hook_on_core(destination, source, length); + return true; + } + catch (std::exception& e) + { + debug_log("Failed to install ept hook on core %d: %s\n", thread::get_processor_index(), e.what()); + return false; + } + catch (...) + { + debug_log("Failed to install ept hook on core %d.\n", thread::get_processor_index()); + return false; + } +} + +void hypervisor::install_ept_hook_on_core(void* destination, const void* source, const size_t length) +{ + auto* vm_state = this->get_current_vm_state(); + if (!vm_state) + { + throw std::runtime_error("No vm state available"); + } + + vm_state->ept.install_hook(destination, source, length); + + if (this->is_enabled()) + { + vm_state->ept.invalidate(); + } +} + vmx::state* hypervisor::get_current_vm_state() const { const auto current_core = thread::get_processor_index(); - if (current_core >= this->vm_state_count_) + if (!this->vm_states_ || current_core >= this->vm_state_count_) { return nullptr; } diff --git a/src/driver/hypervisor.hpp b/src/driver/hypervisor.hpp index 419af0e..486bd50 100644 --- a/src/driver/hypervisor.hpp +++ b/src/driver/hypervisor.hpp @@ -19,6 +19,9 @@ public: bool is_enabled() const; + bool install_ept_hook(void* destination, const void* source, size_t length); + void disable_all_ept_hooks() const; + private: uint32_t vm_state_count_{0}; vmx::state** vm_states_{nullptr}; @@ -30,5 +33,8 @@ private: void allocate_vm_states(); void free_vm_states(); + bool try_install_ept_hook_on_core(void* destination, const void* source, size_t length); + void install_ept_hook_on_core(void* destination, const void* source, size_t length); + vmx::state* get_current_vm_state() const; }; diff --git a/src/driver/nt_ext.hpp b/src/driver/nt_ext.hpp index 0c5b466..fb19cf3 100644 --- a/src/driver/nt_ext.hpp +++ b/src/driver/nt_ext.hpp @@ -116,6 +116,14 @@ PsGetProcessPeb( IN PEPROCESS Process ); + // ---------------------------------------- + +NTKERNELAPI +PCSTR +PsGetProcessImageFileName( + __in PEPROCESS Process +); + // ---------------------------------------- __kernel_entry NTSYSCALLAPI diff --git a/src/driver/process.cpp b/src/driver/process.cpp index 9834b01..e468f96 100644 --- a/src/driver/process.cpp +++ b/src/driver/process.cpp @@ -5,8 +5,8 @@ namespace process { - process_handle::process_handle(const PEPROCESS handle) - : handle_(handle) + process_handle::process_handle(const PEPROCESS handle, const bool own) + : own_(own), handle_(handle) { } @@ -26,7 +26,9 @@ namespace process if (this != &obj) { this->release(); + this->own_ = obj.own_; this->handle_ = obj.handle_; + obj.own_ = false; obj.handle_ = nullptr; } @@ -51,13 +53,25 @@ namespace process return KeWaitForSingleObject(this->handle_, Executive, KernelMode, FALSE, &zero_time) != STATUS_WAIT_0; } + const char* process_handle::get_image_filename() const + { + if (!this->handle_) + { + return nullptr; + } + + return PsGetProcessImageFileName(this->handle_); + } + void process_handle::release() { - if (this->handle_) + if (this->own_ && this->handle_) { ObDereferenceObject(this->handle_); - this->handle_ = nullptr; } + + this->handle_ = nullptr; + this->own_ = false; } process_handle find_process_by_id(const uint32_t process_id) @@ -68,7 +82,12 @@ namespace process return {}; } - return process_handle{process}; + return process_handle{process, true}; + } + + process_handle get_current_process() + { + return process_handle{PsGetCurrentProcess(), false}; } scoped_process_attacher::scoped_process_attacher(const process_handle& process) diff --git a/src/driver/process.hpp b/src/driver/process.hpp index d3c335b..3efaa55 100644 --- a/src/driver/process.hpp +++ b/src/driver/process.hpp @@ -6,7 +6,7 @@ namespace process { public: process_handle() = default; - process_handle(PEPROCESS handle); + process_handle(PEPROCESS handle, bool own = true); ~process_handle(); process_handle(process_handle&& obj) noexcept; @@ -20,13 +20,17 @@ namespace process bool is_alive() const; + const char* get_image_filename() const; + private: + bool own_{true}; PEPROCESS handle_{nullptr}; void release(); }; process_handle find_process_by_id(uint32_t process_id); + process_handle get_current_process(); class scoped_process_attacher { diff --git a/src/driver/vmx.hpp b/src/driver/vmx.hpp index eb6f88a..b00120b 100644 --- a/src/driver/vmx.hpp +++ b/src/driver/vmx.hpp @@ -36,7 +36,6 @@ namespace vmx uint64_t vmx_on_physical_address; uint64_t vmcs_physical_address; uint64_t msr_bitmap_physical_address; - uint64_t ept_pml4_physical_address; ia32_vmx_procbased_ctls2_register ept_controls; };