-
-
Save x1tan/c5b42e084d12fa5f53fb9d0052048564 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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