From 9f56763b6412002bb53f1336d1173327e6fe18dc Mon Sep 17 00:00:00 2001 From: Wiisus Date: Sun, 24 May 2026 00:02:27 +0100 Subject: [PATCH 1/3] Enhancement to VM::INTERRUPT_SHADOW --- src/vmaware.hpp | 63 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/src/vmaware.hpp b/src/vmaware.hpp index 1ecf30e2..5acbae1c 100644 --- a/src/vmaware.hpp +++ b/src/vmaware.hpp @@ -10729,6 +10729,7 @@ struct VM { * @implements VM::INTERRUPT_SHADOW */ [[nodiscard]] static bool interrupt_shadow() { + const bool is_hyperv = (util::hyper_x() == HYPERV_REAL_VM); volatile ULONG_PTR trap_ip = 0; #if (x86_32) && !(CLANG || GCC) @@ -10760,9 +10761,19 @@ struct VM { EXCEPTION_CONTINUE_EXECUTION ) : EXCEPTION_CONTINUE_SEARCH) {} - // hypervisor is detected if the trap fired at any IP differing from the expected baremetal target - // OR if the single step exception never fired at all (trap_ip == 0) - return (trap_ip == 0 || trap_ip != baremetal_target_ip); + const bool shadow_broken = (trap_ip == 0 || trap_ip != baremetal_target_ip); + + if (is_hyperv) { + if (!shadow_broken) { + debug("INTERRUPT_SHADOW: hyper-v present but shadow respected, VM hardening suspected"); + core::add(brand_enum::HYPERV_ARTIFACT_VM, 75); + } else { + debug("INTERRUPT_SHADOW: hyper-v present and shadow broken, baremetal behaviour"); + } + return false; + } + + return shadow_broken; #elif (x86_64) || ((x86_32) && (CLANG || GCC)) const HMODULE ntdll = util::get_ntdll(); @@ -10775,30 +10786,30 @@ struct VM { void* funcs[ARRAYSIZE(names)] = {}; util::get_function_address(ntdll, names, funcs, ARRAYSIZE(names)); - const auto nt_alloc = reinterpret_cast(funcs[0]); + const auto nt_alloc = reinterpret_cast(funcs[0]); const auto nt_protect = reinterpret_cast(funcs[1]); - const auto nt_flush = reinterpret_cast(funcs[2]); - const auto nt_free = reinterpret_cast(funcs[3]); + const auto nt_flush = reinterpret_cast(funcs[2]); + const auto nt_free = reinterpret_cast(funcs[3]); if (!nt_alloc || !nt_protect || !nt_flush || !nt_free) return false; // these opcodes are byte-for-byte identical for both x86_32 and x86_64 architectures // 0x53 maps to push ebx in 32-bit and push rbx in 64-bit static constexpr u8 blockstep_opcodes[] = { - 0x53, // 0: push rbx/ebx (preserve non-volatile register) - 0x31, 0xC0, // 1: xor eax, eax - 0x8C, 0xD0, // 3: mov ax, ss - 0x9C, // 5: pushfq/pushfd + 0x53, // 0: push rbx/ebx (preserve non-volatile register) + 0x31, 0xC0, // 1: xor eax, eax + 0x8C, 0xD0, // 3: mov ax, ss + 0x9C, // 5: pushfq/pushfd 0x81, 0x0C, 0x24, 0x00, 0x01, 0x00, 0x00, // 6: or dword ptr [rsp/esp], 0x100 - 0x9D, // 13: popfq/popfd - 0x8E, 0xD0, // 14: mov ss, ax <- shadow starts here - 0x0F, 0xA2, // 16: cpuid <- buggy hypervisor traps here - 0x5B, // 18: pop rbx/ebx <- baremetal traps here - 0x90, // 19: nop - 0x9C, // 20: pushfq/pushfd + 0x9D, // 13: popfq/popfd + 0x8E, 0xD0, // 14: mov ss, ax <- shadow starts here + 0x0F, 0xA2, // 16: cpuid <- buggy hypervisor traps here + 0x5B, // 18: pop rbx/ebx <- baremetal traps here + 0x90, // 19: nop + 0x9C, // 20: pushfq/pushfd 0x81, 0x24, 0x24, 0xFF, 0xFE, 0xFF, 0xFF, // 21: and dword ptr [rsp/esp], 0xFFFFFEFF - 0x9D, // 28: popfq/popfd - 0xC3 // 29: ret + 0x9D, // 28: popfq/popfd + 0xC3 // 29: ret }; const HANDLE current_process = reinterpret_cast(-1LL); @@ -10830,7 +10841,7 @@ struct VM { trap_ip = GetExceptionInformation()->ContextRecord->Eip, #endif GetExceptionInformation()->ContextRecord->EFlags &= ~0x100, // to avoid infinite looping - EXCEPTION_CONTINUE_EXECUTION + EXCEPTION_CONTINUE_EXECUTION ) : EXCEPTION_CONTINUE_SEARCH) {} } @@ -10839,7 +10850,19 @@ struct VM { // hypervisor is detected if execution trapped at any offset other than expected baremetal // OR if the single step exception never fired at all (trap_ip == 0) - return NT_SUCCESS(st) && (trap_ip == 0 || trap_ip != baremetal_target_ip); + const bool shadow_broken = NT_SUCCESS(st) && (trap_ip == 0 || trap_ip != baremetal_target_ip); + + if (is_hyperv) { + if (!shadow_broken) { + debug("INTERRUPT_SHADOW: hyper-v present but shadow respected, VM hardening suspected"); + core::add(brand_enum::HYPERV_ARTIFACT_VM, 75); + } else { + debug("INTERRUPT_SHADOW: hyper-v present and shadow broken, baremetal behaviour"); + } + return false; + } + + return shadow_broken; #else return false; #endif From a8cdebe5d2783dc63ab9b12baa4a83505e776b90 Mon Sep 17 00:00:00 2001 From: Wiisus Date: Sun, 24 May 2026 00:21:15 +0100 Subject: [PATCH 2/3] Fixed wrong scoring logic --- src/vmaware.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vmaware.hpp b/src/vmaware.hpp index 5acbae1c..03151807 100644 --- a/src/vmaware.hpp +++ b/src/vmaware.hpp @@ -10766,7 +10766,7 @@ struct VM { if (is_hyperv) { if (!shadow_broken) { debug("INTERRUPT_SHADOW: hyper-v present but shadow respected, VM hardening suspected"); - core::add(brand_enum::HYPERV_ARTIFACT_VM, 75); + core::add(brand_enum::NULL_BRAND, 50); } else { debug("INTERRUPT_SHADOW: hyper-v present and shadow broken, baremetal behaviour"); } @@ -10855,7 +10855,7 @@ struct VM { if (is_hyperv) { if (!shadow_broken) { debug("INTERRUPT_SHADOW: hyper-v present but shadow respected, VM hardening suspected"); - core::add(brand_enum::HYPERV_ARTIFACT_VM, 75); + core::add(brand_enum::NULL_BRAND, 50); } else { debug("INTERRUPT_SHADOW: hyper-v present and shadow broken, baremetal behaviour"); } From 715fbf36ff89f96269e414b0864547e413309b2a Mon Sep 17 00:00:00 2001 From: Requiem <114197630+NotRequiem@users.noreply.github.com> Date: Sun, 24 May 2026 04:04:41 +0200 Subject: [PATCH 3/3] Apply suggestions from code review fix: refactored wrong logic Co-authored-by: Requiem <114197630+NotRequiem@users.noreply.github.com> --- src/vmaware.hpp | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/src/vmaware.hpp b/src/vmaware.hpp index 03151807..495890ca 100644 --- a/src/vmaware.hpp +++ b/src/vmaware.hpp @@ -10729,7 +10729,9 @@ struct VM { * @implements VM::INTERRUPT_SHADOW */ [[nodiscard]] static bool interrupt_shadow() { - const bool is_hyperv = (util::hyper_x() == HYPERV_REAL_VM); + if (util::hyper_x() == HYPERV_ARTIFACT_VM) { + return false; + } volatile ULONG_PTR trap_ip = 0; #if (x86_32) && !(CLANG || GCC) @@ -10761,19 +10763,9 @@ struct VM { EXCEPTION_CONTINUE_EXECUTION ) : EXCEPTION_CONTINUE_SEARCH) {} - const bool shadow_broken = (trap_ip == 0 || trap_ip != baremetal_target_ip); - - if (is_hyperv) { - if (!shadow_broken) { - debug("INTERRUPT_SHADOW: hyper-v present but shadow respected, VM hardening suspected"); - core::add(brand_enum::NULL_BRAND, 50); - } else { - debug("INTERRUPT_SHADOW: hyper-v present and shadow broken, baremetal behaviour"); - } - return false; - } - - return shadow_broken; + // hypervisor is detected if the trap fired at any IP differing from the expected baremetal target + // OR if the single step exception never fired at all (trap_ip == 0) + return (trap_ip == 0 || trap_ip != baremetal_target_ip); #elif (x86_64) || ((x86_32) && (CLANG || GCC)) const HMODULE ntdll = util::get_ntdll(); @@ -10850,19 +10842,7 @@ struct VM { // hypervisor is detected if execution trapped at any offset other than expected baremetal // OR if the single step exception never fired at all (trap_ip == 0) - const bool shadow_broken = NT_SUCCESS(st) && (trap_ip == 0 || trap_ip != baremetal_target_ip); - - if (is_hyperv) { - if (!shadow_broken) { - debug("INTERRUPT_SHADOW: hyper-v present but shadow respected, VM hardening suspected"); - core::add(brand_enum::NULL_BRAND, 50); - } else { - debug("INTERRUPT_SHADOW: hyper-v present and shadow broken, baremetal behaviour"); - } - return false; - } - - return shadow_broken; + return NT_SUCCESS(st) && (trap_ip == 0 || trap_ip != baremetal_target_ip); #else return false; #endif