added some event injection fixes, NMI injection was causing loops...

merge-requests/4/head
_xeroxz 3 years ago
parent ea8b460a02
commit f7e0bfe97a

@ -85,8 +85,10 @@
<FilesToPackage Include="$(TargetPath)" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="command.cpp" />
<ClCompile Include="debug.cpp" />
<ClCompile Include="drv_entry.cpp" />
<ClCompile Include="exception.cpp" />
<ClCompile Include="exit_handler.cpp" />
<ClCompile Include="gdt.cpp" />
<ClCompile Include="idt.cpp" />
@ -96,11 +98,12 @@
<ClCompile Include="vmxon.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="command.hpp" />
<ClInclude Include="debug.hpp" />
<ClInclude Include="exception.hpp" />
<ClInclude Include="gdt.hpp" />
<ClInclude Include="ia32.hpp" />
<ClInclude Include="idt.hpp" />
<ClInclude Include="invd.hpp" />
<ClInclude Include="mm.hpp" />
<ClInclude Include="segment_intrin.h" />
<ClInclude Include="hv_types.hpp" />
@ -111,7 +114,6 @@
</ItemGroup>
<ItemGroup>
<MASM Include="idt_handlers.asm" />
<MASM Include="invd.asm" />
<MASM Include="segment_intrin.asm" />
<MASM Include="vmxexit_handler.asm" />
</ItemGroup>

@ -38,6 +38,12 @@
<ClCompile Include="gdt.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="command.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="exception.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="vmxon.hpp">
@ -67,15 +73,18 @@
<ClInclude Include="idt.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="invd.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="debug.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gdt.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="command.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="exception.hpp">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<MASM Include="segment_intrin.asm">
@ -84,9 +93,6 @@
<MASM Include="vmxexit_handler.asm">
<Filter>Source Files</Filter>
</MASM>
<MASM Include="invd.asm">
<Filter>Source Files</Filter>
</MASM>
<MASM Include="idt_handlers.asm">
<Filter>Source Files</Filter>
</MASM>

@ -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<pvmcall_command_t>(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<pvmcall_command_t>(virt_map) = vmcall_command;
}
}

@ -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;
}

@ -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<size_t*>(&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);
}
}

@ -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;
}

@ -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<pvmcall_command_t>(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<pvmcall_command_t>(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;
}

@ -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;
/**

@ -1,6 +0,0 @@
.code
__invd proc
invd
ret
__invd endp
end

@ -1,2 +0,0 @@
#pragma once
extern "C" void __invd(void);

@ -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;

@ -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<u64>(&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;

@ -101,6 +101,6 @@ vmxexit_handler proc
vmresume
call vmresume_failure
hlt
int 3
vmxexit_handler endp
end

@ -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;
extern "C" auto exit_handler(hv::pguest_registers regs) -> void;
Loading…
Cancel
Save