@@ -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, ®ion_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