From 12fb67ff6c4fed8d3c44444b8dfcbc59728b0e39 Mon Sep 17 00:00:00 2001 From: Existential-Kernel Date: Wed, 20 Dec 2023 06:31:00 +0000 Subject: [PATCH] added bochs and major improvements --- docs/documentation.md | 15 +++- src/vmaware.hpp | 194 +++++++++++++++++++++++++++++++++--------- 2 files changed, 167 insertions(+), 42 deletions(-) diff --git a/docs/documentation.md b/docs/documentation.md index 7214489..5f5cbcb 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -64,6 +64,10 @@ This will essentially return the VM brand as a `std::string`. The brand string r - `JoeBox` - `Thread Expert` - `CW Sandbox` +- `SunBelt` +- `Comodo` +- `Bochs` + If none were detected, it will return `Unknown`. It's often not going to produce a satisfying result due to technical difficulties with accomplishing this, on top of being highly dependant on what mechanisms detected a VM. Don't rely on this function too much. @@ -131,7 +135,7 @@ VMAware provides a convenient way to not only check for VMs, but also have the f | `VM::USER` | Match the username for any defaulted ones | Windows | 35% | | | `VM::DLL` | Match for VM-specific DLLs | Windows | 50% | | | `VM::REGISTRY` | Look throughout the registry for all sorts of VMs | Windows | 75% | | -| `VM::SUNBELT` | Detect for Sunbelt technology | Windows | 10% | | +| `VM::SUNBELT_VM` | Detect for Sunbelt technology | Windows | 10% | | | `VM::WINE_CHECK` | Find for a Wine-specific file | Windows | 85% | | | `VM::BOOT` | Analyse the OS uptime | Yes | 5% | | | `VM::VM_FILES` | Find if any VM-specific files exists | Windows | 20% | | @@ -145,11 +149,14 @@ VMAware provides a convenient way to not only check for VMs, but also have the f | `VM::LINUX_USER_HOST` | Check for default VM username and hostname for linux | Linux | 35% | | | `VM::VBOX_WINDOW_CLASS` | Check for the window class for VirtualBox | Windows | 10% | | | `VM::WINDOWS_NUMBER` | Check top-level default window level | Windows | 20% | | -| `VM::GAMARUE` | Check for Gamarue ransomeware technique which compares VM-specific Window product IDs | Windows | 40% | | +| `VM::GAMARUE` | Check for Gamarue ransomware technique which compares VM-specific Window product IDs | Windows | 40% | | | `VM::VMID_0X4` | Check if the CPU manufacturer ID matches that of a VM brand with leaf 0x40000000 | Yes | 100% | | | `VM::VPC_BACKDOOR` | Check for semi-documented detection mechanism for Virtual PC | Windows | 70% | | -| -| +| `VM::PARALLELS_VM` | Check for indications of Parallels VM | [TODO_ADD_THIS_SHIT] | 50% | | +| `VM::SPEC_RDTSC` | Check for RDTSC technique with speculative execution | [TODO_ADD_THIS_SHIT] | 80% | | +| `VM::LOADED_DLLS` | Check for DLLs of multiple VM brands | Windows | 75% | | +| `VM::QEMU_BRAND` | Check for QEMU CPU brand with cpuid | Yes | 100% | | + # Non-technique flags | Flag | Description | diff --git a/src/vmaware.hpp b/src/vmaware.hpp index e9bfbaa..5eb3f7d 100644 --- a/src/vmaware.hpp +++ b/src/vmaware.hpp @@ -281,6 +281,7 @@ struct VM { static constexpr const char* CWSANDBOX = "CW Sandbox"; static constexpr const char* COMODO = "Comodo"; static constexpr const char* SUNBELT = "SunBelt"; + static constexpr const char* BOCHS = "Bochs"; // VM scoreboard table specifically for VM::brand() #if (MSVC) @@ -797,6 +798,8 @@ struct VM { PARALLELS_VM = 1ULL << 43, SPEC_RDTSC = 1ULL << 44, LOADED_DLLS = 1ULL << 45, + QEMU_BRAND = 1ULL << 46, + BOCHS_CPU = 1ULL << 47, // __UNIQUE_LABEL, ADD YOUR UNIQUE FUNCTION FLAG VALUE ABOVE HERE @@ -906,6 +909,51 @@ struct VM { return false; } + + [[nodiscard]] static std::string get_cpu_brand() { + if (!cpuid_supported) { + return ""; + } + + #if (!x86) + return ""; + #else + // maybe not necessary but whatever + #if (LINUX) + if (!__get_cpuid_max(0x80000004, nullptr)) { + return ""; + } + #endif + + std::array buffer{}; + constexpr std::size_t buffer_size = sizeof(i32) * buffer.size(); + std::array charbuffer{}; + + constexpr std::array ids = { + leaf::brand1, + leaf::brand2, + leaf::brand3 + }; + + std::string brand = ""; + + for (const u32 &id : ids) { + cpuid(buffer.at(0), buffer.at(1), buffer.at(2), buffer.at(3), id); + + std::memcpy(charbuffer.data(), buffer.data(), buffer_size); + + const char* convert = charbuffer.data(); + brand += convert; + } + + #ifdef __VMAWARE_DEBUG__ + debug("BRAND: ", "cpu brand = ", brand); + #endif + + return brand; + #endif + } + private: static constexpr u64 DEFAULT = (~(CURSOR) & ALL); @@ -964,38 +1012,8 @@ struct VM { #if (!x86) return false; #else - // maybe not necessary but whatever - #if (LINUX) - if (!__get_cpuid_max(0x80000004, nullptr)) { - return false; - } - #endif - - std::array buffer{}; - constexpr std::size_t buffer_size = sizeof(i32) * buffer.size(); - std::array charbuffer{}; - - constexpr std::array ids = { - leaf::brand1, - leaf::brand2, - leaf::brand3 - }; - - std::string brand = ""; - - for (const u32 &id : ids) { - cpuid(buffer.at(0), buffer.at(1), buffer.at(2), buffer.at(3), id); - - std::memcpy(charbuffer.data(), buffer.data(), buffer_size); - - const char* convert = charbuffer.data(); - brand += convert; - } - - #ifdef __VMAWARE_DEBUG__ - debug("BRAND: ", "cpu brand = ", brand); - #endif - + std::string brand = get_cpu_brand(); + // TODO: might add more potential keywords, be aware that it could (theoretically) cause false positives constexpr std::array vmkeywords { "qemu", "kvm", "virtual", "vm", @@ -1012,7 +1030,7 @@ struct VM { #ifdef __VMAWARE_DEBUG__ if (match) { - debug("BRAND: ", "match = ", vmkeywords.at(i)); + debug("BRAND_KEYWORDS: ", "match = ", vmkeywords.at(i)); } #endif @@ -1020,14 +1038,42 @@ struct VM { } #ifdef __VMAWARE_DEBUG__ - debug("BRAND: ", "matches: ", static_cast(match_count)); + debug("BRAND_KEYWORDS: ", "matches: ", static_cast(match_count)); #endif return (match_count >= 1); #endif } catch (...) { #ifdef __VMAWARE_DEBUG__ - debug("BRAND: catched error, returned false"); + debug("BRAND_KEYWORDS: catched error, returned false"); + #endif + return false; + } + + + /** + * @brief Match for QEMU CPU brand + * @category x86 + */ + [[nodiscard]] static bool cpu_brand_qemu() try { + if (!cpuid_supported || disabled(QEMU_BRAND)) { + return false; + } + + #if (!x86) + return false; + #else + std::string brand = get_cpu_brand(); + + if (brand == "QEMU Virtual CPU") { + return add(QEMU); + } + + return false; + #endif + } catch (...) { + #ifdef __VMAWARE_DEBUG__ + debug("QEMU_BRAND: catched error, returned false"); #endif return false; } @@ -3234,7 +3280,7 @@ struct VM { #else HMODULE hDll; - constexpr std::array szDlls = { + constexpr std::array szDlls = { "avghookx.dll", // AVG "avghooka.dll", // AVG "snxhk.dll", // Avast @@ -3274,6 +3320,74 @@ struct VM { } + /** + * @brief Do various Bochs-related CPU stuff + * @category x86 + * @note The idea for the CPU strings are from pafish + * @link https://github.com/a0rtega/pafish/blob/master/pafish/bochs.c + */ + [[nodiscard]] static bool bochs_cpu() try { + if (!cpuid_supported || disabled(BOCHS_CPU)) { + return false; + } + + #if (!x86) + return false; + #else + constexpr u32 intel_ecx = 0x6c65746e; + constexpr u32 amd_ecx = 0x69746e65; + + bool intel = false; + bool amd = false; + + u32 unused, ecx = 0; + cpuid(unused, unused, ecx, unused, 0); + + if (ecx == intel_ecx) { intel = true; } + if (ecx == amd_ecx) { amd = true; } + + if (!(intel ^ amd)) { return false; } + + const std::string brand = get_cpu_brand(); + + if (intel) { + // technique 1: not a valid brand + if (brand == " Intel(R) Pentium(R) 4 CPU ") { return add(BOCHS); } + } else if (amd) { + // technique 2: "processor" should be "Processor" instead in AMD + if (brand == "AMD Athlon(tm) processor") { return add(BOCHS); } + + // technique 3: Check for AMD easter egg for K7 and K8 CPUs + u32 eax = 0; + cpuid(eax, unused, unused, unused, 1); + + const u32 family = ((eax >> 8) & 0xF); + + if (family != 6 && family != 15) { // AMD K7 = 6, AMD K8 = 15 + return false; + } + + u32 ecx_bochs = 0; + constexpr u32 easter_egg_leaf = 0x8fffffff; + + cpuid(unused, unused, ecx_bochs, unused, easter_egg_leaf); + + if (ecx_bochs == 0) { + return add(BOCHS); + } + } + + return false; + #endif + } catch (...) { + #ifdef __VMAWARE_DEBUG__ + debug("BOCHS_CPU:", "catched error, returned false"); + #endif + return false; + } + + + // __TECHNIQUE_LABEL, label for adding techniques above this point @@ -3502,7 +3616,9 @@ struct VM { { VM::JOEBOX, 0 }, { VM::THREADEXPERT, 0 }, { VM::CWSANDBOX, 0 }, - { VM::COMODO, 0 } + { VM::COMODO, 0 }, + { VM::SUNBELT, 0 }, + { VM::BOCHS, 0 } }; @@ -3580,7 +3696,9 @@ const std::map VM::table = { { VM::VPC_BACKDOOR, { 70, VM::vpc_backdoor }}, { VM::PARALLELS_VM, { 50, VM::parallels }}, { VM::SPEC_RDTSC, { 80, VM::speculative_rdtsc }}, - { VM::LOADED_DLLS, { 75, VM::loaded_dlls }} + { VM::LOADED_DLLS, { 75, VM::loaded_dlls }}, + { VM::QEMU_BRAND, { 100, VM::cpu_brand_qemu }}, + { VM::BOCHS_CPU, { 95, VM::bochs_cpu }} // __TABLE_LABEL, add your technique above // { VM::YOUR_FUNCTION, { POINTS, FUNCTION POINTER }}