From 49e3794c73d2012a069e4038c4ab0c412a525b52 Mon Sep 17 00:00:00 2001 From: Requiem Date: Tue, 5 May 2026 00:20:59 +0200 Subject: [PATCH 1/3] chore: sync remote main --- CMakeLists.txt | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ca1cd9b..4b9263df 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,15 +54,14 @@ if (LINUX) set(CMAKE_CXX_COMPILER "${GPP_EXECUTABLE}") get_filename_component(COMPILER_NAME ${GPP_EXECUTABLE} NAME) endif() +elseif(APPLE) + set(COMPILER_NAME "AppleClang") elseif(MSVC) set(COMPILER_NAME "MSVC") endif() message(STATUS "Compiler: ${COMPILER_NAME}") -# fetch and set build type -set(available_build_types Debug Release Dev_Release) - # Define preprocessor macros based on the build type # Use generator expressions so this works correctly with Visual Studio. add_compile_definitions( @@ -71,8 +70,6 @@ add_compile_definitions( ) # general variables -set(PROJECT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") -set(BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}") set(TARGET "vmaware") # debug/release CXX flag options @@ -83,7 +80,7 @@ elseif(APPLE) elseif(LINUX) message(STATUS "Linux platform detected") else() - message(STATUS "Build set to release mode") + message(STATUS "Unknown platform") endif() # add executable @@ -93,13 +90,15 @@ target_compile_features(${TARGET} PRIVATE cxx_std_20) # debug/release flags (MULTI-CONFIG SAFE) # Apply flags per-config using generator expressions -# Debug (with sanitizers) +# Debug (with sanitizers) — leak sanitizer is Linux-only, not available on macOS target_compile_options(${TARGET} PRIVATE - $<$,$>>:-g;-DDEBUG;-O0;-fsanitize=address,leak> + $<$,$>:-g;-DDEBUG;-O0;-fsanitize=address,leak> + $<$,$>:-g;-DDEBUG;-O0;-fsanitize=address> ) target_link_options(${TARGET} PRIVATE - $<$,$>>:-fsanitize=address,leak> + $<$,$>:-fsanitize=address,leak> + $<$,$>:-fsanitize=address> ) # detect x86 @@ -148,7 +147,6 @@ endif() # CTest stuff include(CTest) if(BUILD_TESTING) - enable_testing() set(ARGUMENTS "--all") if(MSVC) add_test(NAME executable COMMAND $ ${ARGUMENTS}) @@ -160,17 +158,5 @@ endif() # install rules include(GNUInstallDirs) -if (NOT MSVC) - if(CMAKE_BUILD_TYPE MATCHES "Release") - install(TARGETS ${TARGET} DESTINATION /usr/bin) - install(FILES "src/vmaware.hpp" DESTINATION /usr/include) - else() - install(TARGETS ${TARGET} DESTINATION ${CMAKE_SOURCE_DIR}) - endif() -elseif(MSVC) - set(CMAKE_INSTALL_PREFIX "C:\\Program Files\\YourApplication") - install(TARGETS ${TARGET} RUNTIME DESTINATION "bin") - - set(HEADER_INSTALL_PATH "C:\\Program Files (x86)\\YourLibrary\\include") - install(FILES "src/vmaware.hpp" DESTINATION "${HEADER_INSTALL_PATH}") -endif() \ No newline at end of file +install(TARGETS ${TARGET} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(FILES "src/vmaware.hpp" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) \ No newline at end of file From 992b12b44c4a1b7cd1d092b5f2c46e62b525ec53 Mon Sep 17 00:00:00 2001 From: Requiem Date: Wed, 6 May 2026 02:49:36 +0200 Subject: [PATCH 2/3] feat: interrupt shadow technique --- src/cli.cpp | 226 +++++++++++++++++++++++++----------------------- src/vmaware.hpp | 95 +++++++++++++------- 2 files changed, 181 insertions(+), 140 deletions(-) diff --git a/src/cli.cpp b/src/cli.cpp index 5d3a3203..ff7026fa 100755 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -1,49 +1,49 @@ /** * ██╗ ██╗███╗ ███╗ █████╗ ██╗ ██╗ █████╗ ██████╗ ███████╗ * ██║ ██║████╗ ████║██╔══██╗██║ ██║██╔══██╗██╔══██╗██╔════╝ - * ██║ ██║██╔████╔██║███████║██║ █╗ ██║███████║██████╔╝█████╗ - * ╚██╗ ██╔╝██║╚██╔╝██║██╔══██║██║███╗██║██╔══██║██╔══██╗██╔══╝ + * ██║ ██║██╔████╔██║███████║██║ █╗ ██║███████║██████╔╝█████╗ + * ╚██╗ ██╔╝██║╚██╔╝██║██╔══██║██║███╗██║██╔══██║██╔══██╗██╔══╝ * ╚████╔╝ ██║ ╚═╝ ██║██║ ██║╚███╔███╔╝██║ ██║██║ ██║███████╗ * ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ - * + * * C++ VM detection library - * + * * =============================================================== * - * This is the main CLI code, which demonstrates the majority + * This is the main CLI code, which demonstrates the majority * of the library's capabilities while also providing as a * practical and general VM detection tool for everybody to use - * + * * =============================================================== - * + * * - Made by: @kernelwernel (https://github.com/kernelwernel) * - Co-developed by: Requiem (https://github.com/NotRequiem) * - Repository: https://github.com/kernelwernel/VMAware * - License: MIT - */ + */ #include #include #if (defined(__GNUC__) || defined(__linux__)) - #define CLI_LINUX 1 +#define CLI_LINUX 1 #else - #define CLI_LINUX 0 +#define CLI_LINUX 0 #endif #if (defined(__APPLE__) || defined(__APPLE_CPP__) || defined(__MACH__) || defined(__DARWIN)) - #define CLI_APPLE 1 - #include +#define CLI_APPLE 1 +#include #else - #define CLI_APPLE 0 +#define CLI_APPLE 0 #endif #if (defined(_MSC_VER) || defined(_WIN32) || defined(_WIN64) || defined(__MINGW32__)) - #define CLI_WINDOWS 1 - #define WIN32_LEAN_AND_MEAN - #define NOMINMAX +#define CLI_WINDOWS 1 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX #else - #define CLI_WINDOWS 0 +#define CLI_WINDOWS 0 #endif #include "vmaware.hpp" @@ -54,14 +54,14 @@ constexpr const char* date = "April 2026"; std::string bold = "\033[1m"; std::string underline = "\033[4m"; std::string ansi_exit = "\x1B[0m"; -std::string red = "\x1B[38;2;239;75;75m"; +std::string red = "\x1B[38;2;239;75;75m"; std::string orange = "\x1B[38;2;255;180;5m"; std::string green = "\x1B[38;2;94;214;114m"; std::string red_orange = "\x1B[38;2;247;127;40m"; std::string green_orange = "\x1B[38;2;174;197;59m"; std::string grey = "\x1B[38;2;108;108;108m"; -using u8 = std::uint8_t; +using u8 = std::uint8_t; using u16 = std::uint16_t; using u32 = std::uint32_t; using u64 = std::uint64_t; @@ -104,7 +104,7 @@ std::string detected = ("[ " + green + "DETECTED" + ansi_exit + " ]"); std::string not_detected = ("[" + red + "NOT DETECTED" + ansi_exit + "]"); std::string no_support = ("[ " + grey + "NO SUPPORT" + ansi_exit + " ]"); std::string no_perms = ("[" + grey + " NO PERMS " + ansi_exit + "]"); -std::string note = ("[ NOTE ]"); +std::string note = ("[ NOTE ]"); std::string disabled = ("[" + grey + " DISABLED " + ansi_exit + "]"); #if (CLI_WINDOWS) @@ -324,8 +324,8 @@ static std::string compute_self_sha256() { #endif [[noreturn]] static void help(void) { - std::cout << -R"(Usage: + std::cout << + R"(Usage: vmaware [option] [extra] (do not run with any options if you want the full summary) @@ -359,14 +359,14 @@ R"(Usage: [[noreturn]] static void version(void) { std::cout << "vmaware " << "v" << ver << " (" << date << ")\n\n" << - "Derived project of VMAware library at https://github.com/kernelwernel/VMAware\n" - "License MIT:.\n" << - "This is free software: you are free to change and redistribute it.\n" << - "There is NO WARRANTY, to the extent permitted by law.\n" << - - "Developed and maintained by kernelwernel and Requiem,\n" << - "see https://github.com/kernelwernel and https://github.com/NotRequiem\n" << - "For any inquiries, contact us on Discord at shenzken or kr.nl, or email us at jeanruyv@gmail.com\n"; + "Derived project of VMAware library at https://github.com/kernelwernel/VMAware\n" + "License MIT:.\n" << + "This is free software: you are free to change and redistribute it.\n" << + "There is NO WARRANTY, to the extent permitted by law.\n" << + + "Developed and maintained by kernelwernel and Requiem,\n" << + "see https://github.com/kernelwernel and https://github.com/NotRequiem\n" << + "For any inquiries, contact us on Discord at shenzken or kr.nl, or email us at jeanruyv@gmail.com\n"; std::exit(0); } @@ -380,18 +380,20 @@ static const char* color(const u8 score, const bool is_hardened) { } if (arg_bitset.test(DYNAMIC)) { - if (score == 0) { return red.c_str(); } - else if (score <= 12) { return red.c_str(); } - else if (score <= 25) { return red_orange.c_str(); } - else if (score < 50) { return red_orange.c_str(); } - else if (score <= 62) { return orange.c_str(); } - else if (score <= 75) { return green_orange.c_str(); } - else if (score < 100) { return green.c_str(); } + if (score == 0) { return red.c_str(); } + else if (score <= 12) { return red.c_str(); } + else if (score <= 25) { return red_orange.c_str(); } + else if (score < 50) { return red_orange.c_str(); } + else if (score <= 62) { return orange.c_str(); } + else if (score <= 75) { return green_orange.c_str(); } + else if (score < 100) { return green.c_str(); } else if (score == 100) { return green.c_str(); } - } else { + } + else { if (score == 100) { return green.c_str(); - } else { + } + else { return red.c_str(); } } @@ -400,7 +402,7 @@ static const char* color(const u8 score, const bool is_hardened) { } [[noreturn]] static void brand_list() { - std::cout << + std::cout << R"(VirtualBox VMware VMware Express @@ -479,13 +481,13 @@ static const char* color(const u8 score, const bool is_hardened) { static bool is_admin() { #if (CLI_LINUX) - const uid_t uid = getuid(); + const uid_t uid = getuid(); const uid_t euid = geteuid(); const bool is_root = ( - (uid != euid) || + (uid != euid) || (euid == 0) - ); + ); return is_root; #elif (WINDOWS) @@ -516,7 +518,7 @@ static bool are_perms_required(const VM::enum_flags flag) { case VM::DMESG: case VM::QEMU_USB: case VM::KMSG: - case VM::SMBIOS_VM_BIT: + case VM::SMBIOS_VM_BIT: case VM::NVRAM: return true; default: return false; } @@ -536,32 +538,32 @@ static bool is_unsupported(VM::enum_flags flag) { if ( (flag >= VM::HYPERVISOR_BIT) && (flag <= VM::KGT_SIGNATURE) - ) { + ) { return false; } - #if (CLI_LINUX) - return ( - (flag >= VM::LINUX_START) && - (flag <= VM::LINUX_END) +#if (CLI_LINUX) + return ( + (flag >= VM::LINUX_START) && + (flag <= VM::LINUX_END) ); - #elif (CLI_WINDOWS) - return ( - (flag >= VM::WINDOWS_START) && - (flag <= VM::WINDOWS_END) +#elif (CLI_WINDOWS) + return ( + (flag >= VM::WINDOWS_START) && + (flag <= VM::WINDOWS_END) ); - #elif (APPLE) - return ( - (flag >= VM::MACOS_START) && - (flag <= VM::MACOS_END) +#elif (APPLE) + return ( + (flag >= VM::MACOS_START) && + (flag <= VM::MACOS_END) ); - #else - return true; - #endif +#else + return true; +#endif } // just a simple string replacer -static void replace(std::string &text, const std::string &original, const std::string &new_brand) { +static void replace(std::string& text, const std::string& original, const std::string& new_brand) { size_t start_pos = 0; while ((start_pos = text.find(original, start_pos)) != std::string::npos) { text.replace(start_pos, original.length(), new_brand); @@ -734,7 +736,7 @@ static const char* get_vm_description(const std::string& vm_brand) { using RtlInitUnicodeString_t = VOID(__stdcall*)(PUNICODE_STRING, PCWSTR); #pragma warning(push) - #pragma warning(disable:4191) +#pragma warning(disable:4191) auto pRtlInitUnicodeString = reinterpret_cast( GetProcAddress(ntdll, "RtlInitUnicodeString")); auto pNtCreateFile = reinterpret_cast( @@ -801,7 +803,8 @@ static void checker(const VM::enum_flags flag, const char* message) { if (is_unsupported(flag)) { unsupported_count++; - } else { + } + else { supported_count++; } @@ -826,7 +829,8 @@ static void checker(const VM::enum_flags flag, const char* message) { if (result) { std::cout << detected << bold << " Checking " << message << "..." << enum_name << ansi_exit << "\n"; - } else { + } + else { std::cout << not_detected << " Checking " << message << "..." << enum_name << ansi_exit << "\n"; } } @@ -838,7 +842,8 @@ static void checker(const std::function& func, const char* message) { #if (!CLI_WINDOWS) if (arg_bitset.test(VERBOSE)) { unsupported_count++; - } else { + } + else { supported_count++; } #else @@ -856,8 +861,8 @@ static void checker(const std::function& func, const char* message) { (result ? bold : "") << " Checking " << message << - "..." << - (result ? ansi_exit : "") << + "..." << + (result ? ansi_exit : "") << "\n"; } @@ -869,24 +874,24 @@ const bool is_anyrun = (is_anyrun_directory || is_anyrun_driver); static void general( - const VM::enum_flags high_threshold, + const VM::enum_flags high_threshold, const VM::enum_flags all, const VM::enum_flags dynamic ) { bool notes_enabled = false; - + if (arg_bitset.test(NO_ANSI)) { detected = ("[ DETECTED ]"); not_detected = ("[NOT DETECTED]"); no_support = ("[ NO SUPPORT ]"); no_perms = ("[ NO PERMS ]"); - note = ("[ NOTE ]"); + note = ("[ NOTE ]"); disabled = ("[ DISABLED ]"); - + bold = ""; underline = ""; ansi_exit = ""; - red = ""; + red = ""; orange = ""; green = ""; red_orange = ""; @@ -896,19 +901,20 @@ static void general( if (arg_bitset.test(NOTES)) { notes_enabled = false; - } else { + } + else { notes_enabled = true; } - #if (CLI_LINUX) - if (notes_enabled && !is_admin()) { - std::cout << note << " Running under root might give better results\n"; - } - #elif (CLI_WINDOWS) - if (!is_admin()) { - std::cout << note << " Not running as admin, some technique may not run\n"; - } - #endif +#if (CLI_LINUX) + if (notes_enabled && !is_admin()) { + std::cout << note << " Running under root might give better results\n"; + } +#elif (CLI_WINDOWS) + if (!is_admin()) { + std::cout << note << " Not running as admin, some technique may not run\n"; + } +#endif const auto t1 = std::chrono::high_resolution_clock::now(); @@ -988,7 +994,7 @@ static void general( checker(VM::ACPI_SIGNATURE, "ACPI device signatures"); checker(VM::TRAP, "hypervisor interception"); checker(VM::UD, "undefined exceptions"); - checker(VM::BLOCKSTEP, "single step with trap flag"); + checker(VM::INTERRUPT_SHADOW, "interrupt shadows"); checker(VM::DBVM, "DBVM hypervisor"); checker(VM::BOOT_LOGO, "boot logo"); checker(VM::MAC_SYS, "system profiler"); @@ -1021,9 +1027,9 @@ static void general( } const bool is_red = ( - (brand == VM::brands::NULL_BRAND) || + (brand == VM::brands::NULL_BRAND) || (brand == VM::brands::HYPERV_ROOT) - ); + ); std::cout << bold << "VM brand: " << ansi_exit << (is_red ? red : green) << brand << ansi_exit << "\n"; } @@ -1041,7 +1047,8 @@ static void general( if (type == "Unknown" || type == "Host machine") { current_color = red; - } else { + } + else { current_color = green; } @@ -1103,7 +1110,8 @@ static void general( if (vm.is_hardened) { std::cout << green << "likely" << ansi_exit << "\n"; - } else { + } + else { std::cout << grey << "unlikely" << ansi_exit << "\n"; } } @@ -1127,12 +1135,12 @@ static void general( // sha256 output (debug) { - #ifdef __VMAWARE_DEBUG__ - const std::string hash = compute_self_sha256(); - if (!hash.empty()) { - std::cout << "SHA256: " << hash << '\n'; - } - #endif +#ifdef __VMAWARE_DEBUG__ + const std::string hash = compute_self_sha256(); + if (!hash.empty()) { + std::cout << "SHA256: " << hash << '\n'; + } +#endif } @@ -1149,7 +1157,7 @@ static void general( // it harder to read. Kinda like how this comment you're reading is // structured by breaking the lines in a clean and organised way. const u8 max_line_length = 60; - + std::vector divided_description; std::istringstream stream(description); @@ -1167,11 +1175,13 @@ static void general( if (char_count <= 60) { continue; - } else { + } + else { if ((static_cast(char_count) - 1) >= (static_cast(max_line_length) + 3)) { it = divided_description.insert(it + 1, "\n"); char_count = it->length() + 1; - } else { + } + else { continue; } } @@ -1215,11 +1225,11 @@ static void general( // finishing touches with notes if (notes_enabled) { if (vm.detected_count != 0) { - std::cout << - note << + std::cout << + note << " If you found a false positive, please make sure to create\n \ an issue at https://github.com/kernelwernel/VMAware/issues\n\n"; - // ^ do not modify the space above + // ^ do not modify the space above } } @@ -1307,7 +1317,7 @@ int main(int argc, char* argv[]) { return 0; } - static constexpr std::array, 32> table {{ + static constexpr std::array, 32> table{ { { "-h", HELP }, { "-v", VERSION }, { "-a", ALL }, @@ -1340,7 +1350,7 @@ int main(int argc, char* argv[]) { { "--no-ansi", NO_ANSI }, { "--detected-only", DETECTED_ONLY }, { "--json", JSON } - }}; + } }; std::string potential_null_arg = ""; const char* potential_output_arg = "results.json"; @@ -1350,7 +1360,7 @@ int main(int argc, char* argv[]) { auto it = std::find_if(table.cbegin(), table.cend(), [&](const std::pair& p) { return (std::strcmp(p.first, arg_string) == 0); - }); + }); if (it == table.end()) { if (arg_bitset.test(OUTPUT)) { @@ -1359,11 +1369,13 @@ int main(int argc, char* argv[]) { potential_output_arg = arg_string; } arg_bitset.set(OUTPUT, false); - } else { + } + else { arg_bitset.set(NULL_ARG); potential_null_arg = arg_string; } - } else { + } + else { arg_bitset.set(it->second); } } @@ -1377,7 +1389,7 @@ int main(int argc, char* argv[]) { if (arg_bitset.test(HELP)) { help(); - } + } if (arg_bitset.test(VERSION)) { version(); @@ -1405,7 +1417,7 @@ int main(int argc, char* argv[]) { static_cast(arg_bitset.test(BRAND)) + static_cast(arg_bitset.test(TYPE)) + static_cast(arg_bitset.test(CONCLUSION)) - ); + ); const VM::enum_flags high_threshold = (arg_bitset.test(HIGH_THRESHOLD) ? VM::HIGH_THRESHOLD : VM::NULL_ARG); const VM::enum_flags all = (arg_bitset.test(ALL) ? VM::ALL : VM::NULL_ARG); @@ -1435,7 +1447,7 @@ int main(int argc, char* argv[]) { if (arg_bitset.test(BRAND)) { std::string brand = VM::brand(VM::MULTIPLE, high_threshold, all, dynamic); - + if (is_anyrun && (brand == VM::brands::NULL_BRAND)) { brand = "ANY.RUN"; } diff --git a/src/vmaware.hpp b/src/vmaware.hpp index 03aaacbd..0ffa5a2d 100644 --- a/src/vmaware.hpp +++ b/src/vmaware.hpp @@ -586,7 +586,7 @@ struct VM { CUCKOO_PIPE, TRAP, UD, - BLOCKSTEP, + INTERRUPT_SHADOW, DBVM, KERNEL_OBJECTS, NVRAM, @@ -10460,31 +10460,46 @@ struct VM { /** * @brief Check if a hypervisor does not properly restore the interruptibility state after a VM-exit * @category Windows - * @implements VM::BLOCKSTEP + * @implements VM::INTERRUPT_SHADOW */ - [[nodiscard]] static bool blockstep() { - volatile int saw_single_step = 0; + [[nodiscard]] static bool interrupt_shadow() { + volatile ULONG_PTR trap_ip = 0; #if (x86_32) && !(CLANG || GCC) + ULONG_PTR baremetal_target_ip = 0; + __try { __asm { + mov dword ptr[baremetal_target_ip], offset baremetal_target // get exact baremetal trap target address dynamically + push ebx + xor eax, eax + mov ax, ss pushfd or dword ptr[esp], 0x100 // set TF popfd - xor eax, eax - mov ax, ss mov ss, ax // this blocks any debug exception for exactly one instruction cpuid - nop // TF's single-step should fire here on baremetal except on a few buggy processors - pushfd - and dword ptr[esp], 0xFFFFFEFF - popfd + baremetal_target : + pop ebx // baremetal delays the #DB until here due to shadow suppression + nop + pushfd + and dword ptr[esp], 0xFFFFFEFF + popfd } } - __except (GetExceptionCode() == EXCEPTION_SINGLE_STEP ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { - saw_single_step = 1; + __except (GetExceptionCode() == EXCEPTION_SINGLE_STEP ? + ( + trap_ip = GetExceptionInformation()->ContextRecord->Eip, + GetExceptionInformation()->ContextRecord->EFlags &= ~0x100, // clear TF so execution resumes cleanly and stack restores + EXCEPTION_CONTINUE_EXECUTION + ) : EXCEPTION_CONTINUE_SEARCH) { + // unreachable due to continue execution } - return (saw_single_step == 0); + + // 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(); if (!ntdll) return false; @@ -10504,22 +10519,22 @@ struct VM { 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 - // like, 0x53 maps to push ebx in 32-bit and push rbx in 64-bit + // 0x53 maps to push ebx in 32-bit and push rbx in 64-bit static constexpr u8 blockstep_opcodes[] = { - 0x53, // push rbx/ebx (preserve non-volatile register against cpuid) - 0x9C, // pushfq/pushfd - 0x81, 0x0C, 0x24, 0x00, 0x01, 0x00, 0x00, // or dword ptr [rsp/esp], 0x100 - 0x9D, // popfq/popfd - 0x31, 0xC0, // xor eax, eax - 0x8C, 0xD0, // mov ax, ss - 0x8E, 0xD0, // mov ss, ax - 0x0F, 0xA2, // cpuid - 0x90, // nop - 0x9C, // pushfq/pushfd - 0x81, 0x24, 0x24, 0xFF, 0xFE, 0xFF, 0xFF, // and dword ptr [rsp/esp], 0xFFFFFEFF - 0x9D, // popfq/popfd - 0x5B, // pop rbx/ebx - 0xC3 // ret + 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 + 0x81, 0x24, 0x24, 0xFF, 0xFE, 0xFF, 0xFF, // 21: and dword ptr [rsp/esp], 0xFFFFFEFF + 0x9D, // 28: popfq/popfd + 0xC3 // 29: ret }; const HANDLE current_process = reinterpret_cast(-1LL); @@ -10535,20 +10550,34 @@ struct VM { ULONG old_protection = 0; NTSTATUS st = nt_protect(current_process, &base, ®ion_size, PAGE_EXECUTE_READ, &old_protection); + // expect the trap explicitly at offset +18 (pop rbx) because of shadow suppression on real hardware + const ULONG_PTR baremetal_target_ip = reinterpret_cast(base) + 18; + if (NT_SUCCESS(st)) { nt_flush(current_process, base, region_size); __try { reinterpret_cast(base)(); } - __except (GetExceptionCode() == EXCEPTION_SINGLE_STEP ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { - saw_single_step = 1; + __except (GetExceptionCode() == EXCEPTION_SINGLE_STEP ? + ( + #if (x86_64) + trap_ip = GetExceptionInformation()->ContextRecord->Rip, + #else + trap_ip = GetExceptionInformation()->ContextRecord->Eip, + #endif + GetExceptionInformation()->ContextRecord->EFlags &= ~0x100, // to avoid infinite looping + EXCEPTION_CONTINUE_EXECUTION + ) : EXCEPTION_CONTINUE_SEARCH) { + // unreachable due to continue execution } } region_size = 0; nt_free(current_process, &base, ®ion_size, MEM_RELEASE); - return NT_SUCCESS(st) && (saw_single_step == 0); + // 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); #else return false; #endif @@ -13606,7 +13635,7 @@ struct VM { case ACPI_SIGNATURE: return "ACPI_SIGNATURE"; case TRAP: return "TRAP"; case UD: return "UNDEFINED_INSTRUCTION"; - case BLOCKSTEP: return "BLOCKSTEP"; + case INTERRUPT_SHADOW: return "INTERRUPT_SHADOW"; case DBVM: return "DBVM_HYPERCALL"; case BOOT_LOGO: return "BOOT_LOGO"; case MAC_SYS: return "MAC_SYS"; @@ -14164,7 +14193,7 @@ std::array VM::core::technique_table = [ {VM::EIP_OVERFLOW, {100, VM::eip_overflow}}, {VM::HYPERVISOR_HOOK, {100, VM::hypervisor_hook}}, {VM::POPF, {100, VM::popf}}, - {VM::BLOCKSTEP, {100, VM::blockstep}}, + {VM::INTERRUPT_SHADOW, {100, VM::interrupt_shadow}}, {VM::MSR, {100, VM::msr}}, {VM::EDID, {100, VM::edid}}, {VM::VIRTUAL_PROCESSORS, {100, VM::virtual_processors}}, From 053779623268b010133307a0ab5667aeebfd05ed Mon Sep 17 00:00:00 2001 From: Requiem Date: Wed, 6 May 2026 03:13:05 +0200 Subject: [PATCH 3/3] Revert "added kvm enlightment merge logic with hyper-v" This reverts commit e5faa5b0d09b8342c67843fa62a3ff5058d766e8. --- src/vmaware.hpp | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/vmaware.hpp b/src/vmaware.hpp index 96c1fa11..0ffa5a2d 100644 --- a/src/vmaware.hpp +++ b/src/vmaware.hpp @@ -4648,11 +4648,6 @@ struct VM { merge(brand_enum::QEMU, brand_enum::KVM_HYPERV, brand_enum::QEMU_KVM_HYPERV); merge(brand_enum::QEMU_KVM, brand_enum::KVM_HYPERV, brand_enum::QEMU_KVM_HYPERV); - merge(brand_enum::HYPERV_VPC, brand_enum::KVM_HYPERV, brand_enum::KVM_HYPERV); - merge(brand_enum::HYPERV, brand_enum::KVM_HYPERV, brand_enum::KVM_HYPERV); - merge(brand_enum::HYPERV_VPC, brand_enum::QEMU_KVM_HYPERV, brand_enum::QEMU_KVM_HYPERV); - merge(brand_enum::HYPERV, brand_enum::QEMU_KVM_HYPERV, brand_enum::QEMU_KVM_HYPERV); - triple_merge(brand_enum::QEMU, brand_enum::KVM, brand_enum::KVM_HYPERV, brand_enum::QEMU_KVM_HYPERV); merge(brand_enum::VMWARE, brand_enum::VMWARE_FUSION, brand_enum::VMWARE_FUSION); @@ -5763,16 +5758,9 @@ struct VM { return result; }; - bool is_intel = cpu::is_intel(); - - if (is_intel) { - // SERIALIZE (CPUID leaf 7, subleaf 0, EDX bit 14) requires Ice Lake or newer. - // Older Intel CPUs in Azure/Hyper-V environments crash with STATUS_ILLEGAL_INSTRUCTION. - u32 l7_eax = 0, l7_ebx = 0, l7_ecx = 0, l7_edx = 0; - cpu::cpuid(l7_eax, l7_ebx, l7_ecx, l7_edx, 7, 0); - if (!(l7_edx & (1u << 14))) { - is_intel = false; - } + bool is_intel = true; + if (!cpu::is_intel()) { + is_intel = false; } const HANDLE current_thread = reinterpret_cast(-2LL);