diff --git a/CMakeLists.txt b/CMakeLists.txt index 8676850..e8264a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,11 +16,10 @@ set(CMAKE_EXE_LINKER_FLAGS "-lstdc++ -lm") if (MSVC) set(CMAKE_CXX_FLAGS "/W4 /std:c++20") else() - set(CMAKE_CXX_FLAGS "-std=c++20 -Wextra -Wall -Wextra -Wconversion -Wdouble-promotion -Wno-unused-parameter -Wno-unused-function -Wno-sign-conversion") + set(CMAKE_CXX_FLAGS "-Wextra -Wall -Wextra -Wconversion -Wdouble-promotion -Wno-unused-parameter -Wno-unused-function -Wno-sign-conversion") endif() - # fetch and set build type set(available_build_types Debug Release) if(NOT CMAKE_BUILD_TYPE) diff --git a/README.md b/README.md index 0fd7405..7e1686f 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ sudo make install ## Documentation 📒 -You can view the full docs [here](docs/documentation.md). Trust me, it's not too intimidating +You can view the full docs [here](docs/documentation.md). Trust me, it's not too intimidating. ## Q&A ❓ diff --git a/TODO.md b/TODO.md index 1f789d4..b5f4a18 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,7 @@ - revise sidt check - analyse the UUID check technique's efficiency -- create a technique table so i don't have to manually add them for VM::detect() and VM::check() +- update doc without technique column +- fix c++11 debug function param error - ~~make a flagcheck function~~ - ~~create a standard cpuid function (replace __cpuidex bc wine)~~ @@ -8,6 +9,7 @@ - ~~convert makefile to cmake~~ - ~~fix the inconsistent naming~~ - ~~maybe add github templates and other ci/cd stuff~~ +- ~~create a technique table so i don't have to manually add them for VM::detect() and VM::check()~~ # distant plans diff --git a/docs/documentation.md b/docs/documentation.md index be0c87b..4d8fa34 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -42,7 +42,7 @@ int main() { # `VM::brand()` -This will essentially return the VM brand as a std::string_view if it detected a VM. The possible brand string return values are: `VMware`, `VirtualBox`, `KVM`, `bhyve`, `QEMU`, `Microsoft Hyper-V`, `Microsoft x86-to-ARM`, `Parallels`, `Xen HVM`, `ACRN`, `QNX hypervisor`, `Hybrid Analysis`, `Sandboxie`, `Docker`, `Wine`, and `Virtual Apple`. If none were detected, it will return `Unknown`. +This will essentially return the VM brand as a std::string_view (assuming you're using C++17 and above, else it will return a const char*). The possible brand string return values are: `VMware`, `VirtualBox`, `KVM`, `bhyve`, `QEMU`, `Microsoft Hyper-V`, `Microsoft x86-to-ARM`, `Parallels`, `Xen HVM`, `ACRN`, `QNX hypervisor`, `Hybrid Analysis`, `Sandboxie`, `Docker`, `Wine`, `Virtual Apple`, and `Virtual PC`. If none were detected, it will return `Unknown`. ```cpp int main() { @@ -81,37 +81,39 @@ bool result = VM::check(VM::SIDT | VM::RDTSC); VMAware provides a convenient way to not only check for VMs, but also have the flexibility and freedom for the end-user to choose what techniques are used with complete control over what gets executed or not. This is handled with a flag system. -| Technique | Description | Flag alias | Cross-platform? | +| Flag alias | Description | Cross-platform? | Certainty | | --------- | ----------- | ---------- | --------------- | -| VMID | Check if the CPU manufacturer ID matches that of a VM brand | `VM::VMID` | Yes | -| Brand check | Check if the CPU brand string contains any indications of VM keywords | `VM::BRAND` | Yes | -| Hypervisor bit | Check if the hypervisor bit is set (always false on physical CPUs) | `VM::HYPERV_BIT` | Yes | -| 0x4 CPUID | Check if there are any leaf values between 0x40000000 and 0x400000FF that changes the CPUID output | `VM::CPUID_0x4` | Yes | -| Hypervisor length | Check if brand string length is long enough (would be around 2 characters in a host machine while it's longer in a hypervisor) | `VM::HYPERV_STR` | Yes | -| RDTSC check | Benchmark RDTSC and evaluate its speed, usually it's very slow in VMs | `VM::RDTSC` | Linux and Windows | -| SIDT check | Check if SIDT instructions does anything to the interrupt descriptor table | `VM::SIDT` | Linux | -| SIDT 5 check | Check if the 5th byte after sidt is null | `VM::SIDT5` | Linux | -| VMware port | Check if VMware port number 0x5658 is present | `VM::VMWARE_PORT` | Linux and Windows | -| Thread count | Check if there are only 1 or 2 threads, which is a common pattern in VMs with default settings (nowadays physical CPUs should have at least 4 threads for modern CPUs) | `VM::THREADCOUNT` | Yes | -| MAC address match | Check if the system's MAC address matches with preset values for certain VMs | `VM::MAC` | Linux and Windows | -| Check temperature | Check for the presence of CPU temperature sensors (mostly not present in VMs) | `VM::TEMPERATURE` | Linux | -| Check chassis vendor | Check if the chassis has any VM-related keywords | `VM::CVENDOR` | Linux | -| Check chassis type | Check if the chassis type is valid (usually not in VMs) | `VM::CTYPE` | Linux | -| Check docker | Check if any docker-related files are present such as /.dockerenv and /.dockerinit | `VM::DOCKER_CHECK` | Linux | -| Check dmidecode | Get output from dmidecode tool and grep for common VM keywords | `VM::DMIDECODE` | Linux | -| Check dmesg | Get output from dmesg tool and grep for common VM keywords | `VM::DMESG` | Linux | -| Check HWMON | Check if HWMON is present (if not, likely a VM) | `VM::HWMON` | Linux | -| Analyse cursor | Check if cursor isn't active (sign of automated VM environment) | `VM::CURSOR` | Windows | -| Check VMware registry | Look for any VMware-specific registry data | `VM::VMWARE_REG` | Windows | -| Check Vbox registry | Look for any VirtualBox-specific registry data | `VM::VBOX_REG` | Windows | -| Check usernames | Match the username for any defaulted ones | `VM::USER` | Windows | -| Check DLLs | Match for VM-specific DLLs | `VM::DLL` | Windows | -| Check registry | Look throughout the registry for all sorts of VMs | `VM::REGISTRY` | Windows | -| Check Sunbelt | Detect for Sunbelt technology | `VM::SUNBELT` | Windows | -| Check Wine | Find for a Wine-specific file | `VM::WINE_CHECK` | Windows | -| Check boot time | Analyse the OS uptime | `VM::BOOT` | Yes | -| Check VM files | Find if any VM-specific files exists | `VM::VM_FILES` | Windows | -| Check hwmodel | Check if the sysctl for the hwmodel does not contain the "Mac" string | `VM::HWMODEL` | MacOS | +| `VM::VMID` | Check if the CPU manufacturer ID matches that of a VM brand | Yes | 100% | +| `VM::BRAND` | Check if the CPU brand string contains any indications of VM keywords | Yes | 50% | +| `VM::HYPERV_BIT` | Check if the hypervisor bit is set (always false on physical CPUs) | Yes | 95% | +|`VM::CPUID_0x4` | Check if there are any leaf values between 0x40000000 and 0x400000FF that changes the CPUID output | Yes | 70% | +| `VM::HYPERV_STR` | Check if brand string length is long enough (would be around 2 characters in a host machine while it's longer in a hypervisor) | Yes | 45% | +| `VM::RDTSC` | Benchmark RDTSC and evaluate its speed, usually it's very slow in VMs | Linux and Windows | 20% | +| `VM::SIDT` | Check if SIDT instructions does anything to the interrupt descriptor table | Linux | 65% | +| `VM::SIDT5` | Check if the 5th byte after sidt is null | Linux | 45% | +| `VM::VMWARE_PORT` | Check if VMware port number 0x5658 is present | Linux and Windows | 80% | +| `VM::THREADCOUNT` | Check if there are only 1 or 2 threads, which is a common pattern in VMs with default settings (nowadays physical CPUs should have at least 4 threads for modern CPUs) | Yes | 35% | +| `VM::MAC` | Check if the system's MAC address matches with preset values for certain VMs | Linux and Windows | 90% | +| `VM::TEMPERATURE` | Check for the presence of CPU temperature sensors (mostly not present in VMs) | Linux | 15% | +| `VM::SYSTEMD` | Get output from systemd-detect-virt tool | Linux | 70% | +| `VM::CVENDOR` | Check if the chassis has any VM-related keywords | Linux | 65% | +| `VM::CTYPE` | Check if the chassis type is valid (usually not in VMs) | Linux | 10% | +| `VM::DOCKERENV` | Check if any docker-related files are present such as /.dockerenv and /.dockerinit | Linux | 80% | +| `VM::DMIDECODE` | Get output from dmidecode tool and grep for common VM keywords | Linux | 55% | +| `VM::DMESG` | Get output from dmesg tool and grep for common VM keywords | Linux | 55% | +| `VM::HWMON` | Check if HWMON is present (if not, likely a VM) | Linux | 75% | +| `VM::CURSOR` | Check if cursor isn't active (sign of automated VM environment) | Windows | 10% | +| `VM::VMWARE_REG` | Look for any VMware-specific registry data | Windows | 65% | +| `VM::VBOX_REG` | Look for any VirtualBox-specific registry data | Windows | 65% | +| `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::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 | 80% | +| `VM::HWMODEL` | Check if the sysctl for the hwmodel does not contain the "Mac" string | MacOS | 75% | + # Non-technique flags | Flag | Description | diff --git a/src/cli.cpp b/src/cli.cpp index f8dafc7..d8e04b1 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -58,7 +58,7 @@ int main(int argc, char* argv[]) { checker(VM::SYSTEMD, "systemd virtualisation"); checker(VM::CVENDOR, "chassis vendor"); checker(VM::CTYPE, "chassis type"); - checker(VM::DOCKER_CHECK, "Dockerenv"); + checker(VM::DOCKERENV, "Dockerenv"); checker(VM::DMIDECODE, "dmidecode output"); checker(VM::DMESG, "dmesg output"); checker(VM::HWMON, "hwmon presence"); diff --git a/src/vmaware.hpp b/src/vmaware.hpp index 89ff504..1ff8f5d 100644 --- a/src/vmaware.hpp +++ b/src/vmaware.hpp @@ -4,7 +4,7 @@ * ██║ ██║██╔████╔██║███████║██║ █╗ ██║███████║██████╔╝█████╗ * ╚██╗ ██╔╝██║╚██╔╝██║██╔══██║██║███╗██║██╔══██║██╔══██╗██╔══╝ * ╚████╔╝ ██║ ╚═╝ ██║██║ ██║╚███╔███╔╝██║ ██║██║ ██║███████╗ - * ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ v0.1 + * ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ beta version * * A C++ VM detection library * @@ -302,18 +302,18 @@ struct VM { } // for debug output - #ifdef __VMAWARE_DEBUG__ - static inline void debug(auto ...message) noexcept { + static inline void debug(auto ...message) noexcept { + #ifdef __VMAWARE_DEBUG__ constexpr sv black_bg = "\x1B[48;2;0;0;0m", - bold = "\033[1m", - blue = "\x1B[38;2;00;59;193m", - ansiexit = "\x1B[0m"; + bold = "\033[1m", + blue = "\x1B[38;2;00;59;193m", + ansiexit = "\x1B[0m"; std::cout << black_bg << bold << "[" << blue << "DEBUG" << ansiexit << bold << black_bg << "]" << ansiexit << " "; ((std::cout << message),...); std::cout << "\n"; - } - #endif + #endif + } // directly return when adding a brand to the scoreboard for a more succint expression [[nodiscard]] static inline bool add(const sv p_brand) noexcept { @@ -368,24 +368,24 @@ struct VM { SYSTEMD = 1 << 11, CVENDOR = 1 << 12, CTYPE = 1 << 13, - DOCKER_CHECK = 1 << 14, + DOCKERENV = 1 << 14, DMIDECODE = 1 << 15, DMESG = 1 << 16, HWMON = 1 << 17, SIDT5 = 1 << 18, // windows-specific - CURSOR = 1ULL << 40, - VMWARE_REG = 1ULL << 41, - VBOX_REG = 1ULL << 42, - USER = 1ULL << 43, - DLL = 1ULL << 44, - REGISTRY = 1ULL << 45, - SUNBELT = 1ULL << 46, - WINE_CHECK = 1ULL << 47, - BOOT = 1ULL << 48, - VM_FILES = 1ULL << 49, - HWMODEL = 1ULL << 50, + CURSOR = 1 << 19, + VMWARE_REG = 1 << 20, + VBOX_REG = 1 << 21, + USER = 1 << 22, + DLL = 1 << 23, + REGISTRY = 1 << 24, + SUNBELT = 1 << 25, + WINE_CHECK = 1 << 26, + BOOT = 1 << 27, + VM_FILES = 1 << 28, + HWMODEL = 1 << 29, // settings NO_MEMO = 1ULL << 63, @@ -408,9 +408,7 @@ struct VM { return false; #else if (no_cpuid || disabled(VMID)) { - #ifdef __VMAWARE_DEBUG__ - debug("VMID: precondition return called"); - #endif + debug("VMID: precondition return called"); return false; } @@ -470,10 +468,8 @@ struct VM { ss << strconvert(sig_reg[1]); brand = ss.str(); - - #ifdef __VMAWARE_DEBUG__ - debug("VMID: ", brand); - #endif + + debug("VMID: ", brand); const bool found = (std::find(std::begin(IDs), std::end(IDs), brand) != std::end(IDs)); @@ -507,9 +503,7 @@ struct VM { return false; #else if (no_cpuid || disabled(BRAND)) { - #ifdef __VMAWARE_DEBUG__ - debug("BRAND: ", "precondition return called"); - #endif + debug("BRAND: ", "precondition return called"); return false; } @@ -554,9 +548,7 @@ struct VM { matches += std::regex_search(brand, regex); } - #ifdef __VMAWARE_DEBUG__ - debug("BRAND: ", "matches: ", static_cast(matches)); - #endif + debug("BRAND: ", "matches: ", static_cast(matches)); return (matches >= 1); #endif @@ -572,9 +564,7 @@ struct VM { return false; #else if (no_cpuid || disabled(HYPERV_BIT)) { - #ifdef __VMAWARE_DEBUG__ - debug("HYPERV_BIT: precondition return called"); - #endif + debug("HYPERV_BIT: precondition return called"); return false; } @@ -597,9 +587,7 @@ struct VM { return false; #else if (no_cpuid || disabled(CPUID_0x4)) { - #ifdef __VMAWARE_DEBUG__ - debug("CPUID_0X4: precondition return called"); - #endif + debug("CPUID_0X4: precondition return called"); return false; } @@ -626,22 +614,18 @@ struct VM { return false; #else if (disabled(HYPERV_STR)) { - #ifdef __VMAWARE_DEBUG__ - debug("HYPERV_STR: precondition return called"); - #endif + debug("HYPERV_STR: precondition return called"); return false; } char out[sizeof(i32) * 4 + 1] = { 0 }; // e*x size + number of e*x registers + null terminator cpuid((int*)out, leaf::hyperv); - #ifdef __VMAWARE_DEBUG__ - debug("HYPERV_STR: eax: ", static_cast(out[0]), - "\nebx: ", static_cast(out[1]), - "\necx", static_cast(out[2]), - "\nedx", static_cast(out[3]) - ); - #endif + debug("HYPERV_STR: eax: ", static_cast(out[0]), + "\nebx: ", static_cast(out[1]), + "\necx: ", static_cast(out[2]), + "\nedx: ", static_cast(out[3]) + ); return (std::strlen(out + 4) >= 4); #endif @@ -657,9 +641,7 @@ struct VM { return false; #else if (disabled(RDTSC)) { - #ifdef __VMAWARE_DEBUG__ - debug("RDTSC: precondition return called"); - #endif + debug("RDTSC: precondition return called"); return false; } @@ -681,9 +663,7 @@ struct VM { acc += __rdtsc() - s; } - #ifdef __VMAWARE_DEBUG__ - debug("RDTSC: ", "acc = ", acc / 100); - #endif + debug("RDTSC: ", "acc = ", acc / 100); return (acc / 100 > 350); #elif (MSVC) @@ -724,9 +704,7 @@ struct VM { return false; #else if (disabled(SIDT5)) { - #ifdef __VMAWARE_DEBUG__ - debug("SIDT5: ", "precondition return called"); - #endif + debug("SIDT5: ", "precondition return called"); return false; } @@ -766,9 +744,7 @@ struct VM { return false; #else if (disabled(SIDT)) { - #ifdef __VMAWARE_DEBUG__ - debug("SIDT: ", "precondition return called"); - #endif + debug("SIDT: ", "precondition return called"); return false; } @@ -779,9 +755,7 @@ struct VM { : "=m" (idtr) ); - #ifdef __VMAWARE_DEBUG__ - debug("SIDT: ", "idtr = ", idtr); - #endif + debug("SIDT: ", "idtr = ", idtr); return (idtr != 0); #endif @@ -799,9 +773,7 @@ struct VM { return false; #else if (disabled(VMWARE_PORT)) { - #ifdef __VMAWARE_DEBUG__ - debug("VMWARE_PORT: ", "precondition return called"); - #endif + debug("VMWARE_PORT: ", "precondition return called"); return false; } @@ -896,15 +868,11 @@ struct VM { */ [[nodiscard]] static bool thread_count() try { if (disabled(THREADCOUNT)) { - #ifdef __VMAWARE_DEBUG__ - debug("THREADCOUNT: ", "precondition return called"); - #endif + debug("THREADCOUNT: ", "precondition return called"); return false; } - #ifdef __VMAWARE_DEBUG__ - debug("SIDT5: ", "threads = ", std::thread::hardware_concurrency()); - #endif + debug("THREADCOUNT: ", "threads = ", std::thread::hardware_concurrency()); return (std::thread::hardware_concurrency() <= 2); } catch (...) { return false; } @@ -916,9 +884,7 @@ struct VM { */ [[nodiscard]] static bool mac_address_check() try { if (disabled(MAC)) { - #ifdef __VMAWARE_DEBUG__ - debug("MAC: ", "precondition return called"); - #endif + debug("MAC: ", "precondition return called"); return false; } @@ -966,9 +932,7 @@ struct VM { if (success) { std::memcpy(mac, ifr.ifr_hwaddr.sa_data, 6); } else { - #ifdef __VMAWARE_DEBUG__ - debug("MAC: ", "not successful"); - #endif + debug("MAC: ", "not successful"); } #elif (MSVC) PIP_ADAPTER_INFO AdapterInfo; @@ -1057,9 +1021,7 @@ struct VM { return false; #else if (disabled(TEMPERATURE)) { - #ifdef __VMAWARE_DEBUG__ - debug("TEMPERATURE: ", "precondition return called"); - #endif + debug("TEMPERATURE: ", "precondition return called"); return false; } @@ -1077,31 +1039,23 @@ struct VM { return false; #else if (disabled(SYSTEMD)) { - #ifdef __VMAWARE_DEBUG__ - debug("SYSTEMD: ", "precondition return called"); - #endif + debug("SYSTEMD: ", "precondition return called"); return false; } if (!(exists("/usr/bin/systemd-detect-virt") || exists("/bin/systemd-detect-virt"))) { - #ifdef __VMAWARE_DEBUG__ - debug("SYSTEMD: ", "binary doesn't exist"); - #endif + debug("SYSTEMD: ", "binary doesn't exist"); return false; } const std::unique_ptr result = sys_result("systemd-detect-virt"); if (result == nullptr) { - #ifdef __VMAWARE_DEBUG__ - debug("SYSTEMD: ", "invalid stdout output from systemd-detect-virt"); - #endif + debug("SYSTEMD: ", "invalid stdout output from systemd-detect-virt"); return false; } - #ifdef __VMAWARE_DEBUG__ - debug("SYSTEMD: ", "output = ", *result); - #endif + debug("SYSTEMD: ", "output = ", *result); return (*result != "none"); #endif @@ -1117,9 +1071,7 @@ struct VM { return false; #else if (disabled(CVENDOR)) { - #ifdef __VMAWARE_DEBUG__ - debug("CVENDOR: ", "precondition return called"); - #endif + debug("CVENDOR: ", "precondition return called"); return false; } @@ -1132,13 +1084,9 @@ struct VM { if (vendor == "QEMU") { return add(QEMU); } if (vendor == "Oracle Corporation") { return add(VMWARE); } - #ifdef __VMAWARE_DEBUG__ - debug("CVENDOR: ", "unknown vendor = ", vendor); - #endif + debug("CVENDOR: ", "unknown vendor = ", vendor); } else { - #ifdef __VMAWARE_DEBUG__ - debug("CVENDOR: ", "file doesn't exist"); - #endif + debug("CVENDOR: ", "file doesn't exist"); } return false; @@ -1155,9 +1103,7 @@ struct VM { return false; #else if (disabled(CTYPE)) { - #ifdef __VMAWARE_DEBUG__ - debug("CTYPE: ", "precondition return called"); - #endif + debug("CTYPE: ", "precondition return called"); return false; } @@ -1166,9 +1112,7 @@ struct VM { if (exists(chassis)) { return (stoi(read_file(chassis)) == 1); } else { - #ifdef __VMAWARE_DEBUG__ - debug("CTYPE: ", "file doesn't exist"); - #endif + debug("CTYPE: ", "file doesn't exist"); } return false; @@ -1184,10 +1128,8 @@ struct VM { #if (!LINUX) return false; #else - if (disabled(DOCKER_CHECK)) { - #ifdef __VMAWARE_DEBUG__ - debug("DOCKER: ", "precondition return called"); - #endif + if (disabled(DOCKERENV)) { + debug("DOCKER: ", "precondition return called"); return false; } @@ -1205,25 +1147,19 @@ struct VM { return false; #else if (disabled(DMIDECODE) || (is_root() == false)) { - #ifdef __VMAWARE_DEBUG__ - debug("DMIDECODE: ", "precondition return called (root = ", is_root(), ")"); - #endif + debug("DMIDECODE: ", "precondition return called (root = ", is_root(), ")"); return false; } if (!(exists("/bin/dmidecode") || exists("/usr/bin/dmidecode"))) { - #ifdef __VMAWARE_DEBUG__ - debug("DMIDECODE: ", "binary doesn't exist"); - #endif + debug("DMIDECODE: ", "binary doesn't exist"); return false; } const std::unique_ptr result = sys_result("dmidecode -t system | grep 'Manufacturer|Product' | grep -c \"QEMU|VirtualBox|KVM\""); if (*result == "" || result == nullptr) { - #ifdef __VMAWARE_DEBUG__ - debug("DMIDECODE: ", "invalid output"); - #endif + debug("DMIDECODE: ", "invalid output"); return false; } else if (*result == "QEMU") { return add(QEMU); @@ -1234,9 +1170,7 @@ struct VM { } else if (std::atoi(result->c_str()) >= 1) { return true; } else { - #ifdef __VMAWARE_DEBUG__ - debug("DMIDECODE: ", "output = ", *result); - #endif + debug("DMIDECODE: ", "output = ", *result); } return false; @@ -1253,16 +1187,12 @@ struct VM { return false; #else if (disabled(DMESG)) { - #ifdef __VMAWARE_DEBUG__ - debug("DMESG: ", "precondition return called"); - #endif + debug("DMESG: ", "precondition return called"); return false; } if (!exists("/bin/dmesg") && !exists("/usr/bin/dmesg")) { - #ifdef __VMAWARE_DEBUG__ - debug("DMESG: ", "binary doesn't exist"); - #endif + debug("DMESG: ", "binary doesn't exist"); return false; } @@ -1277,9 +1207,7 @@ struct VM { } else if (std::atoi(result->c_str())) { return true; } else { - #ifdef __VMAWARE_DEBUG__ - debug("DMESG: ", "output = ", *result); - #endif + debug("DMESG: ", "output = ", *result); } return false; @@ -1296,9 +1224,7 @@ struct VM { return false; #else if (disabled(HWMON)) { - #ifdef __VMAWARE_DEBUG__ - debug("HWMON: ", "precondition return called"); - #endif + debug("HWMON: ", "precondition return called"); return false; } @@ -1324,9 +1250,7 @@ struct VM { return false; #else if (disabled(REGISTRY)) { - #ifdef __VMAWARE_DEBUG__ - debug("REGISTRY: ", "precondition return called"); - #endif + debug("REGISTRY: ", "precondition return called"); return false; } @@ -1348,9 +1272,7 @@ struct VM { score++; if (std::string(p_brand) != "") [[likely]] { - #ifdef __VMAWARE_DEBUG__ - debug("REGISTRY: ", "detected = ", p_brand); - #endif + debug("REGISTRY: ", "detected = ", p_brand); scoreboard[p_brand]++; } } @@ -1424,9 +1346,7 @@ struct VM { key("Xen HVM", "HKLM\\SYSTEM\\ControlSet001\\Services\\xensvc"); key("Xen HVM", "HKLM\\SYSTEM\\ControlSet001\\Services\\xenvdb"); - #ifdef __VMAWARE_DEBUG__ - debug("REGISTRY: ", "score = ", score); - #endif + debug("REGISTRY: ", "score = ", score); return (score >= 1); #endif @@ -1443,9 +1363,7 @@ struct VM { return false; #else if (disabled(USER)) { - #ifdef __VMAWARE_DEBUG__ - debug("USER: ", "precondition return called"); - #endif + debug("USER: ", "precondition return called"); return false; } @@ -1454,9 +1372,7 @@ struct VM { GetUserName((TCHAR*)user, &user_len); std::string u = user; - #ifdef __VMAWARE_DEBUG__ - debug("USER: ", "output = ", u); - #endif + debug("USER: ", "output = ", u); return ( (u == "username") || // ThreadExpert @@ -1478,9 +1394,7 @@ struct VM { return false; #else if (disabled(SUNBELT)) { - #ifdef __VMAWARE_DEBUG__ - debug("SUNBELT: ", "precondition return called"); - #endif + debug("SUNBELT: ", "precondition return called"); return false; } @@ -1498,9 +1412,7 @@ struct VM { return false; #else if (disabled(DLL)) { - #ifdef __VMAWARE_DEBUG__ - debug("DLL: ", "precondition return called"); - #endif + debug("DLL: ", "precondition return called"); return false; } @@ -1521,9 +1433,7 @@ struct VM { for (auto &dll : real_dlls) { lib_inst = LoadLibraryA(dll); if (lib_inst == nullptr) { - #ifdef __VMAWARE_DEBUG__ - debug("DLL: ", "LIB_INST detected true for real dll = ", dll); - #endif + debug("DLL: ", "LIB_INST detected true for real dll = ", dll); return true; } FreeLibrary(lib_inst); @@ -1532,9 +1442,7 @@ struct VM { for (auto &dll : false_dlls) { lib_inst = LoadLibraryA(dll); if (lib_inst != nullptr) { - #ifdef __VMAWARE_DEBUG__ - debug("DLL: ", "LIB_INST detected true for false dll = ", dll); - #endif + debug("DLL: ", "LIB_INST detected true for false dll = ", dll); return true; } } @@ -1553,9 +1461,7 @@ struct VM { return false; #else if (disabled(VBOX_REG)) { - #ifdef __VMAWARE_DEBUG__ - debug("VBOX_REG: ", "precondition return called"); - #endif + debug("VBOX_REG: ", "precondition return called"); return false; } @@ -1581,9 +1487,7 @@ struct VM { return false; #else if (disabled(VMWARE_REG)) { - #ifdef __VMAWARE_DEBUG__ - debug("VMWARE_REG: ", "precondition return called"); - #endif + debug("VMWARE_REG: ", "precondition return called"); return false; } @@ -1618,9 +1522,7 @@ struct VM { return false; #else if (disabled(CURSOR)) { - #ifdef __VMAWARE_DEBUG__ - debug("CURSOR: ", "precondition return called"); - #endif + debug("CURSOR: ", "precondition return called"); return false; } @@ -1645,9 +1547,7 @@ struct VM { return false; #else if (disabled(WINE_CHECK)) { - #ifdef __VMAWARE_DEBUG__ - debug("WINE: ", "precondition return called"); - #endif + debug("WINE: ", "precondition return called"); return false; } @@ -1670,9 +1570,7 @@ struct VM { */ [[nodiscard]] static bool boot_time() try { if (disabled(BOOT)) { - #ifdef __VMAWARE_DEBUG__ - debug("BOOT: ", "precondition return called"); - #endif + debug("BOOT: ", "precondition return called"); return false; } @@ -1703,9 +1601,7 @@ struct VM { return false; #else if (disabled(VM_FILES)) { - #ifdef __VMAWARE_DEBUG__ - debug("VMFILES: ", "precondition return called"); - #endif + debug("VMFILES: ", "precondition return called"); return false; } @@ -1743,7 +1639,7 @@ struct VM { "C:\\windows\\System32\\VBoxControl.exe", "C:\\windows\\System32\\vboxoglerrorspu.dll", "C:\\windows\\System32\\vboxoglfeedbackspu.dll", - } + }; for (const sv file : files) { if (exists(file)) { @@ -1779,9 +1675,7 @@ struct VM { return false; #else if (disabled(HWMODEL)) { - #ifdef __VMAWARE_DEBUG__ - debug("HWMODEL: ", "precondition return called"); - #endif + debug("HWMODEL: ", "precondition return called"); return false; } @@ -1790,15 +1684,11 @@ struct VM { std::smatch match; if (result == nullptr) { - #ifdef __VMAWARE_DEBUG__ - debug("HWMODEL: ", "null result received"); - #endif + debug("HWMODEL: ", "null result received"); return false; } - #ifdef __VMAWARE_DEBUG__ - debug("HWMODEL: ", "output = ", *result); - #endif + debug("HWMODEL: ", "output = ", *result); // if string contains "Mac" anywhere in the string, assume it's baremetal if (std::regex_search(*result, match, std::regex("Mac"))) { @@ -1818,6 +1708,45 @@ struct VM { // LABEL (ignore this, it's just a label so I can easily teleport to this line on my IDE with CTRL+F) + struct data { + u8 points; + bool(*ptr)(); // function pointer + }; + + // the points are debatable, but I think it's fine how it is. Feel free to disagree. + static inline std::map table = { + { VM::VMID, { 100, vmid }}, + { VM::BRAND, { 50, cpu_brand }}, + { VM::HYPERV_BIT, { 95, cpuid_hyperv }}, + { VM::CPUID_0x4, { 70, cpuid_0x4 }}, + { VM::HYPERV_STR, { 45, hyperv_brand }}, + { VM::RDTSC, { 20, rdtsc_check }}, + { VM::SIDT, { 65, sidt_check }}, + { VM::VMWARE_PORT, { 80, vmware_port }}, + { VM::THREADCOUNT, { 35, thread_count }}, + { VM::MAC, { 90, mac_address_check }}, + { VM::TEMPERATURE, { 15, temperature }}, + { VM::SYSTEMD, { 70, systemd_virt }}, + { VM::CVENDOR, { 65, chassis_vendor }}, + { VM::CTYPE, { 10, chassis_type }}, + { VM::DOCKERENV, { 80, dockerenv }}, + { VM::DMIDECODE, { 55, dmidecode }}, + { VM::DMESG, { 55, dmesg }}, + { VM::HWMON, { 75, hwmon }}, + { VM::SIDT5, { 45, sidt5 }}, + { VM::CURSOR, { 10, cursor_check }}, + { VM::VMWARE_REG, { 65, vmware_registry }}, + { VM::VBOX_REG, { 65, vbox_registry }}, + { VM::USER, { 35, user_check }}, + { VM::DLL, { 50, DLL_check }}, // i genuinely have no idea + { VM::REGISTRY, { 75, registry_key }}, + { VM::SUNBELT, { 10, sunbelt_check }}, + { VM::WINE_CHECK, { 85, wine }}, + { VM::BOOT, { 5, boot_time }}, + { VM::VM_FILES, { 80, vm_files }}, + { VM::HWMODEL, { 75, hwmodel }} + }; + public: /** * @brief Check for a specific technique based on flag argument @@ -1864,53 +1793,30 @@ struct VM { bool result = false; - switch (p_flags) { - case VM::VMID: result = vmid(); break; - case VM::BRAND: result = cpu_brand(); break; - case VM::HYPERV_BIT: result = cpuid_hyperv(); break; - case VM::CPUID_0x4: result = cpuid_0x4(); break; - case VM::HYPERV_STR: result = hyperv_brand(); break; - case VM::SIDT5: result = sidt5(); break; - case VM::SIDT: result = sidt_check(); break; - case VM::TEMPERATURE: result = temperature(); break; - case VM::SYSTEMD: result = systemd_virt(); break; - case VM::CVENDOR: result = chassis_vendor(); break; - case VM::CTYPE: result = chassis_type(); break; - case VM::DOCKER_CHECK: result = dockerenv(); break; - case VM::DMIDECODE: result = dmidecode(); break; - case VM::DMESG: result = dmesg(); break; - case VM::HWMON: result = hwmon(); break; - case VM::RDTSC: result = rdtsc_check(); break; - case VM::MAC: result = mac_address_check(); break; - case VM::VMWARE_PORT: result = vmware_port(); break; - case VM::CURSOR: result = cursor_check(); break; - case VM::VMWARE_REG: result = vmware_registry(); break; - case VM::VBOX_REG: result = vbox_registry(); break; - case VM::USER: result = user_check(); break; - case VM::DLL: result = DLL_check(); break; - case VM::REGISTRY: result = registry_key(); break; - case VM::SUNBELT: result = sunbelt_check(); break; - case VM::THREADCOUNT: result = thread_count(); break; - case VM::WINE_CHECK: result = wine(); break; - case VM::BOOT: result = boot_time(); break; - case VM::VM_FILES: result = vm_files(); break; - case VM::HWMODEL: result = hwmodel(); break; - default: throw std::invalid_argument("Unknown flag provided for VM::check() function"); + auto it = table.find(p_flags); + + if (it == table.end()) { + throw std::invalid_argument("Flag is not known, consult the documentation's flag list"); } + const data &pair = it->second; + result = pair.ptr(); + VM::flags = tmp_flags; return result; } + /** * @brief Fetch the VM brand - * @returns VMware, VirtualBox, KVM, bhyve, QEMU, Microsoft Hyper-V, Microsoft x86-to-ARM, Parallels, Xen HVM, ACRN, QNX hypervisor, Hybrid Analysis, Sandboxie, Docker, Wine, Virtual Apple, Unknown + * @returns VMware, VirtualBox, KVM, bhyve, QEMU, Microsoft Hyper-V, Microsoft x86-to-ARM, Parallels, Xen HVM, ACRN, QNX hypervisor, Hybrid Analysis, Sandboxie, Docker, Wine, Virtual Apple, Virtual PC, Unknown * @link https://github.com/kernelwernel/VMAware/blob/main/docs/documentation.md#vmbrand */ [[nodiscard]] static sv brand(void) { // check if result hasn't been memoized already if (memo.find(true) == memo.end()) { + debug("memoization: detect() called in brand"); detect(); } @@ -1935,7 +1841,8 @@ struct VM { * execution of VM::detect(). This can save around * 5~10x speed depending on the circumstances. */ - if (!(p_flags & NO_MEMO) && memo.find(true) != memo.end()) { + if (disabled(NO_MEMO) && (memo.find(true) != memo.end())) { + debug("memoization: returned cached result in detect()"); return memo[true].first; } @@ -1943,41 +1850,17 @@ struct VM { VM::no_cpuid = !check_cpuid(); VM::flags = p_flags; - f64 points = 0; - - if (thread_count()) { points += 1.5; } - if (mac_address_check()) { points += 3.5; } - if (vmid()) { points += 6.5; } - if (cpu_brand()) { points += 3; } - if (cpuid_hyperv()) { points += 5.5; } - if (cpuid_0x4()) { points += 4; } - if (hyperv_brand()) { points += 4; } - if (rdtsc_check()) { points += 1.5; } - if (sidt_check()) { points += 4; } - if (vmware_port()) { points += 3; } - if (sidt5()) { points += 2; } - if (temperature()) { points += 1; } - if (systemd_virt()) { points += 5; } - if (chassis_vendor()) { points += 4.5; } - if (chassis_type()) { points += 1; } - if (dockerenv()) { points += 3; } - if (dmidecode()) { points += 4; } - if (dmesg()) { points += 3.5; } - if (hwmon()) { points += 0.5; } - if (cursor_check()) { points += 0.5; } - if (vmware_registry()) { points += 4.5; } - if (vbox_registry()) { points += 4.5; } - if (user_check()) { points += 1; } - if (DLL_check()) { points += 3; } // might update this, idk - if (registry_key()) { points += 5; } - if (sunbelt_check()) { points += 1; } - if (wine()) { points += 3.5; } - if (boot_time()) { points += 0.5; } - if (vm_files()) { points += 4; } - if (hwmodel()) { points += 2.5; } + u8 points = 0; + + for (auto it = table.cbegin(); it != table.cend(); ++it) { + const data &pair = it->second; + if (pair.ptr()) { + points += pair.points; + }; + } // arbitrary threshold score - const bool result = (points >= 6.5); + const bool result = (points >= 100); sv current_brand = ""; @@ -1996,7 +1879,15 @@ struct VM { ); if (it != scoreboard.end()) { - current_brand = it->first; + if (std::none_of(scoreboard.cbegin(), scoreboard.cend(), + [](const auto &pair) { + return pair.second; + } + )) { + current_brand = "Unknown"; + } else { + current_brand = it->first; + } } else { current_brand = "Unknown"; } diff --git a/src/vmtest.cpp b/src/vmtest.cpp index 1bf59ca..f2592ad 100644 --- a/src/vmtest.cpp +++ b/src/vmtest.cpp @@ -2,6 +2,6 @@ #include int main(void) { - std::cout << VM::brand() << "\n"; + std::cout << VM::detect() << "\n"; return 0; } \ No newline at end of file