From f7e0bfe97a649d30bd6aaeb7eaf92f9ce0b2d0fe Mon Sep 17 00:00:00 2001 From: _xeroxz Date: Thu, 1 Apr 2021 18:30:07 -0700 Subject: [PATCH] added some event injection fixes, NMI injection was causing loops... --- bluepill.vcxproj | 6 +- bluepill.vcxproj.filters | 18 ++- command.cpp | 26 ++++ command.hpp | 62 ++++++++++ exception.cpp | 48 ++++++++ exception.hpp | 11 ++ exit_handler.cpp | 249 +++++++++++++++------------------------ ia32.hpp | 3 +- invd.asm | 6 - invd.hpp | 2 - mm.cpp | 30 +---- vmcs.cpp | 10 -- vmxexit_handler.asm | 2 +- vmxexit_handler.h | 62 +--------- 14 files changed, 268 insertions(+), 267 deletions(-) create mode 100644 command.cpp create mode 100644 command.hpp create mode 100644 exception.cpp create mode 100644 exception.hpp delete mode 100644 invd.asm delete mode 100644 invd.hpp diff --git a/bluepill.vcxproj b/bluepill.vcxproj index 9c72598..6b4936c 100644 --- a/bluepill.vcxproj +++ b/bluepill.vcxproj @@ -85,8 +85,10 @@ + + @@ -96,11 +98,12 @@ + + - @@ -111,7 +114,6 @@ - diff --git a/bluepill.vcxproj.filters b/bluepill.vcxproj.filters index a1d371b..84c04e5 100644 --- a/bluepill.vcxproj.filters +++ b/bluepill.vcxproj.filters @@ -38,6 +38,12 @@ Source Files + + Source Files + + + Source Files + @@ -67,15 +73,18 @@ Header Files - - Header Files - Header Files Header Files + + Header Files + + + Header Files + @@ -84,9 +93,6 @@ Source Files - - Source Files - Source Files diff --git a/command.cpp b/command.cpp new file mode 100644 index 0000000..22ce2d9 --- /dev/null +++ b/command.cpp @@ -0,0 +1,26 @@ +#include "command.hpp" + +namespace command +{ + auto get(u64 dirbase, u64 command_ptr) -> vmcall_command_t + { + const auto virt_map = + mm::map_virt(dirbase, command_ptr); + + if (!virt_map) + return {}; + + return *reinterpret_cast(virt_map); + } + + auto set(u64 dirbase, u64 command_ptr, const vmcall_command_t& vmcall_command) -> void + { + const auto virt_map = + mm::map_virt(dirbase, command_ptr); + + if (!virt_map) + return; + + *reinterpret_cast(virt_map) = vmcall_command; + } +} \ No newline at end of file diff --git a/command.hpp b/command.hpp new file mode 100644 index 0000000..8451d91 --- /dev/null +++ b/command.hpp @@ -0,0 +1,62 @@ +#pragma once +#include "mm.hpp" + +namespace command +{ + enum class vmcall_option + { + translate, + copy_virt, + write_phys, + read_phys, + dirbase + }; + + typedef struct _vmcall_command_t + { + bool present; + bool result; + vmcall_option option; + + union + { + struct + { + u64 dirbase; + u64 virt_addr; + u64 phys_addr; + } translate; + + struct + { + u64 virt_src; + u64 dirbase_src; + u64 virt_dest; + u64 dirbase_dest; + u64 size; + } copy_virt; + + struct + { + u64 virt_src; + u64 dirbase_src; + u64 phys_dest; + u64 size; + } write_phys; + + struct + { + u64 phys_src; + u64 dirbase_dest; + u64 virt_dest; + u64 size; + } read_phys; + + u64 dirbase; + }; + + } vmcall_command_t, * pvmcall_command_t; + + auto get(u64 dirbase, u64 command_ptr)->vmcall_command_t; + auto set(u64 dirbase, u64 command_ptr, const vmcall_command_t& vmcall_command) -> void; +} \ No newline at end of file diff --git a/exception.cpp b/exception.cpp new file mode 100644 index 0000000..b9abee5 --- /dev/null +++ b/exception.cpp @@ -0,0 +1,48 @@ +#include "exception.hpp" + +namespace exception +{ + auto handle_debug() -> void + { + rflags g_rflags; + ia32_debugctl_register debugctl; + + __vmx_vmread(VMCS_GUEST_RFLAGS, &g_rflags.flags); + __vmx_vmread(VMCS_GUEST_DEBUGCTL, &debugctl.flags); + + // should also check: if ((g_rflags.trap_flag && (debugctl.btf && instruction.type == branching)) + if (g_rflags.trap_flag && !debugctl.btf) + { + vmx_exit_qualification_debug_exception pending_db; + __vmx_vmread(VMCS_GUEST_PENDING_DEBUG_EXCEPTIONS, &pending_db.flags); + pending_db.single_instruction = true; + __vmx_vmwrite(VMCS_GUEST_PENDING_DEBUG_EXCEPTIONS, pending_db.flags); + } + + vmx_interruptibility_state interrupt_state; + __vmx_vmread(VMCS_GUEST_INTERRUPTIBILITY_STATE, + reinterpret_cast(&interrupt_state.flags)); + + // not going to clear blocked by NMI or + // SMI stuff as IRETQ should unblock that... + // im not emulating IRETQ instruction either... + interrupt_state.blocking_by_mov_ss = false; + interrupt_state.blocking_by_sti = false; + __vmx_vmwrite(VMCS_GUEST_INTERRUPTIBILITY_STATE, interrupt_state.flags); + } + + auto injection(interruption_type type, u8 vector, ecode_t error_code) -> void + { + vmentry_interrupt_information interrupt{}; + interrupt.interruption_type = type; + interrupt.vector = vector; + interrupt.valid = true; + + if (error_code.valid) + { + interrupt.deliver_error_code = error_code.valid; + __vmx_vmwrite(VMCS_VMEXIT_INTERRUPTION_ERROR_CODE, error_code.valid); + } + __vmx_vmwrite(VMCS_CTRL_VMENTRY_INTERRUPTION_INFORMATION_FIELD, interrupt.flags); + } +} \ No newline at end of file diff --git a/exception.hpp b/exception.hpp new file mode 100644 index 0000000..10ddc40 --- /dev/null +++ b/exception.hpp @@ -0,0 +1,11 @@ +#pragma once +#include "hv_types.hpp" + +namespace exception +{ + using ecode_t = struct { bool valid; u64 value; }; + auto injection(interruption_type type, u8 vector, ecode_t error_code = {}) -> void; + + // https://howtohypervise.blogspot.com/2019/01/a-common-missight-in-most-hypervisors.html + auto handle_debug() -> void; +} \ No newline at end of file diff --git a/exit_handler.cpp b/exit_handler.cpp index ea3fe7d..ff1014c 100644 --- a/exit_handler.cpp +++ b/exit_handler.cpp @@ -1,32 +1,10 @@ #include "vmxexit_handler.h" -auto get_command(u64 dirbase, u64 command_ptr) -> vmcall_command_t -{ - const auto virt_map = - mm::map_virt(dirbase, command_ptr); - - if (!virt_map) - return {}; - - return *reinterpret_cast(virt_map); -} - -auto set_command(u64 dirbase, u64 command_ptr, const vmcall_command_t& vmcall_command) -> void -{ - const auto virt_map = - mm::map_virt(dirbase, command_ptr); - - if (!virt_map) - return; - - *reinterpret_cast(virt_map) = vmcall_command; -} - auto vmresume_failure() -> void { size_t value; __vmx_vmread(VMCS_VM_INSTRUCTION_ERROR, &value); - __debugbreak(); + dbg::print("> vmresume error... reason = 0x%x\n", value); } auto exit_handler(hv::pguest_registers regs) -> void @@ -44,19 +22,13 @@ auto exit_handler(hv::pguest_registers regs) -> void regs->rbx = result[1]; regs->rcx = result[2]; regs->rdx = result[3]; - break; + goto advance_rip; } // shouldnt get an exit when the LP is already executing an NMI... // so it should be safe to inject an NMI here... case VMX_EXIT_REASON_NMI_WINDOW: { - vmentry_interrupt_information interrupt{}; - interrupt.interruption_type = interruption_type::non_maskable_interrupt; - interrupt.vector = EXCEPTION_NMI; - interrupt.valid = true; - - __vmx_vmwrite(VMCS_CTRL_VMENTRY_INTERRUPTION_INFORMATION_FIELD, interrupt.flags); - __vmx_vmwrite(VMCS_VMEXIT_INTERRUPTION_ERROR_CODE, NULL); + exception::injection(interruption_type::non_maskable_interrupt, EXCEPTION_NMI); // turn off NMI window exiting since we handled the NMI... ia32_vmx_procbased_ctls_register procbased_ctls; @@ -64,16 +36,7 @@ auto exit_handler(hv::pguest_registers regs) -> void procbased_ctls.nmi_window_exiting = false; __vmx_vmwrite(VMCS_CTRL_PROCESSOR_BASED_VM_EXECUTION_CONTROLS, procbased_ctls.flags); - return; // dont advance rip... - } - case VMX_EXIT_REASON_EXCEPTION_OR_NMI: - { - ia32_vmx_procbased_ctls_register procbased_ctls; - __vmx_vmread(VMCS_CTRL_PROCESSOR_BASED_VM_EXECUTION_CONTROLS, &procbased_ctls.flags); - - procbased_ctls.nmi_window_exiting = true; - __vmx_vmwrite(VMCS_CTRL_PROCESSOR_BASED_VM_EXECUTION_CONTROLS, procbased_ctls.flags); - return; // dont advance rip... + goto dont_advance; } case VMX_EXIT_REASON_EXECUTE_XSETBV: { @@ -96,21 +59,14 @@ auto exit_handler(hv::pguest_registers regs) -> void If the LOCK prefix is used. */ _xsetbv(regs->rcx, value.value); - break; + goto advance_rip; } __except (EXCEPTION_EXECUTE_HANDLER) { - vmentry_interrupt_information interrupt{}; - interrupt.interruption_type = interruption_type::hardware_exception; - interrupt.vector = EXCEPTION_GP_FAULT; - - interrupt.valid = true; - interrupt.deliver_error_code = true; - - __vmx_vmwrite(VMCS_CTRL_VMENTRY_INTERRUPTION_INFORMATION_FIELD, interrupt.flags); - __vmx_vmwrite(VMCS_VMEXIT_INTERRUPTION_ERROR_CODE, g_vcpu.error_code); + exception::injection(interruption_type::hardware_exception, + EXCEPTION_GP_FAULT, { true, g_vcpu.error_code }); + goto dont_advance; } - return; // dont advance rip... } case VMX_EXIT_REASON_EXECUTE_RDMSR: { @@ -127,21 +83,14 @@ auto exit_handler(hv::pguest_registers regs) -> void regs->rdx = result.high; regs->rax = result.low; - break; + goto advance_rip; } __except (EXCEPTION_EXECUTE_HANDLER) { - vmentry_interrupt_information interrupt{}; - interrupt.interruption_type = interruption_type::hardware_exception; - interrupt.vector = EXCEPTION_GP_FAULT; - - interrupt.valid = true; - interrupt.deliver_error_code = true; - - __vmx_vmwrite(VMCS_CTRL_VMENTRY_INTERRUPTION_INFORMATION_FIELD, interrupt.flags); - __vmx_vmwrite(VMCS_VMEXIT_INTERRUPTION_ERROR_CODE, g_vcpu.error_code); + exception::injection(interruption_type::hardware_exception, + EXCEPTION_GP_FAULT, { true, g_vcpu.error_code }); + goto dont_advance; } - return; // dont advance rip... } case VMX_EXIT_REASON_EXECUTE_WRMSR: { @@ -158,28 +107,19 @@ auto exit_handler(hv::pguest_registers regs) -> void #UD If the LOCK prefix is used. */ __writemsr(regs->rcx, value.value); - break; + goto advance_rip; } __except (EXCEPTION_EXECUTE_HANDLER) { - vmentry_interrupt_information interrupt{}; - interrupt.interruption_type = interruption_type::hardware_exception; - interrupt.vector = EXCEPTION_GP_FAULT; - - interrupt.valid = true; - interrupt.deliver_error_code = true; - - __vmx_vmwrite(VMCS_CTRL_VMENTRY_INTERRUPTION_INFORMATION_FIELD, interrupt.flags); - __vmx_vmwrite(VMCS_VMEXIT_INTERRUPTION_ERROR_CODE, g_vcpu.error_code); + exception::injection(interruption_type::hardware_exception, + EXCEPTION_GP_FAULT, { true, g_vcpu.error_code }); + goto dont_advance; } - return; // dont advance rip... } case VMX_EXIT_REASON_EXECUTE_INVD: { - // couldnt find the intrin for this so i just made one... - // probably could have used __wbinvd? - __invd(); - break; + __wbinvd(); + goto advance_rip; } case VMX_EXIT_REASON_EXECUTE_VMCALL: { @@ -188,82 +128,78 @@ auto exit_handler(hv::pguest_registers regs) -> void cr3 dirbase; __vmx_vmread(VMCS_GUEST_CR3, &dirbase.flags); - auto command = get_command( + auto command = command::get( dirbase.pml4_pfn << 12, regs->rdx); - if (command.present) + if (!command.present) { - switch (command.option) - { - case vmcall_option::copy_virt: - { - command.result = - mm::copy_virt( - command.copy_virt.dirbase_src, - command.copy_virt.virt_src, - command.copy_virt.dirbase_dest, - command.copy_virt.virt_dest, - command.copy_virt.size); - break; - } - case vmcall_option::translate: - { - command.translate.phys_addr = - mm::translate(mm::virt_addr_t{ - command.translate.virt_addr }, - command.translate.dirbase); - - // true if address is not null... - command.result = command.translate.phys_addr; - break; - } - case vmcall_option::read_phys: - { - command.result = - mm::read_phys( - command.read_phys.dirbase_dest, - command.read_phys.phys_src, - command.read_phys.virt_dest, - command.read_phys.size); - break; - } - case vmcall_option::write_phys: - { - command.result = - mm::write_phys( - command.write_phys.dirbase_src, - command.write_phys.phys_dest, - command.write_phys.virt_src, - command.write_phys.size); - break; - } - case vmcall_option::dirbase: - { - command.result = true; - command.dirbase = dirbase.pml4_pfn << 12; - break; - } - default: - // check to see why the option was invalid... - __debugbreak(); - break; - } + exception::injection(interruption_type::hardware_exception, EXCEPTION_INVALID_OPCODE); + goto dont_advance; + } - set_command(dirbase.pml4_pfn << 12, regs->rdx, command); + switch (command.option) + { + case command::vmcall_option::copy_virt: + { + command.result = + mm::copy_virt( + command.copy_virt.dirbase_src, + command.copy_virt.virt_src, + command.copy_virt.dirbase_dest, + command.copy_virt.virt_dest, + command.copy_virt.size); + break; } + case command::vmcall_option::translate: + { + command.translate.phys_addr = + mm::translate(mm::virt_addr_t{ + command.translate.virt_addr }, + command.translate.dirbase); + + // true if address is not null... + command.result = command.translate.phys_addr; + break; + } + case command::vmcall_option::read_phys: + { + command.result = + mm::read_phys( + command.read_phys.dirbase_dest, + command.read_phys.phys_src, + command.read_phys.virt_dest, + command.read_phys.size); + break; + } + case command::vmcall_option::write_phys: + { + command.result = + mm::write_phys( + command.write_phys.dirbase_src, + command.write_phys.phys_dest, + command.write_phys.virt_src, + command.write_phys.size); + break; + } + case command::vmcall_option::dirbase: + { + command.result = true; + command.dirbase = dirbase.pml4_pfn << 12; + break; + } + default: + // check to see why the option was invalid... + __debugbreak(); + } + + command::set(dirbase.pml4_pfn << 12, regs->rdx, command); + goto advance_rip; } else { - vmentry_interrupt_information interrupt{}; - interrupt.interruption_type = interruption_type::hardware_exception; - interrupt.vector = EXCEPTION_INVALID_OPCODE; - interrupt.valid = true; - - __vmx_vmwrite(VMCS_CTRL_VMENTRY_INTERRUPTION_INFORMATION_FIELD, interrupt.flags); - __vmx_vmwrite(VMCS_VMEXIT_INTERRUPTION_ERROR_CODE, NULL); - return; // dont advance rip... + exception::injection(interruption_type::hardware_exception, EXCEPTION_INVALID_OPCODE); + goto dont_advance; } - break; } case VMX_EXIT_REASON_EXECUTE_VMWRITE: case VMX_EXIT_REASON_EXECUTE_VMREAD: @@ -272,24 +208,27 @@ auto exit_handler(hv::pguest_registers regs) -> void case VMX_EXIT_REASON_EXECUTE_VMCLEAR: case VMX_EXIT_REASON_EXECUTE_VMXOFF: case VMX_EXIT_REASON_EXECUTE_VMXON: + case VMX_EXIT_REASON_EXECUTE_VMFUNC: { - vmentry_interrupt_information interrupt{}; - interrupt.interruption_type = interruption_type::hardware_exception; - interrupt.vector = EXCEPTION_INVALID_OPCODE; - interrupt.valid = true; - - __vmx_vmwrite(VMCS_CTRL_VMENTRY_INTERRUPTION_INFORMATION_FIELD, interrupt.flags); - __vmx_vmwrite(VMCS_VMEXIT_INTERRUPTION_ERROR_CODE, NULL); - return; // dont advance rip... + exception::injection(interruption_type::hardware_exception, EXCEPTION_INVALID_OPCODE); + goto dont_advance; } default: // TODO: check out the vmexit reason and add support for it... __debugbreak(); - break; } - + +advance_rip: size_t rip, exec_len; __vmx_vmread(VMCS_GUEST_RIP, &rip); __vmx_vmread(VMCS_VMEXIT_INSTRUCTION_LENGTH, &exec_len); __vmx_vmwrite(VMCS_GUEST_RIP, rip + exec_len); + + // since we are advancing RIP, also check if TF = 1, if so, set pending #DB... + // otherwise this #DB will fire on the wrong instruction... please refer to: + // https://howtohypervise.blogspot.com/2019/01/a-common-missight-in-most-hypervisors.html + exception::handle_debug(); + +dont_advance: + return; } \ No newline at end of file diff --git a/ia32.hpp b/ia32.hpp index e710e4c..2510e6c 100644 --- a/ia32.hpp +++ b/ia32.hpp @@ -16605,6 +16605,7 @@ typedef struct */ typedef union { + uint64_t flags; struct { /** @@ -16644,8 +16645,6 @@ typedef union #define VMX_EXIT_QUALIFICATION_DEBUG_EXCEPTION_SINGLE_INSTRUCTION(_) (((_) >> 14) & 0x01) uint64_t reserved2 : 49; }; - - uint64_t flags; } vmx_exit_qualification_debug_exception; /** diff --git a/invd.asm b/invd.asm deleted file mode 100644 index 6075711..0000000 --- a/invd.asm +++ /dev/null @@ -1,6 +0,0 @@ -.code -__invd proc - invd - ret -__invd endp -end \ No newline at end of file diff --git a/invd.hpp b/invd.hpp deleted file mode 100644 index fd580a1..0000000 --- a/invd.hpp +++ /dev/null @@ -1,2 +0,0 @@ -#pragma once -extern "C" void __invd(void); \ No newline at end of file diff --git a/mm.cpp b/mm.cpp index c91dfd3..a432f34 100644 --- a/mm.cpp +++ b/mm.cpp @@ -145,14 +145,8 @@ namespace mm if (!mapped_src) return false; - __try - { - memcpy(mapped_dest, mapped_src, current_size); - } - __except (EXCEPTION_EXECUTE_HANDLER) - { - return false; - } + __try { memcpy(mapped_dest, mapped_src, current_size); } + __except (EXCEPTION_EXECUTE_HANDLER) { return false; } guest_phys += current_size; guest_virt += current_size; @@ -196,14 +190,8 @@ namespace mm if (!mapped_src) return false; - __try - { - memcpy(mapped_dest, mapped_src, current_size); - } - __except (EXCEPTION_EXECUTE_HANDLER) - { - return false; - } + __try { memcpy(mapped_dest, mapped_src, current_size); } + __except (EXCEPTION_EXECUTE_HANDLER){ return false; } guest_phys += current_size; guest_virt += current_size; @@ -242,14 +230,8 @@ namespace mm // copy directly between the two pages... auto current_size = min(dest_size, src_size); - __try - { - memcpy(mapped_dest, mapped_src, current_size); - } - __except (EXCEPTION_EXECUTE_HANDLER) - { - return false; - } + __try{ memcpy(mapped_dest, mapped_src, current_size); } + __except (EXCEPTION_EXECUTE_HANDLER){ return false; } virt_src += current_size; virt_dest += current_size; diff --git a/vmcs.cpp b/vmcs.cpp index fb59354..b9d1941 100644 --- a/vmcs.cpp +++ b/vmcs.cpp @@ -152,9 +152,7 @@ namespace vmcs ia32_vmx_exit_ctls_register exit_ctls{}; ia32_vmx_basic_register vmx_basic{ __readmsr(IA32_VMX_BASIC) }; - pinbased_ctls.nmi_exiting = true; pinbased_ctls.virtual_nmi = true; - procbased_ctls.activate_secondary_controls = true; exit_ctls.host_address_space_size = true; @@ -164,7 +162,6 @@ namespace vmcs procbased_ctls2.enable_rdtscp = true; procbased_ctls2.enable_xsaves = true; procbased_ctls2.conceal_vmx_from_pt = true; - //procbased_ctls2.enable_ept = true; if (vmx_basic.vmx_controls) { @@ -211,13 +208,6 @@ namespace vmcs __vmx_vmwrite(VMCS_CTRL_VMEXIT_CONTROLS, exit_ctls.flags); } - /*ept_pointer eptp{}; - eptp.memory_type = MEMORY_TYPE_WRITE_BACK; - eptp.enable_access_and_dirty_flags = true; - eptp.page_walk_length = EPT_PAGE_WALK_LENGTH_4; - eptp.page_frame_number = reinterpret_cast(&mm::epml4) >> 12; - __vmx_vmwrite(VMCS_CTRL_EPT_POINTER, eptp.flags);*/ - msr_fix_value.flags = __readmsr(IA32_VMX_PROCBASED_CTLS2); procbased_ctls2.flags &= msr_fix_value.allowed_1_settings; procbased_ctls2.flags |= msr_fix_value.allowed_0_settings; diff --git a/vmxexit_handler.asm b/vmxexit_handler.asm index 1ab295e..d537844 100644 --- a/vmxexit_handler.asm +++ b/vmxexit_handler.asm @@ -101,6 +101,6 @@ vmxexit_handler proc vmresume call vmresume_failure - hlt + int 3 vmxexit_handler endp end \ No newline at end of file diff --git a/vmxexit_handler.h b/vmxexit_handler.h index b404baa..aae3d5a 100644 --- a/vmxexit_handler.h +++ b/vmxexit_handler.h @@ -1,67 +1,11 @@ #pragma once #include "hv_types.hpp" #include "debug.hpp" -#include "invd.hpp" #include "mm.hpp" #include "vmxon.hpp" - -enum class vmcall_option -{ - translate, - copy_virt, - write_phys, - read_phys, - dirbase -}; - -typedef struct _vmcall_command_t -{ - bool present; - bool result; - vmcall_option option; - - union - { - struct - { - u64 dirbase; - u64 virt_addr; - u64 phys_addr; - } translate; - - struct - { - u64 virt_src; - u64 dirbase_src; - u64 virt_dest; - u64 dirbase_dest; - u64 size; - } copy_virt; - - struct - { - u64 virt_src; - u64 dirbase_src; - u64 phys_dest; - u64 size; - } write_phys; - - struct - { - u64 phys_src; - u64 dirbase_dest; - u64 virt_dest; - u64 size; - } read_phys; - - u64 dirbase; - }; - -} vmcall_command_t, * pvmcall_command_t; +#include "command.hpp" +#include "exception.hpp" extern "C" auto vmxexit_handler() -> void; extern "C" auto vmresume_failure() -> void; -extern "C" auto exit_handler(hv::pguest_registers regs) -> void; - -auto get_command(u64 dirbase, u64 command_ptr) -> vmcall_command_t; -auto set_command(u64 dirbase, u64 command_ptr, const vmcall_command_t& vmcall_command) -> void; \ No newline at end of file +extern "C" auto exit_handler(hv::pguest_registers regs) -> void; \ No newline at end of file