Skip to content

Commit 372f2ae

Browse files
authored
Merge pull request #699 from wiisus/main
Add SVM instruction exception hypervisor detection
2 parents e1ea721 + a448756 commit 372f2ae

2 files changed

Lines changed: 140 additions & 0 deletions

File tree

src/cli/output.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ void general(bool high_threshold, bool all, bool dynamic) {
557557
checker(VM::HYPERVISOR_HOOK, "EPT/NPT hooking");
558558
checker(VM::SINGLE_STEP, "single step behavior");
559559
checker(VM::EIP_OVERFLOW, "instructions in compat mode");
560+
checker(VM::SVM_EXCEPTIONS, "SVM exceptions");
560561
checker(VM::CGROUP, "cgroup namespace");
561562
checker(VM::TIMER, "timing anomalies");
562563

src/vmaware.hpp

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@ struct VM {
598598
HYPERVISOR_HOOK,
599599
SINGLE_STEP,
600600
EIP_OVERFLOW,
601+
SVM_EXCEPTIONS,
601602

602603
// Linux and Windows
603604
SYSTEM_REGISTERS,
@@ -13177,8 +13178,144 @@ struct VM {
1317713178
return hypervisor_detected;
1317813179
#endif
1317913180
}
13181+
/**
13182+
* @brief Check whether a hypervisor leaks EFER.SVME into guest context via SVM instruction fault type
13183+
* @category Windows, x86_64, AMD
13184+
* @implements VM::SVM_EXCEPTIONS
13185+
*/
13186+
[[nodiscard]] static bool svm_exceptions() {
13187+
if (!x86 || !cpu::is_amd()) {
13188+
debug("SVM_EXCEPTIONS: neither AMD or x86 detected, skipping");
13189+
return false;
13190+
}
13191+
13192+
const auto hx = util::hyper_x();
13193+
if (hx == HYPERV_ARTIFACT_VM || hx == HYPERV_REAL_VM || hx == HYPERV_ENLIGHTENMENT) {
13194+
debug("SVM_EXCEPTIONS: Hyper-V active, skipping");
13195+
return false;
13196+
}
13197+
13198+
u32 eax = 0, ebx = 0, ecx = 0, edx = 0;
13199+
cpu::cpuid(eax, ebx, ecx, edx, 0x80000001);
13200+
const bool svmcpuid_visible = (ecx >> 2) & 1;
13201+
13202+
const HMODULE ntdll = util::get_ntdll();
13203+
13204+
if (!ntdll) {
13205+
return false;
13206+
}
13207+
13208+
using nt_allocate_virtual_memory_t = NTSTATUS(__stdcall*)(HANDLE, PVOID*, ULONG_PTR, PSIZE_T, ULONG, ULONG);
13209+
using nt_protect_virtual_memory_t = NTSTATUS(__stdcall*)(HANDLE, PVOID*, PSIZE_T, ULONG, PULONG);
13210+
using nt_free_virtual_memory_t = NTSTATUS(__stdcall*)(HANDLE, PVOID*, PSIZE_T, ULONG);
13211+
using nt_flush_instruction_cache_t = NTSTATUS(__stdcall*)(HANDLE, PVOID, SIZE_T);
13212+
13213+
const char* names[] = {
13214+
"NtAllocateVirtualMemory",
13215+
"NtProtectVirtualMemory",
13216+
"NtFreeVirtualMemory",
13217+
"NtFlushInstructionCache"
13218+
};
13219+
13220+
void* funcs[ARRAYSIZE(names)] = {};
13221+
util::get_function_address(ntdll, names, funcs, ARRAYSIZE(names));
13222+
13223+
const auto nt_allocate_virtual_memory = reinterpret_cast<nt_allocate_virtual_memory_t>(funcs[0]);
13224+
const auto nt_protect_virtual_memory = reinterpret_cast<nt_protect_virtual_memory_t>(funcs[1]);
13225+
const auto nt_free_virtual_memory = reinterpret_cast<nt_free_virtual_memory_t>(funcs[2]);
13226+
const auto nt_flush_instruction_cache = reinterpret_cast<nt_flush_instruction_cache_t>(funcs[3]);
13227+
13228+
if (
13229+
(!nt_allocate_virtual_memory) ||
13230+
(!nt_protect_virtual_memory) ||
13231+
(!nt_free_virtual_memory) ||
13232+
(!nt_flush_instruction_cache)
13233+
) {
13234+
return false;
13235+
}
13236+
13237+
13238+
constexpr u8 opcodes[][4] = {
13239+
{ 0x0F, 0x01, 0xD8, 0xC3 }, // VMRUN
13240+
{ 0x0F, 0x01, 0xDA, 0xC3 }, // VMLOAD
13241+
{ 0x0F, 0x01, 0xDB, 0xC3 }, // VMSAVE
13242+
{ 0x0F, 0x01, 0xDD, 0xC3 }, // CLGI
13243+
{ 0x0F, 0x01, 0xDF, 0xC3 } // INVLPGA
13244+
};
13245+
13246+
const HANDLE current_process = reinterpret_cast<HANDLE>(-1);
13247+
constexpr u32 opcode_size = 4;
13248+
13249+
for (const auto& opcode : opcodes) {
13250+
PVOID base_address = nullptr;
13251+
SIZE_T region_size = 0x1000;
13252+
13253+
NTSTATUS status = nt_allocate_virtual_memory(
13254+
current_process, &base_address, 0, &region_size,
13255+
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE
13256+
);
13257+
13258+
if (!NT_SUCCESS(status)) {
13259+
continue;
13260+
}
13261+
13262+
memcpy(base_address, opcode, opcode_size);
13263+
nt_flush_instruction_cache(current_process, base_address, opcode_size);
13264+
13265+
ULONG old_protect = 0;
13266+
PVOID protect_address = base_address;
13267+
SIZE_T protect_size = region_size;
13268+
13269+
status = nt_protect_virtual_memory(
13270+
current_process, &protect_address, &protect_size,
13271+
PAGE_EXECUTE_READ, &old_protect
13272+
);
13273+
13274+
if (NT_SUCCESS(status)) {
13275+
nt_flush_instruction_cache(current_process, base_address, opcode_size);
13276+
13277+
DWORD exception_status = 0;
13278+
bool fault_hit = false;
13279+
13280+
__try {
13281+
const auto execute_svm = reinterpret_cast<void(*)()>(base_address);
13282+
execute_svm();
13283+
}
13284+
__except (exception_status = GetExceptionCode(), EXCEPTION_EXECUTE_HANDLER) {
13285+
fault_hit = true;
13286+
}
13287+
13288+
SIZE_T free_size = 0;
13289+
nt_free_virtual_memory(current_process, &base_address, &free_size, MEM_RELEASE);
13290+
13291+
if (fault_hit) {
13292+
if (exception_status == EXCEPTION_ILLEGAL_INSTRUCTION) {
13293+
continue;
13294+
}
13295+
13296+
if (svmcpuid_visible) {
13297+
debug("SVM_EXCEPTIONS: #GP with SVM CPUID visible, VM detected");
13298+
} else {
13299+
debug("SVM_EXCEPTIONS: #GP with SVM CPUID hidden, VM spoofing CPUID detected");
13300+
core::add(brand_enum::NULL_BRAND, 100);
13301+
}
13302+
13303+
return true;
13304+
}
13305+
} else {
13306+
SIZE_T free_size = 0;
13307+
nt_free_virtual_memory(current_process, &base_address, &free_size, MEM_RELEASE);
13308+
}
13309+
}
13310+
13311+
return false;
13312+
}
13313+
1318013314
// ADD NEW TECHNIQUE FUNCTION HERE
1318113315

13316+
13317+
13318+
1318213319
#if (CLANG)
1318313320
#pragma clang diagnostic pop
1318413321
#endif
@@ -13935,6 +14072,7 @@ struct VM {
1393514072
case HYPERVISOR_HOOK: return "HYPERVISOR_HOOK";
1393614073
case SINGLE_STEP: return "SINGLE_STEP";
1393714074
case EIP_OVERFLOW: return "EIP_OVERFLOW";
14075+
case SVM_EXCEPTIONS: return "SVM_EXCEPTIONS";
1393814076
case CGROUP: return "CGROUP";
1393914077
// END OF TECHNIQUE LIST
1394014078
case DEFAULT: return "DEFAULT";
@@ -14453,6 +14591,7 @@ std::array<VM::core::technique, VM::enum_size + 1> VM::core::technique_table = [
1445314591
{VM::KVM_INTERCEPTION, {100, VM::kvm_interception}},
1445414592
{VM::INTERRUPT_SHADOW, {100, VM::interrupt_shadow}},
1445514593
{VM::EIP_OVERFLOW, {100, VM::eip_overflow}},
14594+
{VM::SVM_EXCEPTIONS, {25, VM::svm_exceptions}},
1445614595
{VM::HYPERVISOR_HOOK, {100, VM::hypervisor_hook}},
1445714596
{VM::SINGLE_STEP, {100, VM::single_step}},
1445814597
{VM::NVRAM, {100, VM::nvram}},

0 commit comments

Comments
 (0)