@@ -586,7 +586,7 @@ struct VM {
586586 CUCKOO_PIPE,
587587 TRAP,
588588 UD,
589- BLOCKSTEP ,
589+ INTERRUPT_SHADOW ,
590590 DBVM,
591591 KERNEL_OBJECTS,
592592 NVRAM,
@@ -10472,31 +10472,44 @@ struct VM {
1047210472 /**
1047310473 * @brief Check if a hypervisor does not properly restore the interruptibility state after a VM-exit
1047410474 * @category Windows
10475- * @implements VM::BLOCKSTEP
10475+ * @implements VM::INTERRUPT_SHADOW
1047610476 */
10477- [[nodiscard]] static bool blockstep () {
10478- volatile int saw_single_step = 0;
10477+ [[nodiscard]] static bool interrupt_shadow () {
10478+ volatile ULONG_PTR trap_ip = 0;
1047910479
1048010480 #if (x86_32) && !(CLANG || GCC)
10481+ ULONG_PTR baremetal_target_ip = 0;
10482+
1048110483 __try {
1048210484 __asm {
10485+ mov dword ptr[baremetal_target_ip], offset baremetal_target // get exact baremetal trap target address dynamically
10486+ push ebx
10487+ xor eax, eax
10488+ mov ax, ss
1048310489 pushfd
1048410490 or dword ptr[esp], 0x100 // set TF
1048510491 popfd
10486- xor eax, eax
10487- mov ax, ss
1048810492 mov ss, ax // this blocks any debug exception for exactly one instruction
1048910493 cpuid
10490- nop // TF's single-step should fire here on baremetal except on a few buggy processors
10491- pushfd
10492- and dword ptr[esp], 0xFFFFFEFF
10493- popfd
10494+ baremetal_target :
10495+ pop ebx // baremetal delays the #DB until here due to shadow suppression
10496+ nop
10497+ pushfd
10498+ and dword ptr[esp], 0xFFFFFEFF
10499+ popfd
1049410500 }
1049510501 }
10496- __except (GetExceptionCode() == EXCEPTION_SINGLE_STEP ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
10497- saw_single_step = 1;
10498- }
10499- return (saw_single_step == 0);
10502+ __except (GetExceptionCode() == EXCEPTION_SINGLE_STEP ?
10503+ (
10504+ trap_ip = GetExceptionInformation()->ContextRecord->Eip,
10505+ GetExceptionInformation()->ContextRecord->EFlags &= ~0x100, // clear TF so execution resumes cleanly and stack restores
10506+ EXCEPTION_CONTINUE_EXECUTION
10507+ ) : EXCEPTION_CONTINUE_SEARCH) {}
10508+
10509+ // hypervisor is detected if the trap fired at any IP differing from the expected baremetal target
10510+ // OR if the single step exception never fired at all (trap_ip == 0)
10511+ return (trap_ip == 0 || trap_ip != baremetal_target_ip);
10512+
1050010513 #elif (x86_64) || ((x86_32) && (CLANG || GCC))
1050110514 const HMODULE ntdll = util::get_ntdll();
1050210515 if (!ntdll) return false;
@@ -10516,22 +10529,22 @@ struct VM {
1051610529 if (!nt_alloc || !nt_protect || !nt_flush || !nt_free) return false;
1051710530
1051810531 // these opcodes are byte-for-byte identical for both x86_32 and x86_64 architectures
10519- // like, 0x53 maps to push ebx in 32-bit and push rbx in 64-bit
10532+ // 0x53 maps to push ebx in 32-bit and push rbx in 64-bit
1052010533 static constexpr u8 blockstep_opcodes[] = {
10521- 0x53, // push rbx/ebx (preserve non-volatile register against cpuid )
10522- 0x9C, // pushfq/pushfd
10523- 0x81, 0x0C, 0x24, 0x00, 0x01, 0x00, 0x00, // or dword ptr [rsp/esp], 0x100
10524- 0x9D , // popfq/popfd
10525- 0x31, 0xC0, // xor eax, eax
10526- 0x8C, 0xD0, // mov ax, ss
10527- 0x8E, 0xD0, // mov ss, ax
10528- 0x0F, 0xA2, // cpuid
10529- 0x90 , // nop
10530- 0x9C , // pushfq/pushfd
10531- 0x81, 0x24, 0x24, 0xFF, 0xFE, 0xFF, 0xFF, // and dword ptr [rsp/esp], 0xFFFFFEFF
10532- 0x9D, // popfq/popfd
10533- 0x5B , // pop rbx/ebx
10534- 0xC3 // ret
10534+ 0x53, // 0: push rbx/ebx (preserve non-volatile register)
10535+ 0x31, 0xC0, // 1: xor eax, eax
10536+ 0x8C, 0xD0, // 3: mov ax, ss
10537+ 0x9C , // 5: pushfq/pushfd
10538+ 0x81, 0x0C, 0x24, 0x00, 0x01, 0x00, 0x00, // 6: or dword ptr [rsp/esp], 0x100
10539+ 0x9D, // 13: popfq/popfd
10540+ 0x8E, 0xD0, // 14: mov ss, ax <- shadow starts here
10541+ 0x0F, 0xA2, // 16: cpuid <- buggy hypervisor traps here
10542+ 0x5B , // 18: pop rbx/ebx <- baremetal traps here
10543+ 0x90 , // 19: nop
10544+ 0x9C, // 20: pushfq/pushfd
10545+ 0x81, 0x24, 0x24, 0xFF, 0xFE, 0xFF, 0xFF, // 21: and dword ptr [rsp/esp], 0xFFFFFEFF
10546+ 0x9D , // 28: popfq/popfd
10547+ 0xC3 // 29: ret
1053510548 };
1053610549
1053710550 const HANDLE current_process = reinterpret_cast<HANDLE>(-1LL);
@@ -10547,26 +10560,37 @@ struct VM {
1054710560 ULONG old_protection = 0;
1054810561 NTSTATUS st = nt_protect(current_process, &base, ®ion_size, PAGE_EXECUTE_READ, &old_protection);
1054910562
10563+ // expect the trap explicitly at offset +18 (pop rbx) because of shadow suppression on real hardware
10564+ const ULONG_PTR baremetal_target_ip = reinterpret_cast<ULONG_PTR>(base) + 18;
10565+
1055010566 if (NT_SUCCESS(st)) {
1055110567 nt_flush(current_process, base, region_size);
1055210568 __try {
1055310569 reinterpret_cast<void(*)()>(base)();
1055410570 }
10555- __except (GetExceptionCode() == EXCEPTION_SINGLE_STEP ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
10556- saw_single_step = 1;
10557- }
10571+ __except (GetExceptionCode() == EXCEPTION_SINGLE_STEP ?
10572+ (
10573+ #if (x86_64)
10574+ trap_ip = GetExceptionInformation()->ContextRecord->Rip,
10575+ #else
10576+ trap_ip = GetExceptionInformation()->ContextRecord->Eip,
10577+ #endif
10578+ GetExceptionInformation()->ContextRecord->EFlags &= ~0x100, // to avoid infinite looping
10579+ EXCEPTION_CONTINUE_EXECUTION
10580+ ) : EXCEPTION_CONTINUE_SEARCH) {}
1055810581 }
1055910582
1056010583 region_size = 0;
1056110584 nt_free(current_process, &base, ®ion_size, MEM_RELEASE);
1056210585
10563- return NT_SUCCESS(st) && (saw_single_step == 0);
10586+ // hypervisor is detected if execution trapped at any offset other than expected baremetal
10587+ // OR if the single step exception never fired at all (trap_ip == 0)
10588+ return NT_SUCCESS(st) && (trap_ip == 0 || trap_ip != baremetal_target_ip);
1056410589 #else
1056510590 return false;
1056610591 #endif
1056710592 }
1056810593
10569-
1057010594 /**
1057110595 * @brief Check if Dark Byte's VM is present
1057210596 * @category Windows
@@ -13618,7 +13642,7 @@ struct VM {
1361813642 case ACPI_SIGNATURE: return "ACPI_SIGNATURE";
1361913643 case TRAP: return "TRAP";
1362013644 case UD: return "UNDEFINED_INSTRUCTION";
13621- case BLOCKSTEP : return "BLOCKSTEP ";
13645+ case INTERRUPT_SHADOW : return "INTERRUPT_SHADOW ";
1362213646 case DBVM: return "DBVM_HYPERCALL";
1362313647 case BOOT_LOGO: return "BOOT_LOGO";
1362413648 case MAC_SYS: return "MAC_SYS";
@@ -14176,7 +14200,7 @@ std::array<VM::core::technique, VM::enum_size + 1> VM::core::technique_table = [
1417614200 {VM::EIP_OVERFLOW, {100, VM::eip_overflow}},
1417714201 {VM::HYPERVISOR_HOOK, {100, VM::hypervisor_hook}},
1417814202 {VM::POPF, {100, VM::popf}},
14179- {VM::BLOCKSTEP , {100, VM::blockstep }},
14203+ {VM::INTERRUPT_SHADOW , {100, VM::interrupt_shadow }},
1418014204 {VM::MSR, {100, VM::msr}},
1418114205 {VM::EDID, {100, VM::edid}},
1418214206 {VM::VIRTUAL_PROCESSORS, {100, VM::virtual_processors}},
0 commit comments