commit a3929ff50c81dfa0fb2a9c13c3ec1b544aa8a031 Author: Print3M <92022497+Print3M@users.noreply.github.com> Date: Fri Sep 15 22:06:35 2023 +0200 init diff --git a/README.md b/README.md new file mode 100644 index 0000000..8199361 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# Anti-VM techniques + +Basic implementation of several anti-vm techniques (Windows) for educational purpose. They rely on checking different parts of OS and hardware to find out if the script is running in a VM. + +## Implemented techniques + +[x] CPU hypervisor bit (CPUID) +[x] CPU id string (CPUID) +[x] CPU brand string (CPUID) +[x] BIOS manufacturer string +[X] BIOS version string +[x] Screen resolution +[x] Amount of physical memory +[x] Number of CPU cores +[x] Amount of disk space + +## To be implemented + +[ ] Global Descriptor Table location +[ ] Local Descriptor Table location +[ ] Interrupt Descriptor Table location +[ ] ACPI VM-based string checks +[ ] VM-based hostnames and usernames +[ ] VM-based MAC addresses diff --git a/detectors.cpp b/detectors.cpp new file mode 100644 index 0000000..c6c56d8 --- /dev/null +++ b/detectors.cpp @@ -0,0 +1,158 @@ +#include +#include "detectors.hpp" +#include +#include +#include +#include +#include "wmi.hpp" +#include +#include +#include "utils.hpp" +#include + +std::vector detect::vm_names = { "virtual", "qemu", "vmware", "oracle", "innotek" }; +std::vector detect::legit_cpu_ids = { "AuthenticAMD", "GenuineIntel" }; + +bool detect::cpu_hypervisor_bit() { + /* + Check for hypervisor - enabled bit. + eax = 0x1, 31st-bit of ECX + */ + std::array cpuInfo = { 0, 0, 0, 0 }; + __cpuid(cpuInfo.data(), 0x1); + + return (cpuInfo[2] >> 31) & 0x1; +} + +bool detect::cpu_id() { + /* + Check for fake-vm CPU ID string. + eax = 0x0, EBX + ECX + EDX (12 bytes) + */ + + // TODO: I think there is something wrong with the regs order. + std::array cpuInfo = { 0, 0, 0, 0 }; + std::string cpu = ""; + __cpuid(cpuInfo.data(), 0x0); + + for (int i = 1; i <= 3; i++) { + cpu += cpuInfo[i] & 0xff; + cpu += (cpuInfo[i] >> 8) & 0xff; + cpu += (cpuInfo[i] >> 16) & 0xff; + cpu += (cpuInfo[i] >> 24) & 0xff; + } + + return !utils::str_includes(cpu, detect::legit_cpu_ids); +} + + +bool detect::cpu_brand() { + /* + Check for fake-vm CPU brand name. + eax = 0x80000002, 0x80000003, 0x80000004 + (EAX + EBX + ECX + EDX) x 3 = 36 bytes + */ + std::array cpuInfo = { 0, 0, 0, 0 }; + std::string cpu = ""; + + for (int id = 2; id <= 4; id++) { + __cpuid(cpuInfo.data(), 0x80000000 + id); + + for (auto i : cpuInfo) { + cpu += std::tolower(i & 0xff); + cpu += std::tolower((i >> 8) & 0xff); + cpu += std::tolower((i >> 16) & 0xff); + cpu += std::tolower((i >> 24) & 0xff); + } + } + + return utils::str_includes(cpu, detect::vm_names); +} + + +bool detect::bios_manufacturer() { + /* + Check for fake-vm BIOS manufacturer string. + */ + std::string manufacturer = ""; + wmi::get_wmi("select * from Win32_BIOS", [&](IWbemClassObject* pclsObj, VARIANT* vtProp) { + HRESULT hr = pclsObj->Get(L"Manufacturer", 0, vtProp, 0, 0); + manufacturer = utils::lowercase(utils::bstr_to_str(vtProp->bstrVal)); + }); + + std::cout << manufacturer << std::endl; + + return utils::str_includes(manufacturer, detect::vm_names); +} + +bool detect::bios_version() { + /* + Check for fake-vm BIOS version string. + */ + std::string version = ""; + wmi::get_wmi("select * from Win32_BIOS", [&](IWbemClassObject* pclsObj, VARIANT* vtProp) { + HRESULT hr = pclsObj->Get(L"SMBIOSBIOSVersion", 0, vtProp, 0, 0); + version = utils::lowercase(utils::bstr_to_str(vtProp->bstrVal)); + }); + + return utils::str_includes(version, detect::vm_names); +} + +bool detect::screen_resolution() { + /* + Check for uncommon screen resolutions. + */ + auto w = GetSystemMetrics(SM_CXSCREEN); + auto h = GetSystemMetrics(SM_CYSCREEN); + + // Standard and common resolutions + if (w == 1600 && h == 900) return false; + if (w == 1920 && h == 1080) return false; + if (w == 1920 && h == 1200) return false; + if (w == 2560 && h == 1440) return false; + if (w == 3840 && h == 2160) return false; + + return true; +} + +bool detect::memory_amount() { + /* + Check for suspicious amount of physical memory. + */ + MEMORYSTATUSEX statex; + statex.dwLength = sizeof(statex); + + if (GlobalMemoryStatusEx(&statex) == FALSE) { + return true; + } + + // Minimum 4 GB + return statex.ullTotalPhys / 1024 / 1024 < 4096; +} + +bool detect::cpu_cores() { + /* + Check for suspicious amount of CPU cores. + */ + SYSTEM_INFO info; + GetSystemInfo(&info); + + return info.dwNumberOfProcessors < 4; +} + +bool detect::disk_space() { + /* + Check for suspicious amount of total disk space. + */ + ULARGE_INTEGER bytes; + BOOL result = GetDiskFreeSpaceExA( + "\\\\?\\c:\\", + NULL, + &bytes, + NULL + ); + + if (result == FALSE) return true; + + return bytes.QuadPart / 1024 / 1024 / 1024 < 100; +} \ No newline at end of file diff --git a/detectors.hpp b/detectors.hpp new file mode 100644 index 0000000..8b4015a --- /dev/null +++ b/detectors.hpp @@ -0,0 +1,18 @@ +#pragma once +#include +#include + +namespace detect { + extern std::vector vm_names; + extern std::vector legit_cpu_ids; + + bool cpu_hypervisor_bit(); + bool cpu_id(); + bool cpu_brand(); + bool bios_manufacturer(); + bool bios_version(); + bool screen_resolution(); + bool memory_amount(); + bool cpu_cores(); + bool disk_space(); +}; \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..0c74135 --- /dev/null +++ b/main.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include +#include +#include +#include "detectors.hpp" + +/* + TODO: + - memory && CPU resources +*/ + +int main(int argc, char* argv[]) { + printf("CPU Brand Name: %d\n", detect::cpu_brand()); + printf("CPU ID: %d\n", detect::cpu_id()); + printf("CPU Hypervisor bit: %d\n", detect::cpu_hypervisor_bit()); + printf("BIOS Manufacturer: %d\n", detect::bios_manufacturer()); + printf("BIOS Version: %d\n", detect::bios_version()); + printf("Scren resolution: %d\n", detect::screen_resolution()); + printf("Memory amount: %d\n", detect::memory_amount()); + printf("CPU cores: %d\n", detect::cpu_cores()); + printf("Disk space: %d\n", detect::disk_space()); +} diff --git a/utils.cpp b/utils.cpp new file mode 100644 index 0000000..ebef131 --- /dev/null +++ b/utils.cpp @@ -0,0 +1,35 @@ +#include "utils.hpp" +#include "detectors.hpp" +#include +#include +#include + +std::string utils::bstr_to_str(BSTR bstr) { + if (!bstr) return std::string(""); + + std::wstring ws(bstr, SysStringLen(bstr)); + std::wstring_convert> converter; + std::string narrow = converter.to_bytes(ws); + + return narrow; +} + +std::string utils::lowercase(std::string str) { + std::string result = ""; + + for (auto c : str) { + result += std::tolower(c); + } + + return result; +} + +bool utils::str_includes(std::string str, std::vector includes) { + for (auto i : includes) { + if (str.find(i) != std::string::npos) { + return true; + } + } + + return false; +} \ No newline at end of file diff --git a/utils.hpp b/utils.hpp new file mode 100644 index 0000000..34df68c --- /dev/null +++ b/utils.hpp @@ -0,0 +1,10 @@ +#pragma once +#include +#include +#include + +namespace utils { + std::string bstr_to_str(BSTR bstr); + std::string lowercase(std::string str); + bool str_includes(std::string str, std::vector includes); +}; \ No newline at end of file diff --git a/wmi.cpp b/wmi.cpp new file mode 100644 index 0000000..d5796e8 --- /dev/null +++ b/wmi.cpp @@ -0,0 +1,181 @@ +#define _WIN32_DCOM +#include +using namespace std; +#include +#include +#include +#include "wmi.hpp" + +#pragma comment(lib, "wbemuuid.lib") + +int wmi::get_wmi(std::string wql_query, std::function get_property) { + HRESULT hres; + + // Step 1: -------------------------------------------------- + // Initialize COM. ------------------------------------------ + + hres = CoInitializeEx(0, COINIT_MULTITHREADED); + if (FAILED(hres)) + { + cout << "Failed to initialize COM library. Error code = 0x" + << hex << hres << endl; + return 1; // Program has failed. + } + + // Step 2: -------------------------------------------------- + // Set general COM security levels -------------------------- + + hres = CoInitializeSecurity( + NULL, + -1, // COM authentication + NULL, // Authentication services + NULL, // Reserved + RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication + RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation + NULL, // Authentication info + EOAC_NONE, // Additional capabilities + NULL // Reserved + ); + + + if (FAILED(hres)) + { + cout << "Failed to initialize security. Error code = 0x" + << hex << hres << endl; + CoUninitialize(); + return 1; // Program has failed. + } + + // Step 3: --------------------------------------------------- + // Obtain the initial locator to WMI ------------------------- + + IWbemLocator* pLoc = NULL; + + hres = CoCreateInstance( + CLSID_WbemLocator, + 0, + CLSCTX_INPROC_SERVER, + IID_IWbemLocator, (LPVOID*)&pLoc); + + if (FAILED(hres)) + { + cout << "Failed to create IWbemLocator object." + << " Err code = 0x" + << hex << hres << endl; + CoUninitialize(); + return 1; // Program has failed. + } + + // Step 4: ----------------------------------------------------- + // Connect to WMI through the IWbemLocator::ConnectServer method + + IWbemServices* pSvc = NULL; + + // Connect to the root\cimv2 namespace with + // the current user and obtain pointer pSvc + // to make IWbemServices calls. + hres = pLoc->ConnectServer( + _bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace + NULL, // User name. NULL = current user + NULL, // User password. NULL = current + 0, // Locale. NULL indicates current + NULL, // Security flags. + 0, // Authority (for example, Kerberos) + 0, // Context object + &pSvc // pointer to IWbemServices proxy + ); + + if (FAILED(hres)) + { + cout << "Could not connect. Error code = 0x" + << hex << hres << endl; + pLoc->Release(); + CoUninitialize(); + return 1; // Program has failed. + } + + // Step 5: -------------------------------------------------- + // Set security levels on the proxy ------------------------- + + hres = CoSetProxyBlanket( + pSvc, // Indicates the proxy to set + RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx + RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx + NULL, // Server principal name + RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx + RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx + NULL, // client identity + EOAC_NONE // proxy capabilities + ); + + if (FAILED(hres)) + { + cout << "Could not set proxy blanket. Error code = 0x" + << hex << hres << endl; + pSvc->Release(); + pLoc->Release(); + CoUninitialize(); + return 1; // Program has failed. + } + + // Step 6: -------------------------------------------------- + // Use the IWbemServices pointer to make requests of WMI ---- + + // For example, get the name of the operating system + IEnumWbemClassObject* pEnumerator = NULL; + hres = pSvc->ExecQuery( + bstr_t("WQL"), + bstr_t(wql_query.c_str()), + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, + &pEnumerator); + + if (FAILED(hres)) + { + cout << "WMI query failed." << " Error code = 0x" << hex << hres << endl; + pSvc->Release(); + pLoc->Release(); + CoUninitialize(); + return 1; // Program has failed. + } + + // Step 7: ------------------------------------------------- + // Get the data from the query in step 6 ------------------- + + IWbemClassObject* pclsObj = NULL; + ULONG uReturn = 0; + + while (pEnumerator) + { + HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, + &pclsObj, &uReturn); + + if (0 == uReturn) + { + break; + } + + VARIANT vtProp; + + VariantInit(&vtProp); + // Get the value of the property + get_property(pclsObj, &vtProp); + //hr = pclsObj->Get(L"SMBIOSBIOSVersion", 0, &vtProp, 0, 0); + //wcout << "SMBIOSBIOSVersion : " << vtProp.bstrVal << endl; + VariantClear(&vtProp); + + pclsObj->Release(); + } + + // Cleanup + // ======== + + pSvc->Release(); + pLoc->Release(); + pEnumerator->Release(); + CoUninitialize(); + + return 0; // Program successfully completed. + +} + diff --git a/wmi.hpp b/wmi.hpp new file mode 100644 index 0000000..1620cca --- /dev/null +++ b/wmi.hpp @@ -0,0 +1,8 @@ +#pragma once +#include +#include +#include + +namespace wmi { + int get_wmi(std::string wql_query, std::function get_property); +}; \ No newline at end of file