Skip to content

Commit df1e912

Browse files
author
Requiem
committed
feat: improved VM::CPU_HEURISTIC checks
1 parent af437ff commit df1e912

1 file changed

Lines changed: 89 additions & 24 deletions

File tree

src/vmaware.hpp

Lines changed: 89 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4173,7 +4173,7 @@ struct VM {
41734173
return false;
41744174
#else
41754175
if (util::is_running_under_translator()) {
4176-
debug("TIMER: Running inside a binary translation layer.");
4176+
debug("TIMER: Running inside a binary translation layer");
41774177
return false;
41784178
}
41794179
u16 cycleThreshold = 1500;
@@ -4294,7 +4294,7 @@ struct VM {
42944294
};
42954295

42964296
auto xor_lambda = []() -> u64 {
4297-
volatile u64 a = 0xDEADBEEFDEADBEEFull;
4297+
volatile u64 a = 0xDEADBEEFDEADBEEFull; // can be replaced by NOPs
42984298
volatile u64 b = 0x1234567890ABCDEFull;
42994299
u64 v = a ^ b;
43004300
g_sink ^= v;
@@ -4308,7 +4308,7 @@ struct VM {
43084308
volatile fn_t xor_ptr = +xor_lambda;
43094309

43104310
ULONG64 beforeqit = 0;
4311-
QueryInterruptTime(&beforeqit);
4311+
QueryInterruptTime(&beforeqit); // the kernel routine that backs up this api runs at CLOCK_LEVEL(13), only preempted by IPI, POWER_LEVEL and NMIs
43124312
const ULONG64 beforetsc = __rdtsc();
43134313

43144314
volatile u64 dummy = 0;
@@ -4346,7 +4346,7 @@ struct VM {
43464346
}
43474347

43484348
if (difference > 10) {
4349-
return true; // both ratios will always differ under a RDTSC trap since the hypervisor can't account for the XOR loop
4349+
return true; // both ratios will always differ if a RDTSC trap is present, since the hypervisor can't account for the XOR/NOP loop
43504350
}
43514351
// TLB flushes or side channel cache attacks are not even tried due to how ineffective they are against stealthy hypervisors
43524352
#endif
@@ -9379,39 +9379,102 @@ struct VM {
93799379

93809380

93819381
/**
9382-
* @brief Check if the CPU is capable of running certain instructions successfully
9382+
* @brief Check whether the CPU is genuine and its reported instruction capabilities are not masked
93839383
* @category Windows
93849384
* @implements VM::CPU_HEURISTIC
93859385
*/
93869386
[[nodiscard]] static bool cpu_heuristic() {
9387-
if (util::is_running_under_translator()) return false;
9388-
9389-
// 1) Check for commonly disabled instructions on patches
9390-
bool ok = true;
9387+
#if (x86)
9388+
if (util::is_running_under_translator()) {
9389+
debug("CPU_HEURISTIC: Running inside a binary translation layer");
9390+
return false;
9391+
}
93919392

9393+
// 1) Check for commonly disabled instructions on patches and VMs
93929394
u32 a = 0, b = 0, c = 0, d = 0;
93939395
cpu::cpuid(a, b, c, d, 1u);
93949396

93959397
constexpr u32 AES_NI_BIT = 1u << 25;
9396-
if ((c & AES_NI_BIT) == 0) {
9397-
ok = false;
9398+
const bool aes_support = (c & AES_NI_BIT) != 0;
9399+
9400+
alignas(16) unsigned char plaintext[16] = {
9401+
0x00,0x11,0x22,0x33, 0x44,0x55,0x66,0x77,
9402+
0x88,0x99,0xAA,0xBB, 0xCC,0xDD,0xEE,0xFF
9403+
};
9404+
alignas(16) unsigned char key[16] = {
9405+
0x0F,0x0E,0x0D,0x0C, 0x0B,0x0A,0x09,0x08,
9406+
0x07,0x06,0x05,0x04, 0x03,0x02,0x01,0x00
9407+
};
9408+
alignas(16) unsigned char out[16] = { 0 };
9409+
__try {
9410+
__m128i block = _mm_loadu_si128(reinterpret_cast<const __m128i*>(plaintext));
9411+
__m128i key_vec = _mm_loadu_si128(reinterpret_cast<const __m128i*>(key));
9412+
9413+
__m128i tmp = _mm_xor_si128(block, key_vec);
9414+
tmp = _mm_aesenc_si128(tmp, key_vec);
9415+
9416+
_mm_storeu_si128(reinterpret_cast<__m128i*>(out), tmp);
9417+
if (!aes_support) {
9418+
debug("CPU_HEURISTIC: Hypervisor detected hiding AES capabilities");
9419+
return true;
9420+
}
93989421
}
9422+
__except (GetExceptionCode() == EXCEPTION_ILLEGAL_INSTRUCTION
9423+
? EXCEPTION_EXECUTE_HANDLER
9424+
: EXCEPTION_CONTINUE_SEARCH) {
9425+
if (aes_support) {
9426+
debug("CPU_HEURISTIC: Hypervisor reports AES, but it is not handled correctly");
9427+
return true;
9428+
}
9429+
}
93999430

9400-
if (!ok) {
9401-
debug("CPU_HEURISTIC: CPU does not report AES");
9431+
const bool avx_support = ((c >> 28) & 1u) != 0;
9432+
const bool xsave_support = ((c >> 26) & 1u) != 0;
9433+
9434+
if (avx_support && !xsave_support) {
9435+
debug("CPU_HEURISTIC: YMM state not correct for a baremetal machine");
94029436
return true;
94039437
}
94049438

9405-
// 2. Test if the CPU model is spoofed
9439+
const bool rdrand_support = ((c >> 30) & 1u) != 0;
9440+
__try {
9441+
unsigned int v = 0;
9442+
#if (MSVC && !CLANG)
9443+
if (_rdrand32_step(&v) && !rdrand_support) {
9444+
debug("CPU_HEURISTIC: Hypervisor detected hiding RDRAND capabilities");
9445+
return true;
9446+
}
9447+
#else
9448+
unsigned char ok = 0;
9449+
asm volatile("rdrand %0\n\tsetc %1" : "=r"(v), "=qm"(ok) : : "cc");
9450+
if (ok && !rdrand_support) {
9451+
debug("CPU_HEURISTIC: Hypervisor detected hiding RDRAND capabilities");
9452+
return true;
9453+
}
9454+
#endif
9455+
}
9456+
__except (GetExceptionCode() == EXCEPTION_ILLEGAL_INSTRUCTION
9457+
? EXCEPTION_EXECUTE_HANDLER
9458+
: EXCEPTION_CONTINUE_SEARCH) {
9459+
if (rdrand_support) {
9460+
debug("CPU_HEURISTIC: Hypervisor reports RDRAND, but it is not handled correctly");
9461+
return true;
9462+
}
9463+
}
9464+
9465+
// 2. Test if the CPU vendor is spoofed (for example, a CPU reports being AMD in CPUID, but it is Intel)
94069466
/*
9407-
For this task, we want a vendor-only instruction that:
9408-
1. Is compatible enough, meaning both old and new CPUs of this vendor have it
9409-
2. Is enabled by default, without needing BIOS/OS changes
9410-
3. Never switches to kernel-mode, so that is harder to intercept
9411-
4. Is not deprecated
9467+
For this task, we want a instruction that:
9468+
1. It is vendor-only, meaning that other CPU vendors never implemented the same instruction on their microcode
9469+
-> Note: Even if an instruction is vendor-only, it may be treated as a NOP by other CPU vendors, we don't want this
9470+
2. Is compatible enough, meaning both old and new CPUs of this vendor have it
9471+
3. Is enabled by default, without needing BIOS/OS changes
9472+
4. Never switches to kernel-mode, so that is harder to intercept
9473+
5. Is not deprecated today
9474+
6. Its side-effects can be measured from CPL3 (user-mode)
94129475
94139476
On Intel, most options are unreliable:
9414-
SGX are deprecated and disabled by default, MPX is deprecated and treated as NOP even in AMD CPUs, AVX-512 is not found in all processors (and amd integrated part of this set), etc
9477+
SGX are deprecated and disabled by default, MPX is deprecated and treated as NOP even in AMD CPUs, AVX-512 is not found in all processors (and AMD integrated part of this set), etc
94159478
On AMD, 3dNow! could be an option, but since its being deprecated, CLZERO fits this criteria better
94169479
94179480
So for example, if the CPU reports being Intel, and succesfully runs CLZERO without a NOP, then it's not an Intel CPU.
@@ -9440,7 +9503,8 @@ struct VM {
94409503
const bool claimed_intel = cpu::is_intel();
94419504

94429505
if (!claimed_amd && !claimed_intel) {
9443-
return false;
9506+
debug("CPU_HEURISTIC: x86 CPU vendor was not recognized as either Intel or AMD");
9507+
return false; // Zhaoxin? VIA/Centaur?
94449508
}
94459509

94469510
bool spoofed = false;
@@ -9578,6 +9642,9 @@ struct VM {
95789642
}
95799643

95809644
return spoofed;
9645+
#else
9646+
return false;
9647+
#endif
95819648
}
95829649

95839650

@@ -10962,8 +11029,6 @@ struct VM {
1096211029
return it->second;
1096311030
}
1096411031

10965-
debug("VM::type(): No known brand found, something went terribly wrong here...");
10966-
1096711032
return "Unknown";
1096811033
}
1096911034

@@ -11299,7 +11364,7 @@ std::pair<VM::enum_flags, VM::core::technique> VM::core::technique_list[] = {
1129911364
std::make_pair(VM::NVRAM, VM::core::technique(100, VM::nvram_vars)),
1130011365
std::make_pair(VM::CLOCK, VM::core::technique(100, VM::clock)),
1130111366
std::make_pair(VM::POWER_CAPABILITIES, VM::core::technique(45, VM::power_capabilities)),
11302-
std::make_pair(VM::CPU_HEURISTIC, VM::core::technique(100, VM::cpu_heuristic)),
11367+
std::make_pair(VM::CPU_HEURISTIC, VM::core::technique(90, VM::cpu_heuristic)),
1130311368
std::make_pair(VM::EDID, VM::core::technique(100, VM::edid)),
1130411369
std::make_pair(VM::BOOT_LOGO, VM::core::technique(100, VM::boot_logo)),
1130511370
std::make_pair(VM::GPU_CAPABILITIES, VM::core::technique(45, VM::gpu_capabilities)),

0 commit comments

Comments
 (0)