From fda844a29ffd7cccc5a1425a0abddcf69bddadec Mon Sep 17 00:00:00 2001 From: Requiem Date: Thu, 19 Feb 2026 21:05:49 +0100 Subject: [PATCH] feat: improved timing checks even further --- docs/documentation.md | 166 ++++++++++++------------- src/vmaware.hpp | 281 +++++++++++++++++++++++++++--------------- 2 files changed, 267 insertions(+), 180 deletions(-) diff --git a/docs/documentation.md b/docs/documentation.md index 35eb4698..bc93433b 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -510,90 +510,90 @@ VMAware provides a convenient way to not only check for VMs, but also have the f | Flag alias | Description | Supported platforms | Certainty | Admin? | 32-bit only? | Notes | Code implementation | | ---------- | ----------- | ------------------- | --------- | ------ | ------------ | ----- | ------------------- | -| `VM::VMID` | Check CPUID output of manufacturer ID for known VMs/hypervisors at leaf 0 and 0x40000000-0x40000100 | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4284) | -| `VM::CPU_BRAND` | Check if CPU brand model contains any VM-specific string snippets | 🐧🪟🍏 | 95% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4302) | -| `VM::HYPERVISOR_BIT` | Check if hypervisor feature bit in CPUID ECX bit 31 is enabled (always false for physical CPUs) | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4376) | -| `VM::HYPERVISOR_STR` | Check for hypervisor brand string length (would be around 2 characters in a host machine) | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4402) | -| `VM::TIMER` | Check for timing anomalies in the system | 🐧🪟🍏 | 150% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4588) | -| `VM::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 | 🐧🪟🍏 | 35% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7228) | -| `VM::MAC` | Check if mac address starts with certain VM designated values | 🐧 | 20% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5285) | -| `VM::TEMPERATURE` | Check for device's temperature | 🐧 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6137) | -| `VM::SYSTEMD` | Check result from systemd-detect-virt tool | 🐧 | 35% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5166) | -| `VM::CVENDOR` | Check if the chassis vendor is a VM vendor | 🐧 | 65% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5190) | -| `VM::CTYPE` | Check if the chassis type is valid (it's very often invalid in VMs) | 🐧 | 20% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5215) | -| `VM::DOCKERENV` | Check if /.dockerenv or /.dockerinit file is present | 🐧 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5233) | -| `VM::DMIDECODE` | Check if dmidecode output matches a VM brand | 🐧 | 55% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5248) | -| `VM::DMESG` | Check if dmesg output matches a VM brand | 🐧 | 55% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5391) | -| `VM::HWMON` | Check if /sys/class/hwmon/ directory is present. If not, likely a VM | 🐧 | 35% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5432) | -| `VM::DLL` | Check for VM-specific DLLs | 🪟 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7528) | -| `VM::HWMODEL` | Check if the sysctl for the hwmodel does not contain the "Mac" string | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7252) | -| `VM::WINE` | Check if the function "wine_get_unix_file_name" is present and if the OS booted from a VHD container | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7559) | -| `VM::POWER_CAPABILITIES` | Check what power states are enabled | 🪟 | 45% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7669) | -| `VM::PROCESSES` | Check for any VM processes that are active | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6148) | -| `VM::LINUX_USER_HOST` | Check for default VM username and hostname for linux | 🐧 | 10% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5442) | -| `VM::GAMARUE` | Check for Gamarue ransomware technique which compares VM-specific Window product IDs | 🪟 | 10% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7729) | -| `VM::BOCHS_CPU` | Check for various Bochs-related emulation oversights through CPU checks | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4430) | -| `VM::MAC_MEMSIZE` | Check if memory is too low for MacOS system | 🍏 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7288) | -| `VM::MAC_IOKIT` | Check MacOS' IO kit registry for VM-specific strings | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7321) | -| `VM::IOREG_GREP` | Check for VM-strings in ioreg commands for MacOS | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7418) | -| `VM::MAC_SIP` | Check for the status of System Integrity Protection and hv_mm_present | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7459) | -| `VM::VPC_INVALID` | Check for official VPC method | 🪟 | 75% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7838) | +| `VM::VMID` | Check CPUID output of manufacturer ID for known VMs/hypervisors at leaf 0 and 0x40000000-0x40000100 | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4283) | +| `VM::CPU_BRAND` | Check if CPU brand model contains any VM-specific string snippets | 🐧🪟🍏 | 95% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4301) | +| `VM::HYPERVISOR_BIT` | Check if hypervisor feature bit in CPUID ECX bit 31 is enabled (always false for physical CPUs) | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4375) | +| `VM::HYPERVISOR_STR` | Check for hypervisor brand string length (would be around 2 characters in a host machine) | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4401) | +| `VM::TIMER` | Check for timing anomalies in the system | 🐧🪟🍏 | 150% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4587) | +| `VM::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 | 🐧🪟🍏 | 35% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7159) | +| `VM::MAC` | Check if mac address starts with certain VM designated values | 🐧 | 20% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5216) | +| `VM::TEMPERATURE` | Check for device's temperature | 🐧 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6068) | +| `VM::SYSTEMD` | Check result from systemd-detect-virt tool | 🐧 | 35% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5097) | +| `VM::CVENDOR` | Check if the chassis vendor is a VM vendor | 🐧 | 65% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5121) | +| `VM::CTYPE` | Check if the chassis type is valid (it's very often invalid in VMs) | 🐧 | 20% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5146) | +| `VM::DOCKERENV` | Check if /.dockerenv or /.dockerinit file is present | 🐧 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5164) | +| `VM::DMIDECODE` | Check if dmidecode output matches a VM brand | 🐧 | 55% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5179) | +| `VM::DMESG` | Check if dmesg output matches a VM brand | 🐧 | 55% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5322) | +| `VM::HWMON` | Check if /sys/class/hwmon/ directory is present. If not, likely a VM | 🐧 | 35% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5363) | +| `VM::DLL` | Check for VM-specific DLLs | 🪟 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7459) | +| `VM::HWMODEL` | Check if the sysctl for the hwmodel does not contain the "Mac" string | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7183) | +| `VM::WINE` | Check if the function "wine_get_unix_file_name" is present and if the OS booted from a VHD container | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7490) | +| `VM::POWER_CAPABILITIES` | Check what power states are enabled | 🪟 | 45% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7600) | +| `VM::PROCESSES` | Check for any VM processes that are active | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6079) | +| `VM::LINUX_USER_HOST` | Check for default VM username and hostname for linux | 🐧 | 10% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5373) | +| `VM::GAMARUE` | Check for Gamarue ransomware technique which compares VM-specific Window product IDs | 🪟 | 10% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7660) | +| `VM::BOCHS_CPU` | Check for various Bochs-related emulation oversights through CPU checks | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4429) | +| `VM::MAC_MEMSIZE` | Check if memory is too low for MacOS system | 🍏 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7219) | +| `VM::MAC_IOKIT` | Check MacOS' IO kit registry for VM-specific strings | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7252) | +| `VM::IOREG_GREP` | Check for VM-strings in ioreg commands for MacOS | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7349) | +| `VM::MAC_SIP` | Check for the status of System Integrity Protection and hv_mm_present | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7390) | +| `VM::VPC_INVALID` | Check for official VPC method | 🪟 | 75% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7769) | | `VM::SYSTEM_REGISTERS` | | | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L1) | -| `VM::VMWARE_IOMEM` | Check for VMware string in /proc/iomem | 🐧 | 65% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5471) | -| `VM::VMWARE_IOPORTS` | Check for VMware string in /proc/ioports | 🐧 | 70% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5983) | -| `VM::VMWARE_SCSI` | Check for VMware string in /proc/scsi/scsi | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5780) | -| `VM::VMWARE_DMESG` | Check for VMware-specific device name in dmesg output | 🐧 | 65% | Admin | | Disabled by default | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5799) | -| `VM::VMWARE_STR` | Check str assembly instruction method for VMware | 🪟 | 35% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7889) | -| `VM::VMWARE_BACKDOOR` | Check for official VMware io port backdoor technique | 🪟 | 100% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7914) | -| `VM::MUTEX` | Check for mutex strings of VM brands | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7975) | -| `VM::THREAD_MISMATCH` | Check if the system's thread count matches the expected thread count for the detected CPU model | 🐧🪟🍏 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4510) | -| `VM::CUCKOO_DIR` | Check for cuckoo directory using crt and WIN API directory functions | 🪟 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8061) | -| `VM::CUCKOO_PIPE` | Check for Cuckoo specific piping mechanism | 🪟 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8117) | -| `VM::AZURE` | Check for default Azure hostname format (Azure uses Hyper-V as their base VM brand) | 🐧🪟 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6385) | -| `VM::DISPLAY` | Check for display configurations commonly found in VMs | 🪟 | 25% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8173) | -| `VM::DEVICE_STRING` | Check if bogus device string would be accepted | 🪟 | 25% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8208) | -| `VM::BLUESTACKS_FOLDERS` | Check for the presence of BlueStacks-specific folders | 🐧 | 5% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5487) | -| `VM::CPUID_SIGNATURE` | Check for signatures in leaf 0x40000001 in CPUID | 🐧🪟🍏 | 95% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4538) | -| `VM::KGT_SIGNATURE` | Check for Intel KGT (Trusty branch) hypervisor signature in CPUID | 🐧🪟🍏 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4564) | -| `VM::QEMU_VIRTUAL_DMI` | Check for presence of QEMU in the /sys/devices/virtual/dmi/id directory | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5568) | -| `VM::QEMU_USB` | Check for presence of QEMU in the /sys/kernel/debug/usb/devices directory | 🐧 | 20% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5597) | -| `VM::HYPERVISOR_DIR` | Check for presence of any files in /sys/hypervisor directory | 🐧 | 20% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5625) | -| `VM::UML_CPU` | Check for the "UML" string in the CPU brand | 🐧 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5673) | -| `VM::KMSG` | Check for any indications of hypervisors in the kernel message logs | 🐧 | 5% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5703) | -| `VM::VBOX_MODULE` | Check for a VBox kernel module | 🐧 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5757) | -| `VM::SYSINFO_PROC` | Check for potential VM info in /proc/sysinfo | 🐧 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5833) | -| `VM::DMI_SCAN` | Check for string matches of VM brands in the linux DMI | 🐧 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5855) | -| `VM::SMBIOS_VM_BIT` | Check for the VM bit in the SMBIOS data | 🐧 | 50% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5938) | -| `VM::PODMAN_FILE` | Check for podman file in /run/ | 🐧 | 5% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5968) | -| `VM::WSL_PROC` | Check for WSL or microsoft indications in /proc/ subdirectories | 🐧 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6000) | -| `VM::DRIVERS` | Check for VM-specific names for drivers | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8225) | -| `VM::DISK_SERIAL` | Check for serial numbers of virtual disks | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8323) | -| `VM::IVSHMEM` | Check for IVSHMEM device presence | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8562) | -| `VM::GPU_CAPABILITIES` | Check for GPU capabilities related to VMs | 🪟 | 45% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8668) | -| `VM::DEVICE_HANDLES` | Check for vm-specific devices | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8706) | -| `VM::QEMU_FW_CFG` | Detect QEMU fw_cfg interface. This first checks the Device Tree for a fw-cfg node or hypervisor tag, then verifies the presence of the qemu_fw_cfg module and firmware directories in sysfs. | 🐧 | 70% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6028) | -| `VM::VIRTUAL_PROCESSORS` | Check if the number of virtual and logical processors are reported correctly by the system | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8809) | -| `VM::HYPERVISOR_QUERY` | Check if a call to NtQuerySystemInformation with the 0x9f leaf fills a _SYSTEM_HYPERVISOR_DETAIL_INFORMATION structure | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8839) | -| `VM::AMD_SEV` | Check for AMD-SEV MSR running on the system | 🐧🍏 | 50% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5510) | -| `VM::VIRTUAL_REGISTRY` | Check for particular object directory which is present in Sandboxie virtual environment but not in usual host systems | 🪟 | 90% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8907) | -| `VM::FIRMWARE` | Check for VM signatures on all firmware tables | 🐧🪟 | 100% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6445) | -| `VM::FILE_ACCESS_HISTORY` | Check if the number of accessed files are too low for a human-managed environment | 🐧 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6058) | -| `VM::AUDIO` | Check if no waveform-audio output devices are present in the system | 🪟 | 25% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9001) | -| `VM::NSJAIL_PID` | Check if process status matches with nsjail patterns with PID anomalies | 🐧 | 75% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6085) | -| `VM::PCI_DEVICES` | Check for PCI vendor and device IDs that are VM-specific | 🐧🪟 | 95% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6872) | -| `VM::ACPI_SIGNATURE` | Check for VM-specific ACPI device signatures | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9109) | -| `VM::TRAP` | Check if after raising two traps at the same RIP, a hypervisor interferes with the instruction pointer delivery | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9254) | -| `VM::UD` | Check if no waveform-audio output devices are present in the system | 🪟 | 25% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9001) | -| `VM::BLOCKSTEP` | Check if a hypervisor does not properly restore the interruptibility state after a VM-exit in compatibility mode | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9529) | -| `VM::DBVM` | Check if Dark Byte's VM is present | 🪟 | 150% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9576) | -| `VM::BOOT_LOGO` | Check boot logo for known VM images | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9695) | -| `VM::MAC_SYS` | Check for VM-strings in system profiler commands for MacOS | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7503) | -| `VM::KERNEL_OBJECTS` | Check for any signs of VMs in Windows kernel object entities | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9796) | -| `VM::NVRAM` | Check for known NVRAM signatures that are present on virtual firmware | 🪟 | 100% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9986) | -| `VM::SMBIOS_INTEGRITY` | Check if SMBIOS is malformed/corrupted in a way that is typical for VMs | 🪟 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10534) | -| `VM::EDID` | Check for non-standard EDID configurations | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10545) | -| `VM::CPU_HEURISTIC` | Check whether the CPU is genuine and its reported instruction capabilities are not masked | 🪟 | 90% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10801) | -| `VM::CLOCK` | Check the presence of system timers | 🪟 | 90% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L11269) | +| `VM::VMWARE_IOMEM` | Check for VMware string in /proc/iomem | 🐧 | 65% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5402) | +| `VM::VMWARE_IOPORTS` | Check for VMware string in /proc/ioports | 🐧 | 70% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5914) | +| `VM::VMWARE_SCSI` | Check for VMware string in /proc/scsi/scsi | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5711) | +| `VM::VMWARE_DMESG` | Check for VMware-specific device name in dmesg output | 🐧 | 65% | Admin | | Disabled by default | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5730) | +| `VM::VMWARE_STR` | Check str assembly instruction method for VMware | 🪟 | 35% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7820) | +| `VM::VMWARE_BACKDOOR` | Check for official VMware io port backdoor technique | 🪟 | 100% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7845) | +| `VM::MUTEX` | Check for mutex strings of VM brands | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7906) | +| `VM::THREAD_MISMATCH` | Check if the system's thread count matches the expected thread count for the detected CPU model | 🐧🪟🍏 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4509) | +| `VM::CUCKOO_DIR` | Check for cuckoo directory using crt and WIN API directory functions | 🪟 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7992) | +| `VM::CUCKOO_PIPE` | Check for Cuckoo specific piping mechanism | 🪟 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8048) | +| `VM::AZURE` | Check for default Azure hostname format (Azure uses Hyper-V as their base VM brand) | 🐧🪟 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6316) | +| `VM::DISPLAY` | Check for display configurations commonly found in VMs | 🪟 | 25% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8104) | +| `VM::DEVICE_STRING` | Check if bogus device string would be accepted | 🪟 | 25% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8139) | +| `VM::BLUESTACKS_FOLDERS` | Check for the presence of BlueStacks-specific folders | 🐧 | 5% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5418) | +| `VM::CPUID_SIGNATURE` | Check for signatures in leaf 0x40000001 in CPUID | 🐧🪟🍏 | 95% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4537) | +| `VM::KGT_SIGNATURE` | Check for Intel KGT (Trusty branch) hypervisor signature in CPUID | 🐧🪟🍏 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4563) | +| `VM::QEMU_VIRTUAL_DMI` | Check for presence of QEMU in the /sys/devices/virtual/dmi/id directory | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5499) | +| `VM::QEMU_USB` | Check for presence of QEMU in the /sys/kernel/debug/usb/devices directory | 🐧 | 20% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5528) | +| `VM::HYPERVISOR_DIR` | Check for presence of any files in /sys/hypervisor directory | 🐧 | 20% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5556) | +| `VM::UML_CPU` | Check for the "UML" string in the CPU brand | 🐧 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5604) | +| `VM::KMSG` | Check for any indications of hypervisors in the kernel message logs | 🐧 | 5% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5634) | +| `VM::VBOX_MODULE` | Check for a VBox kernel module | 🐧 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5688) | +| `VM::SYSINFO_PROC` | Check for potential VM info in /proc/sysinfo | 🐧 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5764) | +| `VM::DMI_SCAN` | Check for string matches of VM brands in the linux DMI | 🐧 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5786) | +| `VM::SMBIOS_VM_BIT` | Check for the VM bit in the SMBIOS data | 🐧 | 50% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5869) | +| `VM::PODMAN_FILE` | Check for podman file in /run/ | 🐧 | 5% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5899) | +| `VM::WSL_PROC` | Check for WSL or microsoft indications in /proc/ subdirectories | 🐧 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5931) | +| `VM::DRIVERS` | Check for VM-specific names for drivers | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8156) | +| `VM::DISK_SERIAL` | Check for serial numbers of virtual disks | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8254) | +| `VM::IVSHMEM` | Check for IVSHMEM device presence | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8493) | +| `VM::GPU_CAPABILITIES` | Check for GPU capabilities related to VMs | 🪟 | 45% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8599) | +| `VM::DEVICE_HANDLES` | Check for vm-specific devices | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8637) | +| `VM::QEMU_FW_CFG` | Detect QEMU fw_cfg interface. This first checks the Device Tree for a fw-cfg node or hypervisor tag, then verifies the presence of the qemu_fw_cfg module and firmware directories in sysfs. | 🐧 | 70% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5959) | +| `VM::VIRTUAL_PROCESSORS` | Check if the number of virtual and logical processors are reported correctly by the system | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8740) | +| `VM::HYPERVISOR_QUERY` | Check if a call to NtQuerySystemInformation with the 0x9f leaf fills a _SYSTEM_HYPERVISOR_DETAIL_INFORMATION structure | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8770) | +| `VM::AMD_SEV` | Check for AMD-SEV MSR running on the system | 🐧🍏 | 50% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5441) | +| `VM::VIRTUAL_REGISTRY` | Check for particular object directory which is present in Sandboxie virtual environment but not in usual host systems | 🪟 | 90% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8838) | +| `VM::FIRMWARE` | Check for VM signatures on all firmware tables | 🐧🪟 | 100% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6376) | +| `VM::FILE_ACCESS_HISTORY` | Check if the number of accessed files are too low for a human-managed environment | 🐧 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5989) | +| `VM::AUDIO` | Check if no waveform-audio output devices are present in the system | 🪟 | 25% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8932) | +| `VM::NSJAIL_PID` | Check if process status matches with nsjail patterns with PID anomalies | 🐧 | 75% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6016) | +| `VM::PCI_DEVICES` | Check for PCI vendor and device IDs that are VM-specific | 🐧🪟 | 95% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6803) | +| `VM::ACPI_SIGNATURE` | Check for VM-specific ACPI device signatures | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9040) | +| `VM::TRAP` | Check if after raising two traps at the same RIP, a hypervisor interferes with the instruction pointer delivery | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9185) | +| `VM::UD` | Check if no waveform-audio output devices are present in the system | 🪟 | 25% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8932) | +| `VM::BLOCKSTEP` | Check if a hypervisor does not properly restore the interruptibility state after a VM-exit in compatibility mode | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9460) | +| `VM::DBVM` | Check if Dark Byte's VM is present | 🪟 | 150% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9507) | +| `VM::BOOT_LOGO` | Check boot logo for known VM images | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9626) | +| `VM::MAC_SYS` | Check for VM-strings in system profiler commands for MacOS | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7434) | +| `VM::KERNEL_OBJECTS` | Check for any signs of VMs in Windows kernel object entities | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9727) | +| `VM::NVRAM` | Check for known NVRAM signatures that are present on virtual firmware | 🪟 | 100% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9917) | +| `VM::SMBIOS_INTEGRITY` | Check if SMBIOS is malformed/corrupted in a way that is typical for VMs | 🪟 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10454) | +| `VM::EDID` | Check for non-standard EDID configurations | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10465) | +| `VM::CPU_HEURISTIC` | Check whether the CPU is genuine and its reported instruction capabilities are not masked | 🪟 | 90% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10721) | +| `VM::CLOCK` | Check the presence of system timers | 🪟 | 90% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L11189) |
diff --git a/src/vmaware.hpp b/src/vmaware.hpp index 12e403c9..33221fd6 100644 --- a/src/vmaware.hpp +++ b/src/vmaware.hpp @@ -54,14 +54,14 @@ * * * ============================== SECTIONS ================================== - * - enums for publicly accessible techniques => line 546 - * - struct for internal cpu operations => line 718 - * - struct for internal memoization => line 3042 - * - struct for internal utility functions => line 3224 - * - struct for internal core components => line 11418 - * - start of VM detection technique list => line 4279 - * - start of public VM detection functions => line 11796 - * - start of externally defined variables => line 12816 + * - enums for publicly accessible techniques => line 545 + * - struct for internal cpu operations => line 717 + * - struct for internal memoization => line 3041 + * - struct for internal utility functions => line 3223 + * - struct for internal core components => line 11338 + * - start of VM detection technique list => line 4278 + * - start of public VM detection functions => line 11716 + * - start of externally defined variables => line 12736 * * * ============================== EXAMPLE =================================== @@ -4600,8 +4600,8 @@ struct VM { debug("TIMER: Running inside a binary translation layer"); return false; } - // will be used in cpuid measurements later - u16 cycle_threshold = 800; // average latency of a VMX/SVM VMEXIT + // will be used in cpuid measurements + u16 cycle_threshold = 800; // average latency of a VMX/SVM VMEXIT alone if (util::hyper_x() == HYPERV_ARTIFACT_VM) { cycle_threshold = 3250; // if we're running under Hyper-V, make VMAware detect nested virtualization } @@ -4615,13 +4615,12 @@ struct VM { return true; } - const u64 ITER_XOR = 50000000ULL; - const size_t CPUID_ITER = 100; // per leaf - const unsigned int leaves[] = { + constexpr u64 ITER_XOR = 100000000ULL; + constexpr size_t CPUID_ITER = 100; // per leaf + static constexpr unsigned int leaves[] = { 0xB, 0xD, 0x4, 0x1, 0x7, 0xA, 0x12, 0x5, 0x40000000u, 0x80000008u, 0x0 }; - const size_t n_leaves = sizeof(leaves) / sizeof(leaves[0]); - const size_t samples_expected = n_leaves * CPUID_ITER; + constexpr size_t n_leaves = sizeof(leaves) / sizeof(leaves[0]); unsigned hw = std::thread::hardware_concurrency(); if (hw == 0) hw = 1; @@ -4633,11 +4632,9 @@ struct VM { std::atomic t2_start(0), t2_end(0); std::atomic t2_accum(0); - std::vector samples; - samples.resize(samples_expected); - for (size_t i = 0; i < samples.size(); ++i) samples[i] = 0; + std::vector samples(100000, 0); - auto rdtsc = []() -> u64 { + auto rdtsc = []() noexcept -> u64 { #if (MSVC) return static_cast(__rdtsc()); #else @@ -4645,24 +4642,59 @@ struct VM { #endif }; - // best-effort affinity as a local lambda; on macOS it's a no-op - auto try_set_affinity = [](std::thread& t, unsigned core) { + struct affinity_cookie { + bool valid{ false }; + #if (WINDOWS) + HANDLE thread_handle{ nullptr }; + DWORD_PTR prev_mask{ 0 }; + #elif (LINUX) + pthread_t thread{ 0 }; + cpu_set_t prev_mask{}; + #endif + }; + + auto set_affinity = [](std::thread& t, unsigned core) -> affinity_cookie { + affinity_cookie cookie; #if (WINDOWS) HANDLE h = static_cast(t.native_handle()); DWORD_PTR mask = static_cast(1ULL) << core; - (void)SetThreadAffinityMask(h, mask); + DWORD_PTR prev = SetThreadAffinityMask(h, mask); + if (prev != 0) { + cookie.valid = true; + cookie.thread_handle = h; + cookie.prev_mask = prev; + } #elif (LINUX) - cpu_set_t cp; - CPU_ZERO(&cp); - CPU_SET(core, &cp); - (void)pthread_setaffinity_np(t.native_handle(), sizeof(cp), &cp); + pthread_t ph = t.native_handle(); + cpu_set_t prev; + if (pthread_getaffinity_np(ph, sizeof(prev), &prev) == 0) { + cpu_set_t cp; + CPU_ZERO(&cp); + CPU_SET(core, &cp); + (void)pthread_setaffinity_np(ph, sizeof(cp), &cp); + cookie.valid = true; + cookie.thread = ph; + cookie.prev_mask = prev; + } #else (void)t; (void)core; + #endif + return cookie; + }; + + auto restore_affinity = [](const affinity_cookie& cookie) { + if (!cookie.valid) return; + #if (WINDOWS) + (void)SetThreadAffinityMask(cookie.thread_handle, cookie.prev_mask); + #elif (LINUX) + (void)pthread_setaffinity_np(cookie.thread, sizeof(cookie.prev_mask), &cookie.prev_mask); + #else + (void)cookie; #endif }; thread_local u32 aux = 0; - auto cpuid = [&](unsigned int leaf) noexcept -> u64 { + auto cpuid = [](unsigned int leaf) noexcept -> u64 { #if (MSVC) // make regs volatile so writes cannot be optimized out, if this isn't added and the code is compiled in release mode, cycles would be around 40 even under Hyper-V volatile int regs[4]{}; @@ -4699,7 +4731,7 @@ struct VM { volatile unsigned int a, b, c, d; - // this differs from the code above because a, b, c and d are effectively "used" + // this differs from the code above because a, b, c and d are effectively used // the compiler must honor the write to a volatile variable asm volatile("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) @@ -4718,16 +4750,21 @@ struct VM { #endif }; - // calculate_latency (kept as provided, minimal adaptations) auto calculate_latency = [&](const std::vector& samples_in) -> u64 { if (samples_in.empty()) return 0; const size_t N = samples_in.size(); if (N == 1) return samples_in[0]; + + // local sorted copy std::vector s = samples_in; - std::sort(s.begin(), s.end()); + std::sort(s.begin(), s.end()); // ascending + + // tiny-sample short-circuits if (N <= 4) return s.front(); + // median (and works for sorted input) auto median_of_sorted = [](const std::vector& v, size_t lo, size_t hi) -> u64 { + // this is the median of v[lo..hi-1], requires 0 <= lo < hi const size_t len = hi - lo; if (len == 0) return 0; const size_t mid = lo + (len / 2); @@ -4735,6 +4772,7 @@ struct VM { return (v[mid - 1] + v[mid]) / 2; }; + // the robust center: median M and MAD -> approximate sigma const u64 M = median_of_sorted(s, 0, s.size()); std::vector absdev; absdev.reserve(N); @@ -4744,73 +4782,100 @@ struct VM { } std::sort(absdev.begin(), absdev.end()); const u64 MAD = median_of_sorted(absdev, 0, absdev.size()); - const long double kmad_to_sigma = 1.4826L; + // convert MAD to an approximate standard-deviation-like measure + const long double kmad_to_sigma = 1.4826L; // consistent for normal approx const long double sigma = (MAD == 0) ? 1.0L : (static_cast(MAD) * kmad_to_sigma); + // find the densest small-valued cluster by sliding a fixed-count window + // this locates the most concentrated group of samples (likely it would be the true VMEXIT cluster) + // const size_t frac_win = (N * 8 + 99) / 100; // ceil(N * 0.08) + // const size_t win = std::min(N, std::max(MIN_WIN, frac_win)); const size_t MIN_WIN = 10; - const size_t frac_win = static_cast(std::ceil(static_cast(N) * 0.08)); - size_t inner_win = frac_win; - if (inner_win < MIN_WIN) inner_win = MIN_WIN; - const size_t win = (N < inner_win) ? N : inner_win; + const size_t win = std::min( + N, + std::max( + MIN_WIN, + static_cast(std::ceil(static_cast(N) * 0.08)) + ) + ); size_t best_i = 0; - u64 best_span = (s.back() - s.front()) + 1; + u64 best_span = (s.back() - s.front()) + 1; // large initial for (size_t i = 0; i + win <= N; ++i) { const u64 span = s[i + win - 1] - s[i]; - if (span < best_span) { best_span = span; best_i = i; } + if (span < best_span) { + best_span = span; + best_i = i; + } } + // expand the initial window greedily while staying "tight" + // allow expansion while adding samples does not more than multiply the span by EXPAND_FACTOR constexpr long double EXPAND_FACTOR = 1.5L; size_t cluster_lo = best_i; - size_t cluster_hi = best_i + win; + size_t cluster_hi = best_i + win; // exclusive + // expand left while (cluster_lo > 0) { const u64 new_span = s[cluster_hi - 1] - s[cluster_lo - 1]; if (static_cast(new_span) <= EXPAND_FACTOR * static_cast(best_span) || (s[cluster_hi - 1] <= (s[cluster_lo - 1] + static_cast(std::ceil(3.0L * sigma))))) { --cluster_lo; - if (new_span < best_span) best_span = new_span; + best_span = std::min(best_span, new_span); } else break; } + // expand right while (cluster_hi < N) { const u64 new_span = s[cluster_hi] - s[cluster_lo]; if (static_cast(new_span) <= EXPAND_FACTOR * static_cast(best_span) || (s[cluster_hi] <= (s[cluster_lo] + static_cast(std::ceil(3.0L * sigma))))) { ++cluster_hi; - if (new_span < best_span) best_span = new_span; + best_span = std::min(best_span, new_span); } else break; } const size_t cluster_size = (cluster_hi > cluster_lo) ? (cluster_hi - cluster_lo) : 0; + + // cluster must be reasonably dense and cover a non-negligible portion of samples, so this is pure sanity checks const double fraction_in_cluster = static_cast(cluster_size) / static_cast(N); - size_t threshold = N / 50; - if (threshold < 5) threshold = 5; - const size_t MIN_CLUSTER = (threshold < N) ? threshold : N; + const size_t MIN_CLUSTER = std::min(static_cast(std::max(5, static_cast(N / 50))), N); // at least 2% or 5 elements if (cluster_size < MIN_CLUSTER || fraction_in_cluster < 0.02) { - size_t fallback_count = static_cast(std::floor(static_cast(N) * 0.10)); - if (fallback_count < 1) fallback_count = 1; + // low-percentile (10th) trimmed median + const size_t fallback_count = std::max(1, static_cast(std::floor(static_cast(N) * 0.10))); + // median of lowest fallback_count elements (if fallback_count==1 that's smallest) if (fallback_count == 1) return s.front(); const size_t mid = fallback_count / 2; if (fallback_count & 1) return s[mid]; return (s[mid - 1] + s[mid]) / 2; } + // now we try to get a robust estimate inside the cluster, trimmed mean (10% trim) centered on cluster const size_t trim_count = static_cast(std::floor(static_cast(cluster_size) * 0.10)); - size_t lo = cluster_lo + trim_count; - size_t hi = cluster_hi - trim_count; + const size_t lo = cluster_lo + trim_count; + const size_t hi = cluster_hi - trim_count; // exclusive if (hi <= lo) { + // degenerate -> median of cluster return median_of_sorted(s, cluster_lo, cluster_hi); } + // sum with long double to avoid overflow and better rounding long double sum = 0.0L; for (size_t i = lo; i < hi; ++i) sum += static_cast(s[i]); const long double avg = sum / static_cast(hi - lo); u64 result = static_cast(std::llround(avg)); + + // final sanity adjustments: + // if the computed result is suspiciously far from the global median (e.g., > +6*sigma) + // clamp toward the median to avoid choosing a high noisy cluster by mistake const long double diff_from_med = static_cast(result) - static_cast(M); if (diff_from_med > 0 && diff_from_med > (6.0L * sigma)) { + // clamp to median + 4*sigma (conservative) result = static_cast(std::llround(static_cast(M) + 4.0L * sigma)); } + + // also, if result is zero (shouldn't be) or extremely small, return a smallest observed sample if (result == 0) result = s.front(); + return result; }; @@ -4820,12 +4885,29 @@ struct VM { VMAWARE_UNUSED(tmp); } + // if hypervisor downscales TSC globally, this will catch it + const u64 calib_start = rdtsc(); + { + volatile u64 x = 0xDEADBEEFCAFEBABEULL; + for (u64 i = 0; i < ITER_XOR; ++i) { + x ^= i; + x = (x << 1) ^ (x >> 3); + } + VMAWARE_UNUSED(x); + } + const u64 calib_end = rdtsc(); + const u64 calib_delta = (calib_end > calib_start) ? (calib_end - calib_start) : 0; + + ready_count.store(0, std::memory_order_release); + state.store(0, std::memory_order_release); + // Thread 1: start near same cycle, do XOR work, set end std::thread th1([&]() { ready_count.fetch_add(1, std::memory_order_acq_rel); - while (ready_count.load(std::memory_order_acquire) < 2) { /* spin */ } + while (ready_count.load(std::memory_order_acquire) < 2) + _mm_pause(); - u64 s = rdtsc(); + const u64 s = rdtsc(); t1_start.store(s, std::memory_order_release); state.store(1, std::memory_order_release); @@ -4836,15 +4918,16 @@ struct VM { } VMAWARE_UNUSED(x); - u64 e = rdtsc(); + const u64 e = rdtsc(); t1_end.store(e, std::memory_order_release); state.store(2, std::memory_order_release); }); - // Thread 2: barrier, sample start, perform cpuid sampling and keep accumulating rdtsc deltas + // Thread 2: rdtsc and cpuid spammer, forces hypervisor to downscale TSC if patch is present; if interception disabled, caught by cpuid latency std::thread th2([&]() { ready_count.fetch_add(1, std::memory_order_acq_rel); - while (ready_count.load(std::memory_order_acquire) < 2) { /* spin */ } + while (ready_count.load(std::memory_order_acquire) < 2) + _mm_pause(); u64 last = rdtsc(); t2_start.store(last, std::memory_order_release); @@ -4853,55 +4936,56 @@ struct VM { u64 acc = 0; size_t idx = 0; - // per-leaf sampling but do not stop entirely if thread1 is still running after completing planned samples - for (size_t li = 0; li < n_leaves; ++li) { - const unsigned int leaf = leaves[li]; - for (unsigned i = 0; i < CPUID_ITER; ++i) { - // accumulate rdtsc delta up to now (this includes time since last sample and includes previous cpuid) - u64 now = rdtsc(); - acc += (now >= last) ? (now - last) : (u64)((u64)0 - last + now); - last = now; - - // run cpuid and store latency - if (idx < samples.size()) samples[idx] = cpuid(leaf); - ++idx; - - // if thread1 finished, capture a final rdtsc and exit sampling loops - if (state.load(std::memory_order_acquire) == 2) { - u64 final_now = rdtsc(); - acc += (final_now >= last) ? (final_now - last) : (u64)((u64)0 - last + final_now); - last = final_now; - t2_end.store(final_now, std::memory_order_release); - t2_accum.store(acc, std::memory_order_release); - return; + // for each leaf do CPUID_ITER samples, then repeat + while (state.load(std::memory_order_acquire) != 2) { + for (size_t li = 0; li < n_leaves; ++li) { + const unsigned int leaf = leaves[li]; + + for (unsigned i = 0; i < CPUID_ITER; ++i) { + // read rdtsc and accumulate delta + const u64 now = rdtsc(); + acc += (now >= last) ? (now - last) : (u64)((u64)0 - last + now); + last = now; + + // store latency if buffer has space + if (idx < samples.size()) samples[idx] = cpuid(leaf); + ++idx; + + // if thread1 finished + if (state.load(std::memory_order_acquire) == 2) break; } - } - } - // If we reach here, we completed planned samples but thread1 might still be running, so continue spamming - while (state.load(std::memory_order_acquire) != 2) { - u64 now = rdtsc(); - acc += (now >= last) ? (now - last) : (u64)((u64)0 - last + now); - last = now; + if (state.load(std::memory_order_acquire) == 2) break; + } } - // final sample after seeing finished - u64 final_now = rdtsc(); + // final rdtsc after detecting finish + const u64 final_now = rdtsc(); acc += (final_now >= last) ? (final_now - last) : (u64)((u64)0 - last + final_now); last = final_now; + + // publish results t2_end.store(final_now, std::memory_order_release); t2_accum.store(acc, std::memory_order_release); }); - // Try to pin to different cores + // try to pin to different cores + affinity_cookie cookie1{}; + affinity_cookie cookie2{}; if (hw >= 2) { - try_set_affinity(th1, 0); - try_set_affinity(th2, 1); + if (hw >= 2) { + cookie1 = set_affinity(th1, 0); + cookie2 = set_affinity(th2, 1); + } } th1.join(); th2.join(); + restore_affinity(cookie1); + restore_affinity(cookie2); + + // collect results const u64 a = t1_start.load(std::memory_order_acquire); const u64 b = t1_end.load(std::memory_order_acquire); const u64 c = t2_start.load(std::memory_order_acquire); @@ -4912,14 +4996,14 @@ struct VM { const u64 t2_delta = acc; std::vector used; - used.reserve(samples_expected); - for (size_t i = 0; i < samples.size(); ++i) - if (samples[i] != 0) + for (size_t i = 0; i < samples.size(); ++i) + if (samples[i] != 0) used.push_back(samples[i]); const u64 cpuid_latency = calculate_latency(used); - debug("TIMER: thread1 cycles: start=", a, " end=", b, " delta=", t1_delta); - debug("TIMER: thread2 cycles: start=", c, " end=", d, " acc=", t2_delta); + debug("TIMER: calibration cycles: start=", calib_start, " delta=", calib_delta); + debug("TIMER: thread1 cycles: start=", a, " delta=", t1_delta); + debug("TIMER: thread2 cycles: start=", c, " delta=", t2_delta); debug("TIMER: vmexit latency: ", cpuid_latency); if (cpuid_latency >= cycle_threshold) { @@ -4931,16 +5015,20 @@ struct VM { return true; } - if (t1_delta == 0) { - return false; + if (t1_delta == 0 || calib_delta == 0) { + return true; } const double ratio = double(t2_delta) / double(t1_delta); + const double calibration_ratio = static_cast(t1_delta) / static_cast(calib_delta); + + // if thread 1 was faster than thread 2, hypervisor downscaled TSC per-vCPU in either cpuid or rdtsc if (ratio < 0.95 || ratio > 1.05) { - debug("TIMER: VMAware detected an hypervisor offsetting TSC: ", ratio); + return true; } - else { - debug("TIMER: Ratio: ", ratio); + // if calibration was much faster than thread 1, hypervisor downscaled TSC globally while thread 2 was spamming + if (calibration_ratio < 0.95) { + return true; } #if (WINDOWS) @@ -4957,8 +5045,7 @@ struct VM { ProcessorInformation = 11 }; - HMODULE hPowr = GetModuleHandleA("powrprof.dll"); - if (!hPowr) hPowr = LoadLibraryA("powrprof.dll"); + const HMODULE hPowr = GetModuleHandleA("powrprof.dll"); if (!hPowr) return 0; const char* names[] = { "CallNtPowerInformation" };