Skip to content

Instantly share code, notes, and snippets.

@peta909
Forked from drew-gpf/hyperdetect.c
Created June 27, 2020 02:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save peta909/d37faae29e1af912b746940226713a0b to your computer and use it in GitHub Desktop.
Save peta909/d37faae29e1af912b746940226713a0b to your computer and use it in GitHub Desktop.
//this requires being able to run at kernel mode and assumes you're using MSVC
//this also uses an unnamed structure for cr0_t, which is a nonstandard extension of the C language
//data structure for cr0
typedef union _cr0_t
{
struct
{
uint64_t protection_enable : 1;
uint64_t monitor_coprocessor : 1;
uint64_t emulation : 1;
uint64_t task_switched : 1;
uint64_t extension_type : 1;
uint64_t numeric_error : 1;
uint64_t reserved : 10;
uint64_t write_protect : 1;
uint64_t reserved_1 : 1;
uint64_t alignment_mask : 1;
uint64_t reserved_2 : 10;
uint64_t not_write_through : 1;
uint64_t cache_disable : 1;
uint64_t paging : 1;
uint64_t reserved_3 : 32;
};
uint64_t full;
} cr0_t;
bool is_lazy_hypervisor_running(void)
{
//hypervisors like VMWare don't check if the change to cr0.pe is a valid one and just write it to the control register
//since cr0.pg will be set, this is an invalid cr0 state and causes a VM entry failure
__try
{
cr0_t cr0;
cr0.full = __readcr0();
cr0.numeric_error = !cr0.numeric_error; //force a VM exit by toggling the VMX required numeric error bit; this isn't required for VMWare since it VM exits on cr0.pe
cr0.protection_enable = 0; //disable the PE bit
__writecr0(cr0.full);
//write was ignored, return true since this should have caused a #GP(0)
return true;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
//this causes VMWare to close; good hypervisors will inject a #GP(0)
}
//alternative check: a hypervisor may dislike VMX bits being toggled and inject a #GP(0) when it shouldn't
//cr4.vmxe isn't checked as the hypervisor may have an excuse for that by setting CPUID.1:ECX.VMX[bit 5] to 0 and injecting a #UD
//they also may ignore the write which must be checked
{
cr0_t old_cr0;
old_cr0.full = __readcr0();
__try
{
//disable interrupts as we're modifying the state of a control register, which value won't be upheld on context switches
_disable();
{
cr0_t cr0 = old_cr0;
cr0.numeric_error = !cr0.numeric_error;
__writecr0(cr0.full);
//additionally, check if the hypervisor actually toggles the bit
cr0.full = __readcr0();
if (cr0.numeric_error == old_cr0.numeric_error)
{
//the write did not update cr0.ne
_enable();
return true;
}
}
__writecr0(old_cr0.full);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
//only reachable in a virtualized environment
_enable();
return true;
}
_enable();
}
//many hypervisors (including ones made by people I know, many projects online, as well as mine up until recently) don't properly handle reserved bits of cr0 and cr4
//this could cause either a VM entry failure or a triple fault (repeated #GP(0)) since the processor can't reset cr4
//a previous version used cr0 to test this; that was incorrect as reserved CR0 bits will just get forced to 0 by the processor
__try
{
__writecr4(__readcr4() | (1 << 23)); //non-existant bit
//this point is only reachable if the hypervisor ignores the change
return true;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
//if properly handled, a #GP(0) is generated and cr4's value is never changed
}
//along with realizing my previous mistake, this also opens up a detection vector for hypervisors that inject a #GP(0) upon changing cr0 reserved bits
__try
{
__writecr0(__readcr0() | (1 << 23));
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return true; //should never cause a fault
}
//check if cr0's value isn't changed after a #GP(0)
{
_disable();
cr0_t old_cr0;
old_cr0.full = __readcr0();
__try
{
cr0_t cr0 = old_cr0;
cr0.numeric_error = !cr0.numeric_error; //a bit which is guaranteed to cause a VM exit, and which we've verified won't cause a #GP(0)
cr0.protection_enable = 0; //a bit which is guaranteed not to cause VM entry failure, and which will cause a #GP(0)
cr0.write_protect = !cr0.write_protect; //a bit which won't cause a VM entry failure nor a #GP(0)
__writecr0(cr0.full);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
cr0_t cr0;
cr0.full = __readcr0();
//if everything was handled correctly, cr0.write_protect will be equal to its old value
if (cr0.write_protect != old_cr0.write_protect)
{
_enable();
__writecr0(old_cr0.full);
return true;
}
}
_enable();
}
//all checks passed
return false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment