diff --git a/docs/documentation.md b/docs/documentation.md index 5587e81f..89587eb4 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -499,95 +499,96 @@ 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#L2277) | -| `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#L2295) | -| `VM::HYPERVISOR_BIT` | Check if hypervisor feature bit in CPUID eax bit 31 is enabled (always false for physical CPUs) | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L2369) | -| `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#L2400) | -| `VM::TIMER` | Check for timing anomalies in the system | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4233) | -| `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#L6474) | -| `VM::MAC` | Check if mac address starts with certain VM designated values | 🐧 | 20% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4636) | -| `VM::TEMPERATURE` | Check for device's temperature | 🐧 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5486) | -| `VM::SYSTEMD` | Check result from systemd-detect-virt tool | 🐧 | 35% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4517) | -| `VM::CVENDOR` | Check if the chassis vendor is a VM vendor | 🐧 | 65% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4541) | -| `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#L4566) | -| `VM::DOCKERENV` | Check if /.dockerenv or /.dockerinit file is present | 🐧 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4584) | -| `VM::DMIDECODE` | Check if dmidecode output matches a VM brand | 🐧 | 55% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4599) | -| `VM::DMESG` | Check if dmesg output matches a VM brand | 🐧 | 55% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4742) | -| `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#L4783) | -| `VM::DLL` | Check for VM-specific DLLs | 🪟 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6770) | -| `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#L6498) | -| `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#L6801) | -| `VM::POWER_CAPABILITIES` | Check what power states are enabled | 🪟 | 45% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6840) | -| `VM::PROCESSES` | Check for any VM processes that are active | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5497) | -| `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#L4793) | -| `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#L6900) | -| `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#L2428) | -| `VM::MAC_MEMSIZE` | Check if memory is too low for MacOS system | 🍏 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6531) | -| `VM::MAC_IOKIT` | Check MacOS' IO kit registry for VM-specific strings | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6564) | -| `VM::IOREG_GREP` | Check for VM-strings in ioreg commands for MacOS | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6661) | -| `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#L6718) | -| `VM::VPC_INVALID` | Check for official VPC method | 🪟 | 75% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6998) | -| `VM::SIDT` | Check for uncommon IDT virtual addresses | 🐧🪟 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5524) | -| `VM::SGDT` | Check for sgdt instruction method | 🪟 | 50% | | | code documentation paper in /papers/www.offensivecomputing.net_vm.pdf (top-most byte signature) | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7049) | -| `VM::SLDT` | Check for sldt instruction method | 🪟 | 50% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7117) | -| `VM::SMSW` | Check for SMSW assembly instruction technique | 🪟 | 50% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7172) | -| `VM::VMWARE_IOMEM` | Check for VMware string in /proc/iomem | 🐧 | 65% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4822) | -| `VM::VMWARE_IOPORTS` | Check for VMware string in /proc/ioports | 🐧 | 70% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5332) | -| `VM::VMWARE_SCSI` | Check for VMware string in /proc/scsi/scsi | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5131) | -| `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#L5150) | -| `VM::VMWARE_STR` | Check str assembly instruction method for VMware | 🪟 | 35% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7199) | -| `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#L7224) | -| `VM::MUTEX` | Check for mutex strings of VM brands | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7285) | -| `VM::INTEL_THREAD_MISMATCH` | Check for Intel I-series CPU thread count database if it matches the system's thread count | 🐧🪟🍏 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L2516) | -| `VM::XEON_THREAD_MISMATCH` | Check for Intel Xeon CPU thread count database if it matches the system's thread count | 🐧🪟🍏 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L3493) | -| `VM::AMD_THREAD_MISMATCH` | Check for AMD CPU thread count database if it matches the system's thread count | 🐧🪟🍏 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L3649) | -| `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#L7364) | -| `VM::CUCKOO_PIPE` | Check for Cuckoo specific piping mechanism | 🪟 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7420) | +| `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#L2255) | +| `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#L2273) | +| `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#L2347) | +| `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#L2378) | +| `VM::TIMER` | Check for timing anomalies in the system | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4204) | +| `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#L6515) | +| `VM::MAC` | Check if mac address starts with certain VM designated values | 🐧 | 20% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4608) | +| `VM::TEMPERATURE` | Check for device's temperature | 🐧 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5458) | +| `VM::SYSTEMD` | Check result from systemd-detect-virt tool | 🐧 | 35% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4489) | +| `VM::CVENDOR` | Check if the chassis vendor is a VM vendor | 🐧 | 65% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4513) | +| `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#L4538) | +| `VM::DOCKERENV` | Check if /.dockerenv or /.dockerinit file is present | 🐧 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4556) | +| `VM::DMIDECODE` | Check if dmidecode output matches a VM brand | 🐧 | 55% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4571) | +| `VM::DMESG` | Check if dmesg output matches a VM brand | 🐧 | 55% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4714) | +| `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#L4755) | +| `VM::DLL` | Check for VM-specific DLLs | 🪟 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6811) | +| `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#L6539) | +| `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#L6842) | +| `VM::POWER_CAPABILITIES` | Check what power states are enabled | 🪟 | 45% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6881) | +| `VM::PROCESSES` | Check for any VM processes that are active | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5469) | +| `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#L4765) | +| `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#L6941) | +| `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#L2406) | +| `VM::MAC_MEMSIZE` | Check if memory is too low for MacOS system | 🍏 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6572) | +| `VM::MAC_IOKIT` | Check MacOS' IO kit registry for VM-specific strings | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6605) | +| `VM::IOREG_GREP` | Check for VM-strings in ioreg commands for MacOS | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6702) | +| `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#L6759) | +| `VM::VPC_INVALID` | Check for official VPC method | 🪟 | 75% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7039) | +| `VM::SIDT` | Check for uncommon IDT virtual addresses | 🐧🪟 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5496) | +| `VM::SGDT` | Check for sgdt instruction method | 🪟 | 50% | | | code documentation paper in /papers/www.offensivecomputing.net_vm.pdf (top-most byte signature) | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7090) | +| `VM::SLDT` | Check for sldt instruction method | 🪟 | 50% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7158) | +| `VM::SMSW` | Check for SMSW assembly instruction technique | 🪟 | 50% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7213) | +| `VM::VMWARE_IOMEM` | Check for VMware string in /proc/iomem | 🐧 | 65% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4794) | +| `VM::VMWARE_IOPORTS` | Check for VMware string in /proc/ioports | 🐧 | 70% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5304) | +| `VM::VMWARE_SCSI` | Check for VMware string in /proc/scsi/scsi | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5103) | +| `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#L5122) | +| `VM::VMWARE_STR` | Check str assembly instruction method for VMware | 🪟 | 35% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7240) | +| `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#L7265) | +| `VM::MUTEX` | Check for mutex strings of VM brands | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7326) | +| `VM::INTEL_THREAD_MISMATCH` | Check for Intel I-series CPU thread count database if it matches the system's thread count | 🐧🪟🍏 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L2487) | +| `VM::XEON_THREAD_MISMATCH` | Check for Intel Xeon CPU thread count database if it matches the system's thread count | 🐧🪟🍏 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L3464) | +| `VM::AMD_THREAD_MISMATCH` | Check for AMD CPU thread count database if it matches the system's thread count | 🐧🪟🍏 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L3620) | +| `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#L7412) | +| `VM::CUCKOO_PIPE` | Check for Cuckoo specific piping mechanism | 🪟 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7468) | | `VM::AZURE` | | | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L1) | -| `VM::DISPLAY` | Check for display configurations commonly found in VMs | 🪟 | 35% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7476) | -| `VM::DEVICE_STRING` | Check if bogus device string would be accepted | 🪟 | 25% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7511) | -| `VM::BLUESTACKS_FOLDERS` | Check for the presence of BlueStacks-specific folders | 🐧 | 5% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4838) | -| `VM::CPUID_SIGNATURE` | Check for signatures in leaf 0x40000001 in CPUID | 🐧🪟🍏 | 95% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4180) | -| `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#L4209) | -| `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#L4919) | -| `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#L4948) | -| `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#L4976) | -| `VM::UML_CPU` | Check for the "UML" string in the CPU brand | 🐧 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5024) | -| `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#L5054) | -| `VM::VBOX_MODULE` | Check for a VBox kernel module | 🐧 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5108) | -| `VM::SYSINFO_PROC` | Check for potential VM info in /proc/sysinfo | 🐧 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5184) | -| `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#L5206) | -| `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#L5287) | -| `VM::PODMAN_FILE` | Check for podman file in /run/ | 🐧 | 5% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5317) | -| `VM::WSL_PROC` | Check for WSL or microsoft indications in /proc/ subdirectories | 🐧 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5349) | -| `VM::DRIVERS` | Check for VM-specific names for drivers | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7528) | -| `VM::DISK_SERIAL` | Check for serial numbers of virtual disks | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7626) | -| `VM::IVSHMEM` | Check for IVSHMEM device presence | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7845) | -| `VM::GPU_CAPABILITIES` | Check for GPU capabilities related to VMs | 🪟 | 45% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7944) | -| `VM::DEVICE_HANDLES` | Check for vm-specific devices | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7982) | -| `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#L5377) | -| `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#L8078) | -| `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#L8108) | -| `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#L4861) | -| `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#L8169) | -| `VM::FIRMWARE` | Check for VM signatures on all firmware tables | 🐧🪟 | 100% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5678) | -| `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#L5407) | -| `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#L8254) | -| `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#L5434) | -| `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#L6102) | -| `VM::ACPI_SIGNATURE` | Check for VM-specific ACPI device signatures | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8352) | -| `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#L8497) | -| `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#L8254) | -| `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#L8721) | -| `VM::DBVM` | Check if Dark Byte's VM is present | 🪟 | 150% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8768) | -| `VM::BOOT_LOGO` | Check boot logo for known VM images | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8886) | -| `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#L6745) | -| `VM::OBJECTS` | Check for any signs of VMs in Windows kernel object entities | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8978) | -| `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#L9147) | -| `VM::SMBIOS_INTEGRITY` | Check if SMBIOS is malformed/corrupted in a way that is typical for VMs | 🪟 | 60% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9394) | -| `VM::EDID` | Check for non-standard EDID configurations | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9405) | -| `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#L9606) | -| `VM::CLOCK` | Check the presence of system timers | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10097) | +| `VM::DISPLAY` | Check for display configurations commonly found in VMs | 🪟 | 35% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7524) | +| `VM::DEVICE_STRING` | Check if bogus device string would be accepted | 🪟 | 25% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7559) | +| `VM::BLUESTACKS_FOLDERS` | Check for the presence of BlueStacks-specific folders | 🐧 | 5% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4810) | +| `VM::CPUID_SIGNATURE` | Check for signatures in leaf 0x40000001 in CPUID | 🐧🪟🍏 | 95% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4151) | +| `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#L4180) | +| `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#L4891) | +| `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#L4920) | +| `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#L4948) | +| `VM::UML_CPU` | Check for the "UML" string in the CPU brand | 🐧 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4996) | +| `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#L5026) | +| `VM::VBOX_MODULE` | Check for a VBox kernel module | 🐧 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5080) | +| `VM::SYSINFO_PROC` | Check for potential VM info in /proc/sysinfo | 🐧 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5156) | +| `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#L5178) | +| `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#L5259) | +| `VM::PODMAN_FILE` | Check for podman file in /run/ | 🐧 | 5% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5289) | +| `VM::WSL_PROC` | Check for WSL or microsoft indications in /proc/ subdirectories | 🐧 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5321) | +| `VM::DRIVERS` | Check for VM-specific names for drivers | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7576) | +| `VM::DISK_SERIAL` | Check for serial numbers of virtual disks | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7674) | +| `VM::IVSHMEM` | Check for IVSHMEM device presence | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7893) | +| `VM::GPU_CAPABILITIES` | Check for GPU capabilities related to VMs | 🪟 | 45% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7992) | +| `VM::DEVICE_HANDLES` | Check for vm-specific devices | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8030) | +| `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#L5349) | +| `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#L8133) | +| `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#L8163) | +| `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#L4833) | +| `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#L8224) | +| `VM::FIRMWARE` | Check for VM signatures on all firmware tables | 🐧🪟 | 100% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5650) | +| `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#L5379) | +| `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#L8309) | +| `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#L5406) | +| `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#L6077) | +| `VM::ACPI_SIGNATURE` | Check for VM-specific ACPI device signatures | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8407) | +| `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#L8552) | +| `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#L8309) | +| `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#L8779) | +| `VM::DBVM` | Check if Dark Byte's VM is present | 🪟 | 150% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8826) | +| `VM::BOOT_LOGO` | Check boot logo for known VM images | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8945) | +| `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#L6786) | +| `VM::OBJECTS` | Check for any signs of VMs in Windows kernel object entities | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9037) | +| `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#L9206) | +| `VM::SMBIOS_INTEGRITY` | Check if SMBIOS is malformed/corrupted in a way that is typical for VMs | 🪟 | 60% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9537) | +| `VM::EDID` | Check for non-standard EDID configurations | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9548) | +| `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#L9782) | +| `VM::CLOCK` | Check the presence of system timers | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10220) | +| `VM::POST` | Check for anomalies in BIOS POST time | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10315) |
diff --git a/src/cli.cpp b/src/cli.cpp index 973ddc22..f1a49f6e 100755 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -464,7 +464,7 @@ static std::string vm_description(const std::string& vm_brand) { { brands::AZURE_HYPERV, "Azure Hyper-V is Microsoft's cloud-optimized hypervisor variant powering Azure VMs. Implements Azure-specific virtual devices like NVMe Accelerated Networking and vTPMs. Supports nested virtualization for running Hyper-V/containers within Azure VMs, enabling cloud-based CI/CD pipelines and dev/test environments." }, { brands::NANOVISOR, "NanoVisor is a Hyper-V modification serving as the host OS of Xbox's devices: the Xbox System Software. It contains 2 partitions: the \"Exclusive\" partition is a custom VM for games, while the other partition, called the \"Shared\" partition is a custom VM for running multiple apps including the OS itself. The OS was based on Windows 8 Core at the Xbox One launch in 2013." }, { brands::SIMPLEVISOR, "SimpleVisor is a minimalist Intel VT-x hypervisor by Alex Ionescu for Windows/Linux research. Demonstrates EPT-based memory isolation and hypercall handling. Used to study VM escapes and hypervisor rootkits, with hooks for intercepting CR3 changes and MSR accesses." }, - { brands::HYPERV_ARTIFACT, "VMAware detected Hyper-V operating as a Type 1 hypervisor, not as a guest virtual machine. Although your hardware/firmware signatures match Microsoft's Hyper-V architecture, we determined that you're running on baremetal, with the help of our \"Hyper-X\" mechanism that differentiates between the root partition (host OS) and guest VM environments. This prevents false positives, as Windows sometimes runs under Hyper-V (type 1) hypervisor." }, + { brands::HYPERV_ARTIFACT, "VMAware detected Hyper-V operating as a type 1 hypervisor, not as a guest virtual machine. Although your hardware/firmware signatures match Microsoft's Hyper-V architecture, we determined that you're running on baremetal. This prevents false positives, as Windows sometimes runs under Hyper-V (type 1) hypervisor." }, { brands::UML, "User-Mode Linux (UML) allows running Linux kernels as user-space processes using ptrace-based virtualization. Primarily used for kernel debugging and network namespace testing. Offers lightweight isolation without hardware acceleration, but requires host/guest kernel version matching for stable operation." }, { brands::POWERVM, "IBM PowerVM is a type 1 hypervisor for POWER9/10 systems, supporting Live Partition Mobility and Shared Processor Pools. Implements VIOS (Virtual I/O Server) for storage/networking virtualization, enabling concurrent AIX, IBM i, and Linux workloads with RAS features like predictive failure analysis." }, { brands::GCE, "Google Compute Engine (GCE) utilizes KVM-based virtualization with custom Titanium security chips for hardware root of trust. Features live migration during host maintenance and shielded VMs with UEFI secure boot. Underpins Google Cloud's Confidential Computing offering using AMD SEV-SNP memory encryption." }, @@ -828,6 +828,8 @@ static void general() { checker(VM::EDID, "EDID"); checker(VM::CPU_HEURISTIC, "CPU heuristics"); checker(VM::CLOCK, "system timers"); + checker(VM::POST, "BIOS POST time"); + // ADD NEW TECHNIQUE CHECKER HERE const auto t2 = std::chrono::high_resolution_clock::now(); diff --git a/src/vmaware.hpp b/src/vmaware.hpp index c2979d08..54dd4ed2 100644 --- a/src/vmaware.hpp +++ b/src/vmaware.hpp @@ -53,13 +53,13 @@ * * ============================== SECTIONS ================================== * - enums for publicly accessible techniques => line 535 - * - struct for internal cpu operations => line 716 - * - struct for internal memoization => line 1159 - * - struct for internal utility functions => line 1289 - * - struct for internal core components => line 10195 - * - start of VM detection technique list => line 2272 - * - start of public VM detection functions => line 10688 - * - start of externally defined variables => line 11667 + * - struct for internal cpu operations => line 717 + * - struct for internal memoization => line 1141 + * - struct for internal utility functions => line 1271 + * - struct for internal core components => line 10375 + * - start of VM detection technique list => line 2250 + * - start of public VM detection functions => line 10868 + * - start of externally defined variables => line 11871 * * * ============================== EXAMPLE =================================== @@ -571,6 +571,7 @@ struct VM { EDID, CPU_HEURISTIC, CLOCK, + POST, // Linux and Windows SIDT, @@ -832,33 +833,24 @@ struct VM { return "Unknown"; } - constexpr std::array ids {{ - cpu::leaf::brand1, - cpu::leaf::brand2, - cpu::leaf::brand3 - }}; + alignas(16) char buffer[49]{}; + u32* regs = reinterpret_cast(buffer); - std::string b; - b.reserve(48); // expected brand length + // unrolled calls to fill buffer directly + cpu::cpuid(regs[0], regs[1], regs[2], regs[3], cpu::leaf::brand1); + cpu::cpuid(regs[4], regs[5], regs[6], regs[7], cpu::leaf::brand2); + cpu::cpuid(regs[8], regs[9], regs[10], regs[11], cpu::leaf::brand3); - union Regs { - u32 i[4]; - char c[16]; - } regs{}; - - for (auto leaf_id : ids) { - cpu::cpuid(regs.i[0], regs.i[1], regs.i[2], regs.i[3], leaf_id); - b.append(regs.c, 16); - } + buffer[48] = '\0'; // do NOT touch trailing spaces for the AMD_THREAD_MISMATCH technique - const size_t nul = b.find('\0'); - if (nul != std::string::npos) b.resize(nul); + const size_t len = std::strlen(buffer); // left-trim only to handle stupid whitespaces before the brand string in ARM CPUs (Virtual CPUs) size_t start = 0; - while (start < b.size() && std::isspace(static_cast(b[start]))) ++start; - if (start) b.erase(0, start); + while (start < len && std::isspace(static_cast(buffer[start]))) ++start; + + std::string b(buffer + start, len - start); memo::cpu_brand::store(b); debug("CPU: ", b); @@ -897,25 +889,31 @@ struct VM { const EVT_HANDLE hCtx = EvtCreateRenderContext(2, props, EvtRenderContextValues); if (!hCtx) { EvtClose(hQuery); return 0; } - auto to_u64 = [](EVT_VARIANT& v)->uint64_t { + auto to_u64 = [](EVT_VARIANT& v) noexcept -> u64 { switch (v.Type) { case EvtVarTypeUInt32: return v.UInt32Val; case EvtVarTypeUInt64: return v.UInt64Val; - case EvtVarTypeInt32: return (uint64_t)v.Int32Val; - case EvtVarTypeInt64: return (uint64_t)v.Int64Val; + case EvtVarTypeInt32: return static_cast(v.Int32Val); + case EvtVarTypeInt64: return static_cast(v.Int64Val); case EvtVarTypeUInt16: return v.UInt16Val; - case EvtVarTypeSByte: return (uint8_t)v.SByteVal; + case EvtVarTypeSByte: return static_cast(v.SByteVal); case EvtVarTypeByte: return v.ByteVal; case EvtVarTypeBoolean: return v.BooleanVal ? 1ull : 0ull; - case EvtVarTypeDouble: return (uint64_t)v.DoubleVal; + case EvtVarTypeDouble: return static_cast(v.DoubleVal); case EvtVarTypeAnsiString: - if (v.AnsiStringVal) try { return std::stoull(std::string(v.AnsiStringVal)); } - catch (...) { return 0; } + if (v.AnsiStringVal) { + char* end; + return std::strtoull(v.AnsiStringVal, &end, 10); + } return 0; + case EvtVarTypeString: - if (v.StringVal) try { return std::stoull(std::wstring(v.StringVal)); } - catch (...) { return 0; } + if (v.StringVal) { + wchar_t* end; + return std::wcstoull(v.StringVal, &end, 10); + } return 0; + default: return 0; } @@ -939,10 +937,10 @@ struct VM { EvtClose(hEv); if (propCount < 2) continue; EVT_VARIANT* v = reinterpret_cast(buf.data()); - uint64_t num = to_u64(v[0]); + const u64 num = to_u64(v[0]); if (num != 0) continue; // only processor Number == 0 because thats where we will pin our thread on VM::TIMER and other functions - uint64_t nominal = to_u64(v[1]); - if (nominal != 0) { EvtClose(hCtx); EvtClose(hQuery); return static_cast(nominal); } + const u64 nominal = to_u64(v[1]); + if (nominal != 0) { EvtClose(hCtx); EvtClose(hQuery); return static_cast(nominal); } } } EvtClose(hCtx); @@ -952,46 +950,30 @@ struct VM { } #endif - static std::string cpu_manufacturer(const u32 p_leaf) { - auto cpuid_thingy = [](const u32 p_leaf, u32* regs, std::size_t start = 0, std::size_t end = 4) -> bool { - u32 x[4]{}; - cpu::cpuid(x[0], x[1], x[2], x[3], p_leaf); + [[nodiscard]] static std::string cpu_manufacturer(const u32 leaf_id) { + alignas(16) char buffer[13]{}; + u32* regs = reinterpret_cast(buffer); - for (; start < end; start++) { - *regs++ = x[start]; - } - - return true; - }; - - u32 sig_reg[3] = { 0 }; + u32 eax, ebx, ecx, edx; + cpu::cpuid(eax, ebx, ecx, edx, leaf_id); - // Start at index 1 to get EBX, ECX, EDX (x[1], x[2], x[3]) - if (!cpuid_thingy(p_leaf, sig_reg, 1, 4)) { + if (ebx == 0 && ecx == 0 && edx == 0) { return ""; } - if ((sig_reg[0] == 0) && (sig_reg[1] == 0) && (sig_reg[2] == 0)) { - return ""; - } - - auto strconvert = [](u32 n) -> std::string { - const char* bytes = reinterpret_cast(&n); - return std::string(bytes, 4); - }; - - std::stringstream ss; - - if (p_leaf >= 0x40000000) { - // Hypervisor vendor string order: EBX, ECX, EDX - ss << strconvert(sig_reg[0]) << strconvert(sig_reg[1]) << strconvert(sig_reg[2]); + if (leaf_id >= 0x40000000) { + regs[0] = ebx; + regs[1] = ecx; + regs[2] = edx; } else { - // Standard vendor string (leaf 0x0) order: EBX, EDX, ECX - ss << strconvert(sig_reg[0]) << strconvert(sig_reg[2]) << strconvert(sig_reg[1]); + regs[0] = ebx; + regs[1] = edx; + regs[2] = ecx; } - return ss.str(); + buffer[12] = '\0'; + return std::string(buffer); } struct stepping_struct { @@ -1723,17 +1705,16 @@ struct VM { return memo::hyperx::fetch(); } - // check if hypervisor feature bit in CPUID eax bit 31 is enabled (always false for physical CPUs) - auto is_hyperv_present = []() -> bool { + // Check if hypervisor feature bit in CPUID Leaf 1, ECX bit 31 is enabled + auto is_hyperv_present = []() noexcept -> bool { u32 unused, ecx = 0; cpu::cpuid(unused, unused, ecx, unused, 1); - const u32 mask = (1u << 31); - return (ecx & mask); + return (ecx >> 31) & 1; }; // 0x40000003 on EBX indicates the flags that a parent partition specified to create a child partition (https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/datatypes/hv_partition_privilege_mask) - auto is_root_partition = []() -> bool { + auto is_root_partition = []() noexcept -> bool { u32 ebx, unused = 0; cpu::cpuid(unused, ebx, unused, unused, 0x40000003); @@ -1747,13 +1728,12 @@ struct VM { * which is reflected in the maximum input value for hypervisor CPUID information as 11 * Essentially, it indicates that the hypervisor is managing the VM and that the VM is not running directly on hardware but rather in a virtualized environment */ - auto eax = []() -> u32 { - char out[sizeof(i32) * 4 + 1] = { 0 }; - cpu::cpuid(reinterpret_cast(out), cpu::leaf::hypervisor); + auto eax = []() noexcept -> u32 { + u32 eax_reg, unused = 0; + cpu::cpuid(eax_reg, unused, unused, unused, cpu::leaf::hypervisor); - const u32 eax = static_cast(out[0]); - - return eax; + // truncation is intentional + return eax_reg & 0xFF; }; hyperx_state state = HYPERV_UNKNOWN; @@ -1798,7 +1778,7 @@ struct VM { // to search in our databases, we want to precompute hashes at compile time for C++11 and later // so we need to match the hardware _mm_crc32_u8, it is based on CRC32-C (Castagnoli) polynomial - struct ConstexprHash { + struct constexpr_hash { // it does 8 rounds of CRC32-C bit reflection recursively static constexpr u32 crc32_bits(u32 crc, int bits) { return (bits == 0) ? crc : @@ -1820,17 +1800,17 @@ struct VM { struct thread_entry { u32 hash; u32 threads; - constexpr thread_entry(const char* m, u32 t) : hash(ConstexprHash::get(m)), threads(t) {} + constexpr thread_entry(const char* m, u32 t) : hash(constexpr_hash::get(m)), threads(t) {} }; - enum class CpuType { + enum class cpu_type { INTEL_I, INTEL_XEON, AMD }; // 4 arguments to stay compliant with x64 __fastcall (just in case) - [[nodiscard]] static bool verify_thread_count(const thread_entry* db, size_t db_size, size_t max_model_len, CpuType type) { + [[nodiscard]] static bool verify_thread_count(const thread_entry* db, size_t db_size, size_t max_model_len, cpu_type type) { // to save a few cycles struct hasher { static u32 crc32_sw(u32 crc, char data) { @@ -1865,7 +1845,7 @@ struct VM { std::string model_string; const char* debug_tag = ""; - if (type == CpuType::AMD) { + if (type == cpu_type::AMD) { if (!cpu::is_amd()) { return false; } @@ -1883,7 +1863,7 @@ struct VM { return false; } - if (type == CpuType::INTEL_I) { + if (type == cpu_type::INTEL_I) { if (!model.is_i_series) { return false; } @@ -1947,7 +1927,7 @@ struct VM { */ // convert to lowercase on-the-fly to match compile-time keys - if (type == CpuType::AMD && (k >= 'A' && k <= 'Z')) k += 32; + if (type == cpu_type::AMD && (k >= 'A' && k <= 'Z')) k += 32; // since this technique is cross-platform, we cannot use a standard C++ try-catch block to catch a missing CPU instruction // we could use preprocessor directives and add an exception handler (VEH/SEH or SIGHANDLER) but nah @@ -1964,7 +1944,7 @@ struct VM { if (!next_is_alnum) { // Check specific Z1 Extreme token // Hash for "extreme" (CRC32-C) is 0x3D09D5B4 - if (type == CpuType::AMD && current_hash == 0x3D09D5B4) { z_series_threads = 16; } + if (type == cpu_type::AMD && current_hash == 0x3D09D5B4) { z_series_threads = 16; } // since it's a contiguous block of integers in .rodata/.rdata, this is extremely fast for (size_t idx = 0; idx < db_size; ++idx) { @@ -1985,7 +1965,7 @@ struct VM { } // Z1 Extreme fix - if (type == CpuType::AMD && z_series_threads != 0 && expected_threads == 12) { + if (type == cpu_type::AMD && z_series_threads != 0 && expected_threads == 12) { expected_threads = z_series_threads; } @@ -2022,22 +2002,20 @@ struct VM { } } - auto valid_range = [&](size_t offset, size_t sz) -> bool { - if (sz == 0) return false; - if (module_size == 0) return false; - if (offset >= module_size) return false; - if (sz > module_size) return false; - if (offset > module_size - sz) return false; - return true; + auto valid_range = [&](size_t offset, size_t sz) noexcept -> bool { + return (sz > 0) && (offset < module_size) && (sz <= module_size - offset); }; - auto safe_cstr_from_rva = [&](DWORD rva) -> const char* { + auto safe_cstr_from_rva = [&](DWORD rva) noexcept -> const char* { if (!valid_range(static_cast(rva), 1)) return nullptr; - const char* p = reinterpret_cast(base + rva); + + const char* start = reinterpret_cast(base + rva); const size_t remaining = module_size - static_cast(rva); - for (size_t i = 0; i < remaining; ++i) { - if (p[i] == '\0') return p; + + if (std::memchr(start, '\0', remaining)) { + return start; } + return nullptr; }; @@ -2364,7 +2342,7 @@ struct VM { /** - * @brief Check if hypervisor feature bit in CPUID eax bit 31 is enabled (always false for physical CPUs) + * @brief Check if hypervisor feature bit in CPUID ECX bit 31 is enabled (always false for physical CPUs) * @category x86 * @implements VM::HYPERVISOR_BIT */ @@ -2466,31 +2444,24 @@ struct VM { u32 unused, eax = 0; cpu::cpuid(eax, unused, unused, unused, 1); - auto is_k7 = [](const u32 eax) -> bool { - const u32 family = (eax >> 8) & 0xF; - const u32 model = (eax >> 4) & 0xF; - const u32 extended_family = (eax >> 20) & 0xFF; - - if (family == 6 && extended_family == 0) { - if (model == 1 || model == 2 || model == 3 || model == 4) { - return true; - } + auto is_k7 = [](const u32 eax) noexcept -> bool { + if ((eax & 0x0FF00F00) != 0x00000600) { + return false; } - return false; - }; + const u32 model = (eax >> 4) & 0xF; - auto is_k8 = [](const u32 eax) -> bool { - const u32 family = (eax >> 8) & 0xF; - const u32 extended_family = (eax >> 20) & 0xFF; + return (model - 1) < 4; + }; - if (family == 0xF) { - if (extended_family == 0x00 || extended_family == 0x01) { - return true; - } + auto is_k8 = [](const u32 eax) noexcept -> bool { + if (((eax >> 8) & 0xF) != 0xF) { + return false; } - return false; + const u32 extended_family = (eax >> 20) & 0xFF; + + return extended_family <= 1; }; if (!(is_k7(eax) || is_k8(eax))) { @@ -3481,7 +3452,7 @@ struct VM { static constexpr size_t MAX_INTEL_MODEL_LEN = 16; - return util::verify_thread_count(thread_database, sizeof(thread_database) / sizeof(util::thread_entry), MAX_INTEL_MODEL_LEN, util::CpuType::INTEL_I); + return util::verify_thread_count(thread_database, sizeof(thread_database) / sizeof(util::thread_entry), MAX_INTEL_MODEL_LEN, util::cpu_type::INTEL_I); #endif } @@ -3637,7 +3608,7 @@ struct VM { static constexpr size_t MAX_XEON_MODEL_LEN = 16; - return util::verify_thread_count(thread_database, sizeof(thread_database) / sizeof(util::thread_entry), MAX_XEON_MODEL_LEN, util::CpuType::INTEL_XEON); + return util::verify_thread_count(thread_database, sizeof(thread_database) / sizeof(util::thread_entry), MAX_XEON_MODEL_LEN, util::cpu_type::INTEL_XEON); #endif } @@ -4167,7 +4138,7 @@ struct VM { static constexpr size_t MAX_AMD_MODEL_LEN = 24; // "threadripper" is long - return util::verify_thread_count(thread_database, sizeof(thread_database) / sizeof(util::thread_entry), MAX_AMD_MODEL_LEN, util::CpuType::INTEL_XEON); + return util::verify_thread_count(thread_database, sizeof(thread_database) / sizeof(util::thread_entry), MAX_AMD_MODEL_LEN, util::cpu_type::INTEL_XEON); #endif } @@ -4340,7 +4311,8 @@ struct VM { return true; } } - auto cpuid = [&]() -> u64 { + + auto cpuid = [&]() noexcept -> u64 { const u64 t1 = __rdtsc(); u32 a, b, c, d; @@ -4353,7 +4325,7 @@ struct VM { constexpr u16 N = 1000; - auto sample_avg = [&]() -> u64 { + auto sample_avg = [&]() noexcept -> u64 { u64 sum = 0; for (u16 i = 0; i < N; ++i) { sum += cpuid(); @@ -4424,13 +4396,13 @@ struct VM { const ULONG64 count_second = 200000000ULL; static thread_local volatile u64 g_sink = 0; // so that it doesnt need to be captured by the lambda - auto rd_lambda = []() -> u64 { + auto rd_lambda = []() noexcept -> u64 { u64 v = __rdtsc(); g_sink ^= v; return v; }; - auto xor_lambda = []() -> u64 { + auto xor_lambda = []() noexcept -> u64 { volatile u64 a = 0xDEADBEEFDEADBEEFull; // can be replaced by NOPs volatile u64 b = 0x1234567890ABCDEFull; u64 v = a ^ b; @@ -5242,13 +5214,13 @@ struct VM { { "google compute engine", brands::GCE } }}; - auto to_lower = [](std::string &str) { + auto to_lower = [](std::string& str) noexcept { for (auto& c : str) { - if (c == ' ') { - continue; + // std::tolower is surprisingly slow because it handles locales + // for ASCII strings simple bitwise math is 10x faster + if (c >= 'A' && c <= 'Z') { + c |= 0x20; } - - c = static_cast(tolower(c)); } }; @@ -5747,7 +5719,7 @@ struct VM { static_assert(targets.size() == brands_map.size(), "targets and brands_map must be the same length"); auto scan_table = [&](const BYTE* buf, const size_t len) noexcept -> bool { - // faster than std::search because of a manual byte-by-byte loop, could be optimized further with Boyer-Moore-Horspool implementations for large firmware tables like DSDT + // faster than std::search because of a manual byte-by-byte loop, could be optimized further with Boyer-Moore-Horspool for large tables like DSDT auto find_pattern = [&](const char* pat, size_t patlen) noexcept -> bool { if (patlen == 0 || patlen > len) return false; const u8 first = static_cast(pat[0]); @@ -5893,16 +5865,20 @@ struct VM { } // helper to fetch one table into a malloc'd buffer - auto fetch = [&](DWORD provider, DWORD tableID, BYTE*& outBuf, size_t& outLen) -> bool { - const UINT sz = GetSystemFirmwareTable(provider, tableID, nullptr, 0); + auto fetch = [&](DWORD provider, DWORD tableID, BYTE*& outBuf, size_t& outLen) noexcept -> bool { + const DWORD sz = GetSystemFirmwareTable(provider, tableID, nullptr, 0); if (sz == 0) return false; - outBuf = reinterpret_cast(malloc(sz)); - if (!outBuf) return false; - if (GetSystemFirmwareTable(provider, tableID, outBuf, sz) != sz) { - free(outBuf); + + BYTE* buf = static_cast(malloc(sz)); + if (!buf) return false; + + if (GetSystemFirmwareTable(provider, tableID, buf, sz) != sz) { + free(buf); return false; } - outLen = sz; + + outBuf = buf; + outLen = static_cast(sz); return true; }; @@ -6094,7 +6070,6 @@ struct VM { #endif } - /** * @brief Check for PCI vendor and device IDs that are VM-specific * @link https://www.pcilookup.com/?ven=&dev=&action=submit @@ -6146,164 +6121,158 @@ struct VM { L"SYSTEM\\CurrentControlSet\\Enum\\HDAUDIO" }; - enum RootType { RT_PCI, RT_USB, RT_HDAUDIO }; + enum root_type { RT_PCI, RT_USB, RT_HDAUDIO }; constexpr DWORD MAX_MULTI_SZ = 64 * 1024; - auto hexVal = [](wchar_t c) -> int { + auto hex_val = [](wchar_t c) noexcept -> int { if (c >= L'0' && c <= L'9') return c - L'0'; - c = towupper(c); - if (c >= L'A' && c <= L'F') return 10 + (c - L'A'); + + const wchar_t lower = static_cast((static_cast(c) | 0x20)); + if (lower >= L'a' && lower <= L'f') return lower - L'a' + 10; + return -1; }; - // parse up to maxDigits from ptr; stop also if stopLen supplied (SIZE_MAX = no limit) - auto parseHexInplace = [&](const wchar_t* ptr, size_t maxDigits, size_t stopLen, unsigned long& out, size_t& consumed) -> bool { + auto parse_hex = [&](const wchar_t* ptr, size_t maxDigits, size_t stopLen, unsigned long& out, size_t& consumed) noexcept -> bool { out = 0; consumed = 0; - while (consumed < maxDigits && (stopLen == SIZE_MAX || consumed < stopLen)) { - int v = hexVal(ptr[consumed]); + + const size_t limit = (stopLen < maxDigits) ? stopLen : maxDigits; + + for (; consumed < limit; ++consumed) { + const int v = hex_val(ptr[consumed]); if (v < 0) break; + + // caller must ensure maxDigits doesn't exceed 8, because on Windows unsigned long is 32-bit out = (out << 4) | static_cast(v); - ++consumed; } + return consumed > 0; }; std::unordered_set seen; - // Lambda #1: Process the hardware ID on an instance key, - // extract every (VID, DID) pair, and push into devices - auto processHardwareID = [&](HKEY hInst, RootType rootType) { - DWORD type = 0, cbData = 0; - LONG rv = RegGetValueW( - hInst, - nullptr, - L"HardwareID", - RRF_RT_REG_MULTI_SZ, - &type, - nullptr, - &cbData - ); - if (rv != ERROR_SUCCESS || type != REG_MULTI_SZ || cbData <= sizeof(wchar_t)) { - return; + auto add_device = [&](u16 vid, u32 did) noexcept { + const unsigned long long key = (static_cast(vid) << 32) | static_cast(did); + if (seen.insert(key).second) { + devices.push_back({ vid, did }); } + }; - if (cbData > MAX_MULTI_SZ) { - debug("PCI_DEVICES: HardwareID size too large: ", cbData); - return; + auto scan_text_ids = [&](const wchar_t* text) noexcept { + if (!text) return; + + // USB: VID_ and then PID_ + const wchar_t* p = text; + while ((p = wcsstr(p, L"VID_"))) { + const wchar_t* v = p; + p += 4; + const wchar_t* d = wcsstr(v + 4, L"PID_"); + if (d && (d - v) < 64) { + unsigned long parsed_v = 0, parsed_d = 0; + size_t c_v = 0, c_d = 0; + if (parse_hex(v + 4, 4, SIZE_MAX, parsed_v, c_v) && + parse_hex(d + 4, 8, SIZE_MAX, parsed_d, c_d)) { + add_device(static_cast(parsed_v & 0xFFFFu), static_cast(parsed_d)); + } + } + } + + // PCI or HDAUDIO = VEN_ and then DEV_ after it + p = text; + while ((p = wcsstr(p, L"VEN_"))) { + const wchar_t* v = p; + p += 4; + const wchar_t* d = wcsstr(v + 4, L"DEV_"); + if (d && (d - v) < 64) { + unsigned long parsed_v = 0; + size_t c_v = 0; + if (parse_hex(v + 4, 4, SIZE_MAX, parsed_v, c_v)) { + const wchar_t* dev_start = const_cast(d + 4); + const wchar_t* amp_after_dev = wcschr(dev_start, L'&'); + const size_t dev_len = amp_after_dev ? static_cast(amp_after_dev - dev_start) : wcslen(dev_start); + + // for HDAUDIO expect 4 digits and for PCI allow up to 8 + if (dev_len > 0 && dev_len <= 8) { + unsigned long parsed_d = 0; + size_t c_d = 0; + // parse exactly devLen digits (fail if any char is non-hex) + if (parse_hex(dev_start, 8, dev_len, parsed_d, c_d) && c_d == dev_len) { + add_device(static_cast(parsed_v & 0xFFFFu), static_cast(parsed_d)); + } + } + } + } } + }; + + // process the hardware ID on an instance key + auto process_hardware_id_reg = [&](HKEY h_inst) noexcept { + // most HardwareIDs fit within 512 bytes + static thread_local std::vector buf; + if (buf.empty()) buf.resize(512); + + DWORD type = 0; + DWORD cb_data = static_cast(buf.size() * sizeof(wchar_t)); - // allocate a buffer large enough to hold the entire MULTI_SZ - std::vector buf((cbData / sizeof(wchar_t)) + 1); - buf.back() = L'\0'; - rv = RegGetValueW( - hInst, + LONG rv = RegGetValueW( + h_inst, nullptr, L"HardwareID", RRF_RT_REG_MULTI_SZ, - nullptr, + &type, buf.data(), - &cbData + &cb_data ); - if (rv != ERROR_SUCCESS) { + + if (rv == ERROR_MORE_DATA) { + if (cb_data > MAX_MULTI_SZ) { + return; + } + + // allocate a buffer large enough to hold the entire MULTI_SZ + // (+1 for safety null terminator logic below) + buf.resize((cb_data / sizeof(wchar_t)) + 2); + + rv = RegGetValueW( + h_inst, + nullptr, + L"HardwareID", + RRF_RT_REG_MULTI_SZ, + &type, + buf.data(), + &cb_data + ); + } + + if (rv != ERROR_SUCCESS || type != REG_MULTI_SZ || cb_data <= sizeof(wchar_t)) { return; } // guarantee terminating NUL - size_t wcharCount = cbData / sizeof(wchar_t); - if (wcharCount < buf.size()) buf[wcharCount] = L'\0'; + // RegGetValueW with RRF_RT_REG_MULTI_SZ usually handles this but for safety + const size_t wchar_count = cb_data / sizeof(wchar_t); + if (wchar_count < buf.size()) buf[wchar_count] = L'\0'; else buf.back() = L'\0'; for (wchar_t* p = buf.data(); *p; p += wcslen(p) + 1) { - wchar_t* s = p; - wchar_t* v = nullptr; - wchar_t* d = nullptr; - u16 vid = 0; - u32 did = 0; - bool ok = false; - - if (rootType == RT_USB) { - // USB: VID_ and then PID_ - v = wcsstr(s, L"VID_"); - if (v) d = wcsstr(v + 4, L"PID_"); - if (v && d) { - unsigned long parsedV = 0, parsedD = 0; - size_t cV = 0, cD = 0; - // VID_ usually 4 hex digits, PID_ usually 4 - if (parseHexInplace(v + 4, 4, SIZE_MAX, parsedV, cV) && - parseHexInplace(d + 4, 8, SIZE_MAX, parsedD, cD)) { - vid = static_cast(parsedV & 0xFFFFu); - did = static_cast(parsedD); - ok = true; - } - } - } - else { - // PCI or HDAUDIO = VEN_ and then DEV_ after it - v = wcsstr(s, L"VEN_"); - if (v) d = wcsstr(v + 4, L"DEV_"); - if (v && d) { - unsigned long parsedV = 0; - size_t cV = 0; - if (!parseHexInplace(v + 4, 4, SIZE_MAX, parsedV, cV)) { - continue; - } - vid = static_cast(parsedV & 0xFFFFu); - - wchar_t* devStart = d + 4; - wchar_t* ampAfterDev = wcschr(devStart, L'&'); - size_t devLen = ampAfterDev ? static_cast(ampAfterDev - devStart) : wcslen(devStart); - - // For HDAUDIO expect 4 digits and for PCI allow up to 8 - size_t maxDigits = (rootType == RT_HDAUDIO) ? 4 : 8; - if (devLen == 0 || devLen > maxDigits) { - // If the token is longer than maxDigits, we cap parsing to maxDigits but - // require that the parsed digit count equals devLen - if (devLen > maxDigits) continue; - } - - unsigned long parsedD = 0; - size_t cD = 0; - // parse exactly devLen digits (fail if any char is non-hex) - if (!parseHexInplace(devStart, maxDigits, devLen, parsedD, cD)) { - continue; - } - // require we consumed all characters in device token (like std::stoul on the substring) - if (cD != devLen) continue; - - // overflow checks - if (rootType == RT_HDAUDIO) { - if (parsedD > 0xFFFF) continue; - did = static_cast(parsedD & 0xFFFFu); - } - else { - // PCI device id may be up to 32-bit - did = static_cast(parsedD); - } - ok = true; - } - } - - if (ok) { - unsigned long long key = (static_cast(vid) << 32) | static_cast(did); - if (seen.insert(key).second) { - devices.push_back({ vid, did }); - } - } + scan_text_ids(p); } }; - // Lambda #2: all instance subkeys under a given device key, - // and for each instance, open it and call processHardwareID() - auto enumInstances = [&](HKEY hDev, RootType rootType) { + // all instance subkeys under a given device key + auto enum_instances = [&](HKEY h_dev) noexcept { + wchar_t inst_name[256]; + for (DWORD j = 0;; ++j) { - wchar_t instName[256]{}; - DWORD cbInst = _countof(instName); - LONG st2 = RegEnumKeyExW( - hDev, + // reset size for each iteration as RegEnumKeyExW modifies it + DWORD cb_inst = _countof(inst_name); + + const LONG st2 = RegEnumKeyExW( + h_dev, j, - instName, - &cbInst, + inst_name, + &cb_inst, nullptr, nullptr, nullptr, @@ -6312,25 +6281,26 @@ struct VM { if (st2 == ERROR_NO_MORE_ITEMS) break; if (st2 != ERROR_SUCCESS) continue; - HKEY hInst = nullptr; - if (RegOpenKeyExW(hDev, instName, 0, KEY_READ, &hInst) != ERROR_SUCCESS) continue; + HKEY h_inst = nullptr; + if (RegOpenKeyExW(h_dev, inst_name, 0, KEY_READ, &h_inst) != ERROR_SUCCESS) continue; - processHardwareID(hInst, rootType); - RegCloseKey(hInst); + process_hardware_id_reg(h_inst); + RegCloseKey(h_inst); } }; - // Lambda #3: all device subkeys under a given root key, - // open each device key, and call enumInstances() - auto enumDevices = [&](HKEY hRoot, RootType rootType) { + // all device subkeys under a given root key + auto enum_devices = [&](HKEY h_root) noexcept { + wchar_t device_name[256]; + for (DWORD i = 0;; ++i) { - wchar_t deviceName[256]; - DWORD cbName = _countof(deviceName); - LONG status = RegEnumKeyExW( - hRoot, + DWORD cb_name = _countof(device_name); + + const LONG status = RegEnumKeyExW( + h_root, i, - deviceName, - &cbName, + device_name, + &cb_name, nullptr, nullptr, nullptr, @@ -6339,15 +6309,15 @@ struct VM { if (status == ERROR_NO_MORE_ITEMS) break; if (status != ERROR_SUCCESS) continue; - HKEY hDev = nullptr; - if (RegOpenKeyExW(hRoot, deviceName, 0, KEY_READ, &hDev) != ERROR_SUCCESS) continue; + HKEY h_dev = nullptr; + if (RegOpenKeyExW(h_root, device_name, 0, KEY_READ, &h_dev) != ERROR_SUCCESS) continue; - enumInstances(hDev, rootType); - RegCloseKey(hDev); + enum_instances(h_dev); + RegCloseKey(h_dev); } }; - // for each rootPath we open the root key once, compute its RootType, then call enumDevices() + // for each rootPath we open the root key once for (size_t rootIdx = 0; rootIdx < _countof(kRoots); ++rootIdx) { const wchar_t* rootPath = kRoots[rootIdx]; HKEY hRoot = nullptr; @@ -6361,20 +6331,91 @@ struct VM { continue; } - RootType rootType; - if (wcscmp(rootPath, L"SYSTEM\\CurrentControlSet\\Enum\\USB") == 0) { - rootType = RT_USB; - } - else if (wcscmp(rootPath, L"SYSTEM\\CurrentControlSet\\Enum\\HDAUDIO") == 0) { - rootType = RT_HDAUDIO; - } - else { - rootType = RT_PCI; - } - - enumDevices(hRoot, rootType); + enum_devices(hRoot); RegCloseKey(hRoot); } + + auto scan_evt_logs = [&]() noexcept { + static constexpr const wchar_t* q = L"" + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L""; + + const EVT_HANDLE hSub = EvtQuery(nullptr, nullptr, q, EvtQueryReverseDirection | EvtQueryTolerateQueryErrors); + if (!hSub) return; + + EVT_HANDLE hEvents[1] = { nullptr }; + DWORD returned = 0; + + static thread_local std::vector buf; + if (buf.empty()) buf.resize(8192); + + while (true) { + returned = 0; + hEvents[0] = nullptr; + + if (!EvtNext(hSub, 1, hEvents, 2000, 0, &returned)) { + const DWORD err = GetLastError(); + if (err == ERROR_NO_MORE_ITEMS) break; + if (err == ERROR_TIMEOUT) continue; + break; + } + + if (returned == 0 || hEvents[0] == nullptr) { + if (hEvents[0]) { EvtClose(hEvents[0]); hEvents[0] = nullptr; } + continue; + } + + DWORD bufUsed = 0; + DWORD bufProps = 0; + + if (!EvtRender(nullptr, hEvents[0], EvtRenderEventXml, + static_cast(buf.size() * sizeof(wchar_t)), + buf.data(), &bufUsed, &bufProps)) { + const DWORD err = GetLastError(); + if (err == ERROR_INSUFFICIENT_BUFFER) { + size_t neededChars = (bufUsed + sizeof(wchar_t) - 1) / sizeof(wchar_t); + buf.resize(neededChars + 1); + + if (!EvtRender(nullptr, hEvents[0], EvtRenderEventXml, + static_cast(buf.size() * sizeof(wchar_t)), + buf.data(), &bufUsed, &bufProps)) { + if (hEvents[0]) { EvtClose(hEvents[0]); hEvents[0] = nullptr; } + continue; + } + } + else { + if (hEvents[0]) { EvtClose(hEvents[0]); hEvents[0] = nullptr; } + continue; + } + } + + const size_t charCount = bufUsed / sizeof(wchar_t); + if (charCount >= buf.size()) buf.resize(charCount + 1); + buf[charCount] = L'\0'; + + scan_text_ids(buf.data()); + + if (hEvents[0]) { EvtClose(hEvents[0]); hEvents[0] = nullptr; } + } + + EvtClose(hSub); + }; + + scan_evt_logs(); #endif for (auto& d : devices) { @@ -7001,7 +7042,7 @@ struct VM { bool rc = false; #if (x86_32 && !CLANG) - auto IsInsideVPC_exceptionFilter = [](PEXCEPTION_POINTERS ep) -> DWORD { + auto IsInsideVPC_exceptionFilter = [](PEXCEPTION_POINTERS ep) noexcept -> DWORD { PCONTEXT ctx = ep->ContextRecord; ctx->Ebx = static_cast(-1); // Not running VPC @@ -7304,38 +7345,45 @@ struct VM { return false; } - auto try_mutex_name = [&](const wchar_t* baseName) -> bool { - std::wstring full = L"\\BaseNamedObjects\\"; - full += baseName; + auto try_mutex_name = [&](const wchar_t* base_name) noexcept -> bool { + constexpr wchar_t prefix[] = L"\\BaseNamedObjects\\"; + constexpr size_t prefix_len = (sizeof(prefix) / sizeof(wchar_t)) - 1; - UNICODE_STRING uName; - pRtlInitUnicodeString(&uName, full.c_str()); + wchar_t full_path[260]; - OBJECT_ATTRIBUTES objAttr; - ZeroMemory(&objAttr, sizeof(objAttr)); - objAttr.Length = sizeof(objAttr); - objAttr.ObjectName = &uName; - objAttr.Attributes = OBJ_CASE_INSENSITIVE; + // memcpy as it is faster than wcscpy/wcscat + memcpy(full_path, prefix, sizeof(prefix)); - HANDLE hMutant = nullptr; - NTSTATUS st = pNtOpenMutant(&hMutant, MUTANT_QUERY_STATE, &objAttr); - if (NT_SUCCESS(st)) { - if (hMutant) pNtClose(hMutant); - return true; + const size_t name_len = wcslen(base_name); + if (prefix_len + name_len < 260) { + memcpy(full_path + prefix_len, base_name, (name_len + 1) * sizeof(wchar_t)); + } + else { + // should not happen for standard VM artifacts + full_path[0] = L'\0'; } - // some contexts expose it without the prefix - pRtlInitUnicodeString(&uName, baseName); - ZeroMemory(&objAttr, sizeof(objAttr)); - objAttr.Length = sizeof(objAttr); - objAttr.ObjectName = &uName; - objAttr.Attributes = OBJ_CASE_INSENSITIVE; + const wchar_t* attempts[] = { full_path, base_name }; - hMutant = nullptr; - st = pNtOpenMutant(&hMutant, MUTANT_QUERY_STATE, &objAttr); - if (NT_SUCCESS(st)) { - if (hMutant) pNtClose(hMutant); - return true; + for (const wchar_t* path : attempts) { + if (*path == L'\0') continue; + + UNICODE_STRING u_name; + pRtlInitUnicodeString(&u_name, path); + + OBJECT_ATTRIBUTES obj_attr; + memset(&obj_attr, 0, sizeof(obj_attr)); + obj_attr.Length = sizeof(obj_attr); + obj_attr.ObjectName = &u_name; + obj_attr.Attributes = OBJ_CASE_INSENSITIVE; + + HANDLE h_mutant = nullptr; + const NTSTATUS st = pNtOpenMutant(&h_mutant, MUTANT_QUERY_STATE, &obj_attr); + + if (NT_SUCCESS(st)) { + if (h_mutant) pNtClose(h_mutant); + return true; + } } return false; @@ -7638,41 +7686,41 @@ struct VM { constexpr SIZE_T MAX_DESCRIPTOR_SIZE = 64 * 1024; u8 successfulOpens = 0; - auto is_qemu_serial = [](const char* str) -> bool { - return _strnicmp(str, "QM0000", 6) == 0; + auto is_qemu_serial = [](const char* str) noexcept -> bool { + if ((str[0] & 0xDF) != 'Q') return false; + if ((str[1] & 0xDF) != 'M') return false; + + // we check byte-by-byte to be safe regarding alignment, + // though a 32-bit integer check (0x30303030) could be used if alignment is guaranteed + // we also essentially check for null termination safety here because '\0' != '0'. + return str[2] == '0' && str[3] == '0' && str[4] == '0' && str[5] == '0'; }; - auto is_vbox_serial = [](const char* str, size_t len) -> bool { - if (len != 19) { - return false; - } + auto is_vbox_serial = [](const char* str, size_t len) noexcept -> bool { + // format: VB12345678-12345678 (19 chars) + if (len != 19) return false; - auto toupper_char = [](char c) -> char { - return (c >= 'a' && c <= 'z') ? static_cast(c - 'a' + 'A') : c; - }; + if ((str[0] & 0xDF) != 'V' || (str[1] & 0xDF) != 'B') return false; - if (toupper_char(str[0]) != 'V' || toupper_char(str[1]) != 'B' || str[10] != '-') { - return false; - } + if (str[10] != '-') return false; - auto is_hex = [&](char c) { - char upper_c = toupper_char(c); - return (upper_c >= '0' && upper_c <= '9') || (upper_c >= 'A' && upper_c <= 'F'); + auto is_hex = [](char c) noexcept -> bool { + const char lower = c | 0x20; + return (c >= '0' && c <= '9') || (lower >= 'a' && lower <= 'f'); }; - static constexpr std::array hex_positions = { { - 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18 - } }; + for (size_t i = 2; i < 10; ++i) { + if (!is_hex(str[i])) return false; + } - for (u8 idx : hex_positions) { - if (!is_hex(str[idx])) { - return false; - } + for (size_t i = 11; i < 19; ++i) { + if (!is_hex(str[i])) return false; } + return true; }; - auto __strnlen = [](const char* s, size_t max) -> size_t { + auto strnlen = [](const char* s, size_t max) noexcept -> size_t { const void* p = memchr(s, 0, max); if (!p) return max; return static_cast(static_cast(p) - s); @@ -7804,7 +7852,7 @@ struct VM { if (serialOffset > 0 && serialOffset < descriptor->Size) { const char* serial = reinterpret_cast(descriptor) + serialOffset; const size_t maxAvail = static_cast(descriptor->Size) - static_cast(serialOffset); - const size_t serialLen = __strnlen(serial, maxAvail); + const size_t serialLen = strnlen(serial, maxAvail); debug("DISK_SERIAL: ", serial); @@ -7997,33 +8045,40 @@ struct VM { return false; } - auto try_open_native = [&](const wchar_t* nativePath) -> HANDLE { - UNICODE_STRING uPath; - pRtlInitUnicodeString(&uPath, nativePath); + auto try_open_mutex = [&](const wchar_t* native_path) noexcept -> HANDLE { + UNICODE_STRING u_path{}; + u_path.Buffer = const_cast(native_path); - OBJECT_ATTRIBUTES objAttr; - RtlZeroMemory(&objAttr, sizeof(objAttr)); - objAttr.Length = sizeof(objAttr); - objAttr.ObjectName = &uPath; - objAttr.Attributes = OBJ_CASE_INSENSITIVE; + const size_t len_bytes = wcslen(native_path) * sizeof(wchar_t); + u_path.Length = static_cast(len_bytes); + u_path.MaximumLength = static_cast(len_bytes + sizeof(wchar_t)); + + OBJECT_ATTRIBUTES obj_attr = { + sizeof(OBJECT_ATTRIBUTES), + nullptr, + &u_path, + OBJ_CASE_INSENSITIVE, + nullptr, + nullptr + }; IO_STATUS_BLOCK iosb; - HANDLE hFile = nullptr; + HANDLE h_file = nullptr; - // minimal read access to emulate CreateFile(...GENERIC_READ...) - constexpr ACCESS_MASK desiredAccess = FILE_READ_DATA | FILE_READ_ATTRIBUTES; - constexpr ULONG shareAccess = FILE_SHARE_READ; - constexpr ULONG openOptions = FILE_OPEN | FILE_SYNCHRONOUS_IO_NONALERT; + constexpr ACCESS_MASK desired_access = FILE_READ_DATA | FILE_READ_ATTRIBUTES | SYNCHRONIZE; + constexpr ULONG share_access = FILE_SHARE_READ; + constexpr ULONG open_options = FILE_OPEN | FILE_SYNCHRONOUS_IO_NONALERT; - const NTSTATUS st = pNtOpenFile(&hFile, desiredAccess, &objAttr, &iosb, shareAccess, openOptions); - if (NT_SUCCESS(st) && hFile) { - return hFile; + const NTSTATUS st = pNtOpenFile(&h_file, desired_access, &obj_attr, &iosb, share_access, open_options); + + if (NT_SUCCESS(st)) { + return h_file; } return INVALID_HANDLE_VALUE; }; // \\.\Name -> \??\Name, \\.\pipe\name -> \??\pipe\name - const wchar_t* paths[] = { + constexpr const wchar_t* paths[] = { L"\\??\\VBoxMiniRdrDN", // \\.\VBoxMiniRdrDN L"\\??\\pipe\\VBoxMiniRdDN",// \\.\pipe\VBoxMiniRdDN L"\\??\\VBoxTrayIPC", // \\.\VBoxTrayIPC @@ -8034,7 +8089,7 @@ struct VM { HANDLE handles[ARRAYSIZE(paths)]{}; for (size_t i = 0; i < ARRAYSIZE(paths); ++i) { - handles[i] = try_open_native(paths[i]); + handles[i] = try_open_mutex(paths[i]); } bool vbox = false; @@ -8601,24 +8656,27 @@ struct VM { return false; } - auto vetExceptions = [&](u32 code, EXCEPTION_POINTERS* info) -> u8 { + auto vetExceptions = [&](u32 code, EXCEPTION_POINTERS* info) noexcept -> u8 { // if not single-step, hypervisor likely swatted our trap if (code != static_cast(0x80000004L)) { hypervisorCaught = true; return EXCEPTION_CONTINUE_SEARCH; } + // count breakpoint hits hitCount++; + // validate exception address matches our breakpoint location if (reinterpret_cast(info->ExceptionRecord->ExceptionAddress) != baseAddr + 11) { hypervisorCaught = true; return EXCEPTION_EXECUTE_HANDLER; } + // check if Trap Flag and DR0 contributed + constexpr u64 required_bits = (1ULL << 14) | 1ULL; const u64 status = info->ContextRecord->Dr6; - const bool fromTrapFlag = (status & (1ULL << 14)) != 0; - const bool fromDr0 = (status & 1ULL) != 0; - if (!fromTrapFlag || !fromDr0) { + + if ((status & required_bits) != required_bits) { if (util::hyper_x() != HYPERV_ARTIFACT_VM) hypervisorCaught = true; // detects type 1 Hyper-V too, which we consider legitimate } @@ -8852,7 +8910,8 @@ struct VM { pNtFlushInstructionCache(hCurrentProcess, stub, regionSize); - auto tryPass = [&]() -> bool { + auto tryPass = [&]() noexcept -> bool { + // store forwarding in modern CPUs vmcallInfo.structsize = static_cast(sizeof(VMCallInfo)); vmcallInfo.level2pass = PW2; vmcallInfo.command = 0; @@ -9146,14 +9205,19 @@ struct VM { * @warning Permissions required * @implements VM::NVRAM */ - [[nodiscard]] static bool nvram_vars() { + static bool nvram() { struct VARIABLE_NAME { ULONG NextEntryOffset; GUID VendorGuid; WCHAR Name[1]; }; using PVARIABLE_NAME = VARIABLE_NAME*; using NtEnumerateSystemEnvironmentValuesEx_t = NTSTATUS(__stdcall*)(ULONG, PVOID, PULONG); + // Secure Boot stuff bool found_dbDefault = false, found_dbxDefault = false, found_KEKDefault = false, found_PKDefault = false; + /* + MemoryOverwriteRequestControlLock is part of a state machine defined in the TCG Platform Reset Attack Mitigation Specification + the SMM driver expects to initialize and manage this variable itself during the DXE phase of booting + Secure Boot, TPM and SMM must be enabled to set it + */ bool found_MORCL = false; - bool pk_checked = false; if (!util::is_admin()) return false; @@ -9163,7 +9227,7 @@ struct VM { LUID luid{}; bool priv_enabled = false; - auto cleanup = [&]() { + auto cleanup = [&]() noexcept { if (priv_enabled && hToken) { TOKEN_PRIVILEGES tpDisable{}; tpDisable.PrivilegeCount = 1; @@ -9171,218 +9235,297 @@ struct VM { tpDisable.Privileges[0].Attributes = 0; AdjustTokenPrivileges(hToken, FALSE, &tpDisable, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr); } - if (hToken) { - CloseHandle(hToken); - hToken = nullptr; - } + if (hToken) { CloseHandle(hToken); hToken = nullptr; } }; if (!LookupPrivilegeValue(nullptr, SE_SYSTEM_ENVIRONMENT_NAME, &luid)) { cleanup(); return false; } - - TOKEN_PRIVILEGES tpEnable{}; - tpEnable.PrivilegeCount = 1; - tpEnable.Privileges[0].Luid = luid; - tpEnable.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + TOKEN_PRIVILEGES tpEnable{}; tpEnable.PrivilegeCount = 1; tpEnable.Privileges[0].Luid = luid; tpEnable.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken, FALSE, &tpEnable, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr); if (GetLastError() != ERROR_SUCCESS) { cleanup(); return false; } priv_enabled = true; - bool hasFunction = false; - bool success = false; - std::vector resBuffer; - ULONG bufferLength = 0; const HMODULE ntdll = util::get_ntdll(); - if (ntdll) { - const char* names[] = { "NtEnumerateSystemEnvironmentValuesEx" }; - void* functions[1] = { nullptr }; - util::get_function_address(ntdll, names, functions, 1); - const auto NtEnum = reinterpret_cast(functions[0]); - if (NtEnum) { - hasFunction = true; - NtEnum(1, nullptr, &bufferLength); - if (bufferLength != 0) { - try { resBuffer.resize(bufferLength); } - catch (...) { resBuffer.clear(); bufferLength = 0; } - if (!resBuffer.empty()) { - const NTSTATUS status = NtEnum(1, resBuffer.data(), &bufferLength); - if (status == 0) { success = true; resBuffer.resize(bufferLength); } - else resBuffer.clear(); - } - } - } - } - - if (!hasFunction) { - debug("NVRAM: NtEnumerateSystemEnvironmentValuesEx could not be resolved"); - cleanup(); - return false; - } - if (!success) { - debug("NVRAM: System is not UEFI"); - cleanup(); - return false; - } + if (!ntdll) { cleanup(); return false; } + + const char* names[] = { "NtEnumerateSystemEnvironmentValuesEx", "NtAllocateVirtualMemory", "NtFreeVirtualMemory" }; + void* funcs[sizeof(names) / sizeof(names[0])] = {}; + util::get_function_address(ntdll, names, funcs, sizeof(names) / sizeof(names[0])); + const auto pNtEnum = reinterpret_cast(funcs[0]); + typedef NTSTATUS(__stdcall* NtAllocateVirtualMemory_t)(HANDLE, PVOID*, ULONG_PTR, PSIZE_T, ULONG, ULONG); + typedef NTSTATUS(__stdcall* NtFreeVirtualMemory_t)(HANDLE, PVOID*, PSIZE_T, ULONG); + const auto pNtAllocateVirtualMemory = reinterpret_cast(funcs[1]); + const auto pNtFreeVirtualMemory = reinterpret_cast(funcs[2]); + if (!pNtEnum || !pNtAllocateVirtualMemory || !pNtFreeVirtualMemory) { cleanup(); return false; } - auto contains_redhat_ascii_ci = [](const BYTE* data, size_t len)->bool { - static const char pattern[] = "red hat secure boot"; - const size_t plen = sizeof(pattern) - 1; - if (len < plen) return false; - for (size_t i = 0; i <= len - plen; ++i) { + bool hasFunction = false, success = false; + PVOID enumBase = nullptr; + SIZE_T enumSize = 0; + ULONG bufferLength = 0; + // ask for size + if (pNtEnum) { + hasFunction = true; + pNtEnum(static_cast(1), nullptr, &bufferLength); + if (bufferLength != 0) { + enumSize = static_cast(bufferLength); + NTSTATUS st = pNtAllocateVirtualMemory(hCurrentProcess, &enumBase, 0, &enumSize, static_cast(MEM_COMMIT | MEM_RESERVE), static_cast(PAGE_READWRITE)); + if (st == 0 && enumBase) { + st = pNtEnum(static_cast(1), enumBase, &bufferLength); + if (st == 0) { success = true; } + else { SIZE_T zero = 0; pNtFreeVirtualMemory(hCurrentProcess, &enumBase, &zero, 0x8000); enumBase = nullptr; enumSize = 0; } + } + } + } + + if (!hasFunction) { debug("NVRAM: NtEnumerateSystemEnvironmentValuesEx could not be resolved"); cleanup(); return true; } + if (!success) { debug("NVRAM: System is not UEFI"); cleanup(); return false; } + + // helpers stuff + auto ci_ascii_contains = [](const BYTE* data, size_t len, const char* pat) noexcept -> bool { + if (!data || len == 0 || !pat) return false; + const size_t plen = strlen(pat); if (len < plen) return false; + const BYTE p0 = static_cast((pat[0] >= 'A' && pat[0] <= 'Z') ? (pat[0] + 32) : pat[0]); + const BYTE* end = data + (len - plen); + for (const BYTE* p = data; p <= end; ++p) { + BYTE c0 = *p; c0 = static_cast((c0 >= 'A' && c0 <= 'Z') ? (c0 + 32) : c0); + if (c0 != p0) continue; bool ok = true; - for (size_t j = 0; j < plen; ++j) { - char c = static_cast(data[i + j]); - unsigned char uc = static_cast(c); - char lc = static_cast(::tolower(uc)); - if (lc != pattern[j]) { ok = false; break; } + for (size_t j = 1; j < plen; ++j) { + BYTE dj = p[j]; dj = static_cast((dj >= 'A' && dj <= 'Z') ? (dj + 32) : dj); + BYTE pj = static_cast((pat[j] >= 'A' && pat[j] <= 'Z') ? (pat[j] + 32) : pat[j]); + if (dj != pj) { ok = false; break; } } if (ok) return true; } return false; - }; - - auto contains_redhat_utf16le_ci = [](const WCHAR* wdata, size_t wlen)->bool { - static const wchar_t pattern[] = L"red hat secure boot"; - const size_t plen = (sizeof(pattern) / sizeof(pattern[0])) - 1; - if (wlen < plen) return false; - for (size_t i = 0; i <= wlen - plen; ++i) { + }; + auto ci_utf16le_contains = [](const WCHAR* data, size_t wlen, const wchar_t* pat) noexcept -> bool { + if (!data || wlen == 0 || !pat) return false; + const size_t plen = wcslen(pat); if (wlen < plen) return false; + const WCHAR p0 = static_cast((pat[0] >= L'A' && pat[0] <= L'Z') ? (pat[0] + 32) : pat[0]); + const WCHAR* end = data + (wlen - plen); + for (const WCHAR* p = data; p <= end; ++p) { + WCHAR c0 = *p; c0 = static_cast((c0 >= L'A' && c0 <= L'Z') ? (c0 + 32) : c0); + if (c0 != p0) continue; bool ok = true; - for (size_t j = 0; j < plen; ++j) { - wchar_t wc = wdata[i + j]; - wchar_t lw = static_cast(::towlower(wc)); - if (lw != pattern[j]) { ok = false; break; } + for (size_t j = 1; j < plen; ++j) { + WCHAR dj = p[j]; dj = static_cast((dj >= L'A' && dj <= L'Z') ? (dj + 32) : dj); + WCHAR pj = static_cast((pat[j] >= L'A' && pat[j] <= L'Z') ? (pat[j] + 32) : pat[j]); + if (dj != pj) { ok = false; break; } } if (ok) return true; } return false; - }; + }; - PVARIABLE_NAME varName = reinterpret_cast(resBuffer.data()); - const size_t bufSize = resBuffer.size(); - constexpr size_t MAX_NAME_BYTES = 4096; + constexpr const char* vendor_ascii[] = { "msi","asrock","asus","asustek","gigabyte","giga-byte","micro-star","microstar" }; + constexpr const wchar_t* vendor_wide[] = { L"msi",L"asrock",L"asus",L"asustek",L"gigabyte",L"giga-byte",L"micro-star",L"microstar" }; + constexpr const char redhat_ascii[] = "red hat"; + constexpr const wchar_t redhat_wide[] = L"red hat"; + + constexpr size_t MAX_VAR_SZ = 131072; // 128KB + // Use unique_ptr to move buffer from stack to heap (fixes 132KB stack usage) + std::unique_ptr stackBufPtr(new BYTE[MAX_VAR_SZ]()); + BYTE* stackBuf = stackBufPtr.get(); + + BYTE* pkDefaultBuf = nullptr, * pkBuf = nullptr, * kekDefaultBuf = nullptr, * kekBuf = nullptr; + SIZE_T pkDefaultLen = 0, pkLen = 0, kekDefaultLen = 0, kekLen = 0; + + auto read_var_to_buf = [&](const std::wstring& name, const GUID& guid, BYTE*& outBuf, SIZE_T& outLen) noexcept -> bool { + wchar_t guidStr[40] = {}; + constexpr wchar_t hex[] = L"0123456789ABCDEF"; + guidStr[0] = L'{'; + guidStr[1] = hex[static_cast((guid.Data1 >> 28) & 0xF)]; guidStr[2] = hex[static_cast((guid.Data1 >> 24) & 0xF)]; guidStr[3] = hex[static_cast((guid.Data1 >> 20) & 0xF)]; guidStr[4] = hex[static_cast((guid.Data1 >> 16) & 0xF)]; + guidStr[5] = hex[static_cast((guid.Data1 >> 12) & 0xF)]; guidStr[6] = hex[static_cast((guid.Data1 >> 8) & 0xF)]; guidStr[7] = hex[static_cast((guid.Data1 >> 4) & 0xF)]; guidStr[8] = hex[static_cast(guid.Data1 & 0xF)]; + guidStr[9] = L'-'; + guidStr[10] = hex[static_cast((guid.Data2 >> 12) & 0xF)]; guidStr[11] = hex[static_cast((guid.Data2 >> 8) & 0xF)]; guidStr[12] = hex[static_cast((guid.Data2 >> 4) & 0xF)]; guidStr[13] = hex[static_cast(guid.Data2 & 0xF)]; + guidStr[14] = L'-'; + guidStr[15] = hex[static_cast((guid.Data3 >> 12) & 0xF)]; guidStr[16] = hex[static_cast((guid.Data3 >> 8) & 0xF)]; guidStr[17] = hex[static_cast((guid.Data3 >> 4) & 0xF)]; guidStr[18] = hex[static_cast(guid.Data3 & 0xF)]; + guidStr[19] = L'-'; + guidStr[20] = hex[static_cast((guid.Data4[0] >> 4) & 0xF)]; guidStr[21] = hex[static_cast(guid.Data4[0] & 0xF)]; guidStr[22] = hex[static_cast((guid.Data4[1] >> 4) & 0xF)]; guidStr[23] = hex[static_cast(guid.Data4[1] & 0xF)]; + guidStr[24] = L'-'; + size_t idx = 25; + for (int i = 2; i < 8; ++i) { guidStr[idx++] = hex[static_cast((guid.Data4[i] >> 4) & 0xF)]; guidStr[idx++] = hex[static_cast(guid.Data4[i] & 0xF)]; } + guidStr[37] = L'}'; guidStr[38] = L'\0'; + + DWORD ret = GetFirmwareEnvironmentVariableW(name.c_str(), guidStr, stackBuf, static_cast(MAX_VAR_SZ)); + if (ret > 0) { outBuf = stackBuf; outLen = ret; return true; } + const DWORD err = GetLastError(); + if (err != ERROR_INSUFFICIENT_BUFFER) { outBuf = nullptr; outLen = 0; return false; } + // fallback allocate up to 256KB + PVOID base = nullptr; SIZE_T rsz = 256 * 1024; + const ULONG ALLOC_FLAGS = 0x3000; const ULONG PAGE_RW = 0x04; + NTSTATUS st = pNtAllocateVirtualMemory(hCurrentProcess, &base, 0, &rsz, ALLOC_FLAGS, PAGE_RW); + if (st != 0 || !base) { outBuf = nullptr; outLen = 0; return false; } + ret = GetFirmwareEnvironmentVariableW(name.c_str(), guidStr, reinterpret_cast(base), static_cast(rsz)); + if (ret > 0) { outBuf = reinterpret_cast(base); outLen = ret; return true; } + // free and fail + SIZE_T zero = 0; pNtFreeVirtualMemory(hCurrentProcess, &base, &zero, 0x8000); + outBuf = nullptr; outLen = 0; return false; + }; + PVARIABLE_NAME varName = reinterpret_cast(enumBase); + const size_t bufSize = static_cast(bufferLength); + constexpr size_t MAX_NAME_BYTES = 4096; while (true) { - const uintptr_t basePtr = reinterpret_cast(resBuffer.data()); + const uintptr_t basePtr = reinterpret_cast(enumBase); const uintptr_t curPtr = reinterpret_cast(varName); if (curPtr < basePtr) break; const size_t offset = static_cast(curPtr - basePtr); if (offset >= bufSize) break; - const size_t nameOffset = offsetof(VARIABLE_NAME, Name); if (bufSize - offset < nameOffset) break; - size_t nameMaxBytes = 0; if (varName->NextEntryOffset != 0) { const SIZE_T ne = static_cast(varName->NextEntryOffset); - if (ne <= nameOffset) { cleanup(); return false; } + if (ne <= nameOffset) { SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &enumBase, &z, 0x8000); cleanup(); return false; } if (ne > bufSize - offset) break; nameMaxBytes = ne - nameOffset; } else { - if (offset + nameOffset >= bufSize) { cleanup(); return false; } + if (offset + nameOffset >= bufSize) { SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &enumBase, &z, 0x8000); cleanup(); return false; } nameMaxBytes = bufSize - (offset + nameOffset); } if (nameMaxBytes > MAX_NAME_BYTES) nameMaxBytes = MAX_NAME_BYTES; - - std::wstring_view nameView; + std::wstring nameView; if (nameMaxBytes >= sizeof(WCHAR)) { const WCHAR* namePtr = reinterpret_cast(reinterpret_cast(varName) + nameOffset); const size_t maxChars = nameMaxBytes / sizeof(WCHAR); size_t realChars = 0; while (realChars < maxChars && namePtr[realChars] != L'\0') ++realChars; - if (realChars == maxChars) { cleanup(); return false; } - nameView = std::wstring_view(namePtr, realChars); - } - - auto format_guid = [](const GUID& g)->std::wstring { - wchar_t buf[40] = {}; - int written = _snwprintf_s(buf, _countof(buf), _TRUNCATE, - L"{%08lX-%04hX-%04hX-%02X%02X-%02X%02X%02X%02X%02X%02X}", - static_cast(g.Data1), - static_cast(g.Data2), - static_cast(g.Data3), - static_cast(g.Data4[0]), static_cast(g.Data4[1]), - static_cast(g.Data4[2]), static_cast(g.Data4[3]), - static_cast(g.Data4[4]), static_cast(g.Data4[5]), - static_cast(g.Data4[6]), static_cast(g.Data4[7])); - if (written <= 0) return std::wstring(); - return std::wstring(buf); - }; - - if (!nameView.empty() && nameView.rfind(L"VMM", 0) == 0) { - debug("NVRAM: Detected hypervisor signature"); - cleanup(); - return true; + if (realChars == maxChars) { SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &enumBase, &z, 0x8000); cleanup(); return false; } + nameView = std::wstring(namePtr, realChars); } - + if (!nameView.empty() && nameView.rfind(L"VMM", 0) == 0) { debug("NVRAM: Detected hypervisor signature"); SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &enumBase, &z, 0x8000); cleanup(); return true; } if (nameView == L"dbDefault") found_dbDefault = true; else if (nameView == L"KEKDefault") found_KEKDefault = true; else if (nameView == L"PKDefault") found_PKDefault = true; else if (nameView == L"dbxDefault") found_dbxDefault = true; else if (nameView == L"MemoryOverwriteRequestControlLock") found_MORCL = true; - if (!pk_checked && nameView == L"PKDefault") { - const std::wstring guidStr = format_guid(varName->VendorGuid); - if (guidStr.empty()) { cleanup(); return true; } - - DWORD bufSizeAttempt = 8192; - std::vector valueBuf; - for (int attempt = 0; attempt < 4; ++attempt) { // up to 128KB aprox - valueBuf.resize(bufSizeAttempt); - DWORD readLen = GetFirmwareEnvironmentVariableW( - std::wstring(nameView).c_str(), - guidStr.c_str(), - valueBuf.data(), - bufSizeAttempt); - if (readLen > 0) { - valueBuf.resize(readLen); - break; - } - DWORD err = GetLastError(); - if (err == ERROR_INSUFFICIENT_BUFFER) { - bufSizeAttempt *= 2; - continue; - } - valueBuf.clear(); - break; - } + if (nameView == L"PKDefault") (void)read_var_to_buf(std::wstring(nameView), varName->VendorGuid, pkDefaultBuf, pkDefaultLen); + else if (nameView == L"PK") (void)read_var_to_buf(std::wstring(nameView), varName->VendorGuid, pkBuf, pkLen); + else if (nameView == L"KEKDefault") (void)read_var_to_buf(std::wstring(nameView), varName->VendorGuid, kekDefaultBuf, kekDefaultLen); + else if (nameView == L"KEK") (void)read_var_to_buf(std::wstring(nameView), varName->VendorGuid, kekBuf, kekLen); - bool pk_has_redhat = false; - if (!valueBuf.empty()) { - if (valueBuf.size() >= 2 && (valueBuf.size() % 2) == 0) { - const WCHAR* wptr = reinterpret_cast(valueBuf.data()); - size_t wlen = valueBuf.size() / sizeof(WCHAR); - if (contains_redhat_utf16le_ci(wptr, wlen)) pk_has_redhat = true; + // https://github.com/tianocore/edk2/blob/af9cc80359e320690877e4add870aa13fe889fbe/SecurityPkg/Library/AuthVariableLib/AuthServiceInternal.h + if (nameView == L"certdb" || nameView == L"certdbv") { + debug("NVRAM: EDK II (TianoCore) detected"); + SIZE_T z = 0; + pNtFreeVirtualMemory(hCurrentProcess, &enumBase, &z, 0x8000); + cleanup(); + return true; // we cant return a brand here since its used in VMWare, QEMU with OVMF, VirtualBox, etc + } + + if (nameView == L"Boot0000") { // should be Windows Boot Manager + BYTE* bootBuf = nullptr; SIZE_T bootLen = 0; + if (read_var_to_buf(nameView, varName->VendorGuid, bootBuf, bootLen)) { + bool anomaly = (bootLen < 6); + if (!anomaly) { + unsigned short fplLen = 0; + memcpy(&fplLen, bootBuf + 4, sizeof(fplLen)); + // we could also check if loadOptionsLength is 136 + if (fplLen != 116) anomaly = true; } - if (!pk_has_redhat) { - if (contains_redhat_ascii_ci(valueBuf.data(), valueBuf.size())) pk_has_redhat = true; + + if (bootBuf && bootBuf != stackBuf) { + PVOID b = bootBuf; SIZE_T z = 0; + pNtFreeVirtualMemory(hCurrentProcess, &b, &z, 0x8000); } - } - pk_checked = true; - if (pk_has_redhat) { - debug("NVRAM: QEMU detected"); - cleanup(); - return core::add(brands::QEMU); + if (anomaly) { + debug("NVRAM: Environment was loaded using a virtual boot loader"); // "virtual" here -> non genuine + SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &enumBase, &z, 0x8000); + cleanup(); + return true; + } } } - if (found_MORCL && found_dbDefault && found_dbxDefault && found_KEKDefault && found_PKDefault && pk_checked) { - break; - } - if (varName->NextEntryOffset == 0) break; const SIZE_T ne = static_cast(varName->NextEntryOffset); const size_t nextOffset = offset + ne; if (nextOffset <= offset || nextOffset > bufSize) break; - varName = reinterpret_cast(reinterpret_cast(resBuffer.data()) + nextOffset); + varName = reinterpret_cast(reinterpret_cast(enumBase) + nextOffset); } + // free stuff + { SIZE_T zero = 0; pNtFreeVirtualMemory(hCurrentProcess, &enumBase, &zero, 0x8000); enumBase = nullptr; enumSize = 0; } + if (!found_MORCL) { debug("NVRAM: Missing MemoryOverwriteRequestControlLock"); cleanup(); return true; } if (!found_dbDefault) { debug("NVRAM: Missing dbDefault"); cleanup(); return true; } if (!found_dbxDefault) { debug("NVRAM: Missing dbxDefault"); cleanup(); return true; } if (!found_KEKDefault) { debug("NVRAM: Missing KEKDefault"); cleanup(); return true; } if (!found_PKDefault) { debug("NVRAM: Missing PKDefault"); cleanup(); return true; } + // check for official red hat certs + bool found_redhat = false; + if (pkDefaultBuf && pkDefaultLen) { + if ((pkDefaultLen >= 2) && ((pkDefaultLen % 2) == 0)) { + const WCHAR* wptr = reinterpret_cast(pkDefaultBuf); + const size_t wlen = pkDefaultLen / sizeof(WCHAR); + if (ci_utf16le_contains(wptr, wlen, redhat_wide)) found_redhat = true; + } + if (!found_redhat) if (ci_ascii_contains(pkDefaultBuf, pkDefaultLen, redhat_ascii)) found_redhat = true; + } + if (found_redhat) { + debug("NVRAM: QEMU/OVMF detected"); + if (pkBuf && pkBuf != stackBuf) { PVOID b = pkBuf; SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &b, &z, 0x8000); } + if (kekBuf && kekBuf != stackBuf) { PVOID b = kekBuf; SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &b, &z, 0x8000); } + if (pkDefaultBuf && pkDefaultBuf != stackBuf) { PVOID b = pkDefaultBuf; SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &b, &z, 0x8000); } + if (kekDefaultBuf && kekDefaultBuf != stackBuf) { PVOID b = kekDefaultBuf; SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &b, &z, 0x8000); } + cleanup(); return core::add(brands::QEMU); + } + + // Vendor string checks and PK/KEK mismatch checks + auto buf_contains_vendor_any = [&](BYTE* buf, SIZE_T len) noexcept -> bool { + if (!buf || len == 0) return false; + if ((len >= 2) && ((len % 2) == 0)) { + const WCHAR* wptr = reinterpret_cast(buf); const size_t wlen = len / sizeof(WCHAR); + for (const wchar_t* p : vendor_wide) if (ci_utf16le_contains(wptr, wlen, p)) return true; + } + for (const char* p : vendor_ascii) if (ci_ascii_contains(buf, len, p)) return true; + return false; + }; + auto buf_contains_vendor_specific = [&](BYTE* buf, SIZE_T len, const char* a, const wchar_t* w) noexcept -> bool { + if (!buf || len == 0) return false; + if ((len >= 2) && ((len % 2) == 0) && w) { const WCHAR* wp = reinterpret_cast(buf); if (ci_utf16le_contains(wp, len / sizeof(WCHAR), w)) return true; } + if (a) if (ci_ascii_contains(buf, len, a)) return true; + return false; + }; + + const bool pkdef_has_vendor = buf_contains_vendor_any(pkDefaultBuf, pkDefaultLen); + const bool kekdef_has_vendor = buf_contains_vendor_any(kekDefaultBuf, kekDefaultLen); + + if (pkdef_has_vendor || kekdef_has_vendor) { + for (size_t i = 0; i < sizeof(vendor_ascii) / sizeof(*vendor_ascii); ++i) { + const char* vasc = vendor_ascii[i]; + const wchar_t* vw = vendor_wide[i]; + const bool inPKDef = buf_contains_vendor_specific(pkDefaultBuf, pkDefaultLen, vasc, vw); + const bool inKEKDef = buf_contains_vendor_specific(kekDefaultBuf, kekDefaultLen, vasc, vw); + if (!inPKDef && !inKEKDef) continue; + const bool inPK = buf_contains_vendor_specific(pkBuf, pkLen, vasc, vw); + const bool inKEK = buf_contains_vendor_specific(kekBuf, kekLen, vasc, vw); + if (!inPK && !inKEK) { + debug("NVRAM: Vendor string found in PKDefault/KEKDefault but missing from active PK/KEK"); + if (pkBuf && pkBuf != stackBuf) { PVOID b = pkBuf; SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &b, &z, 0x8000); } + if (kekBuf && kekBuf != stackBuf) { PVOID b = kekBuf; SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &b, &z, 0x8000); } + if (pkDefaultBuf && pkDefaultBuf != stackBuf) { PVOID b = pkDefaultBuf; SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &b, &z, 0x8000); } + if (kekDefaultBuf && kekDefaultBuf != stackBuf) { PVOID b = kekDefaultBuf; SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &b, &z, 0x8000); } + cleanup(); return core::add(brands::QEMU); + } + } + } + + if (pkDefaultBuf && pkBuf && (pkDefaultLen != pkLen || memcmp(pkDefaultBuf, pkBuf, static_cast(pkDefaultLen < pkLen ? pkDefaultLen : pkLen)) != 0)) + debug("NVRAM: PK vs PKDefault raw mismatch detected"); + if (kekDefaultBuf && kekBuf && (kekDefaultLen != kekLen || memcmp(kekDefaultBuf, kekBuf, static_cast(kekDefaultLen < kekLen ? kekDefaultLen : kekLen)) != 0)) + debug("NVRAM: KEK vs KEKDefault raw mismatch detected"); + + if (pkBuf && pkBuf != stackBuf) { PVOID b = pkBuf; SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &b, &z, 0x8000); } + if (kekBuf && kekBuf != stackBuf) { PVOID b = kekBuf; SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &b, &z, 0x8000); } + if (pkDefaultBuf && pkDefaultBuf != stackBuf) { PVOID b = pkDefaultBuf; SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &b, &z, 0x8000); } + if (kekDefaultBuf && kekDefaultBuf != stackBuf) { PVOID b = kekDefaultBuf; SIZE_T z = 0; pNtFreeVirtualMemory(hCurrentProcess, &b, &z, 0x8000); } + cleanup(); return false; } @@ -9405,101 +9548,134 @@ struct VM { * @implements VM::EDID */ [[nodiscard]] static bool edid() { - auto decodeManufacturer = [](const BYTE* edid, char out[4]) { + // compiles to single mov + auto read_le16 = [](const BYTE* p) noexcept -> u16 { + u16 v; memcpy(&v, p, sizeof(v)); return v; + }; + + auto read_le32 = [](const BYTE* p) noexcept -> u32 { + u32 v; memcpy(&v, p, sizeof(v)); return v; + }; + + auto decode_manufacturer = [](const BYTE* edid, char out[4]) noexcept { const u16 word = static_cast((edid[8] << 8) | edid[9]); + + // 5 bits per character. 0x01='A', 0x1A='Z' const u8 c1 = static_cast((word >> 10) & 0x1F); const u8 c2 = static_cast((word >> 5) & 0x1F); - const u8 c3 = static_cast((word >> 0) & 0x1F); + const u8 c3 = static_cast(word & 0x1F); + + // '?' is fallback for valid EDID range 1-26 out[0] = (c1 >= 1 && c1 <= 26) ? static_cast('A' + c1 - 1) : '?'; out[1] = (c2 >= 1 && c2 <= 26) ? static_cast('A' + c2 - 1) : '?'; out[2] = (c3 >= 1 && c3 <= 26) ? static_cast('A' + c3 - 1) : '?'; out[3] = '\0'; }; - auto isThreeUpperAlpha = [](const char m[4]) -> bool { + auto is_three_upper_alpha = [](const char m[4]) noexcept -> bool { return (m[0] >= 'A' && m[0] <= 'Z') && (m[1] >= 'A' && m[1] <= 'Z') && (m[2] >= 'A' && m[2] <= 'Z'); }; - auto edidChecksumValid = [](const BYTE* edid, size_t len) -> bool { + auto edid_checksum_valid = [](const BYTE* edid, size_t len) noexcept -> bool { if (len < 128) return false; + u8 sum = 0; - for (size_t i = 0; i < 128; ++i) sum = static_cast(sum + edid[i]); + const BYTE* end = edid + 128; + + while (edid < end) { + sum += *edid++; + } + return sum == 0; }; - auto extractMonitorName = [](const BYTE* edid, size_t len, char out[32]) -> bool { - if (len < 128) { out[0] = '\0'; return false; } - const size_t base = 54; - for (int i = 0; i < 4; ++i) { - size_t off = base + i * 18; - if (off + 18 > 128) break; - const BYTE* block = edid + off; - // descriptor: bytes 0 to 1 == 0x00, byte3 = tag - if (block[0] == 0x00 && block[1] == 0x00) { - if (block[3] == 0xFC) { // monitor name - int outi = 0; - for (int j = 5; j < 18 && outi < 31; ++j) { - char c = static_cast(block[j]); - if (c == 0x0A || c == 0x0D || c == 0x00) break; - out[outi++] = c; - } - out[outi] = '\0'; - // trim trailing spaces - while (outi > 0 && (out[outi - 1] == ' ' || out[outi - 1] == '\t')) { out[--outi] = '\0'; } - return outi > 0; - } - // monitor serial descriptor 0xFF could be used as name fallback - if (block[3] == 0xFF) { - int outi = 0; - for (int j = 5; j < 18 && outi < 31; ++j) { - char c = static_cast(block[j]); - if (c == 0x0A || c == 0x0D || c == '\0') break; - out[outi++] = c; - } - out[outi] = '\0'; - while (outi > 0 && (out[outi - 1] == ' ' || out[outi - 1] == '\t')) { out[--outi] = '\0'; } - return outi > 0; - } + auto extract_monitor_name = [](const BYTE* edid, size_t len, char out[32]) noexcept -> bool { + out[0] = '\0'; + if (len < 128) return false; + + // Standard EDID 1.3/1.4 Descriptor offsets + const BYTE* block = edid + 54; + const BYTE* end = edid + 126; // block area + + const BYTE* best_block = nullptr; + + for (; block <= end - 18; block += 18) { + // bytes 0-2 must be 0 to indicate a Display Descriptor + if (block[0] != 0 || block[1] != 0 || block[2] != 0) continue; + + const u8 tag = block[3]; + + // 0xFC = Monitor Name + if (tag == 0xFC) { + best_block = block; + break; + } + + // 0xFF = Monitor Serial (this is only a fallback) + if (tag == 0xFF && !best_block) { + best_block = block; } } - out[0] = '\0'; - return false; - }; - auto read_le16 = [](const BYTE* p) -> u16 { - return static_cast(p[0] | (p[1] << 8)); - }; - auto read_le32 = [](const BYTE* p) -> u32 { - return static_cast(p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24)); + if (best_block) { + int outi = 0; + for (int j = 5; j < 18 && outi < 31; ++j) { + const char c = static_cast(best_block[j]); + // Terminate on newline (0x0A) or carriage return (0x0D) or null + if (c == 0x0A || c == 0x0D || c == '\0') break; + out[outi++] = c; + } + + // right-trim spaces + while (outi > 0 && (out[outi - 1] == ' ' || out[outi - 1] == '\t')) { + --outi; + } + + out[outi] = '\0'; + return outi > 0; + } + + return false; }; - auto getDevicePropertyA = [](HDEVINFO devInfo, SP_DEVINFO_DATA& devData, DWORD propId, - char* outBuf, DWORD outBufSize) -> bool { + auto get_device_property = [](HDEVINFO dev_info, SP_DEVINFO_DATA& dev_data, DWORD prop_id, + char* out_buf, DWORD out_buf_size) noexcept -> bool { DWORD needed = 0; - if (SetupDiGetDeviceRegistryPropertyA(devInfo, &devData, propId, nullptr, - reinterpret_cast(outBuf), outBufSize, &needed)) { - outBuf[outBufSize - 1] = '\0'; + + if (SetupDiGetDeviceRegistryPropertyA(dev_info, &dev_data, prop_id, nullptr, + reinterpret_cast(out_buf), out_buf_size, &needed)) { + if (out_buf_size > 0) out_buf[out_buf_size - 1] = '\0'; return true; } + const DWORD err = GetLastError(); + if (err == ERROR_INSUFFICIENT_BUFFER && needed > 0 && needed < 65536) { - HLOCAL h = LocalAlloc(LMEM_FIXED, static_cast(needed) + 1); + + void* h = malloc(static_cast(needed) + 1); if (!h) return false; - if (SetupDiGetDeviceRegistryPropertyA(devInfo, &devData, propId, nullptr, + + if (SetupDiGetDeviceRegistryPropertyA(dev_info, &dev_data, prop_id, nullptr, reinterpret_cast(h), needed, &needed)) { - DWORD toCopy = (needed < outBufSize - 1) ? needed : outBufSize - 1; - memcpy(outBuf, h, toCopy); - outBuf[toCopy] = '\0'; - LocalFree(h); + + const DWORD to_copy = (needed < out_buf_size - 1) ? needed : (out_buf_size - 1); + + if (out_buf_size > 0) { + memcpy(out_buf, h, to_copy); + out_buf[to_copy] = '\0'; + } + + free(h); return true; } - LocalFree(h); + free(h); } - outBuf[0] = '\0'; + + if (out_buf_size > 0) out_buf[0] = '\0'; return false; - }; + }; const HDEVINFO devInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_MONITOR, nullptr, nullptr, DIGCF_PRESENT); if (devInfo == INVALID_HANDLE_VALUE) return false; @@ -9519,7 +9695,7 @@ struct VM { BYTE edid_stack[256]; DWORD bufSize = static_cast(sizeof(edid_stack)); - LONG rc = RegQueryValueExA(hDevKey, "EDID", nullptr, nullptr, edid_stack, &bufSize); + const LONG rc = RegQueryValueExA(hDevKey, "EDID", nullptr, nullptr, edid_stack, &bufSize); RegCloseKey(hDevKey); BYTE* edid = nullptr; @@ -9564,24 +9740,24 @@ struct VM { int score = 0; - if (!edidChecksumValid(edid, bufSize)) score += 1; + if (!edid_checksum_valid(edid, bufSize)) score += 1; char manu[4]; - decodeManufacturer(edid, manu); - if (!isThreeUpperAlpha(manu)) score += 1; + decode_manufacturer(edid, manu); + if (!is_three_upper_alpha(manu)) score += 1; - u16 product = static_cast(edid[10] | (edid[11] << 8)); // because its little-endian - u32 serial = static_cast(edid[12] | (edid[13] << 8) | (edid[14] << 16) | (edid[15] << 24)); + const u16 product = static_cast(edid[10] | (edid[11] << 8)); // because its little-endian + const u32 serial = static_cast(edid[12] | (edid[13] << 8) | (edid[14] << 16) | (edid[15] << 24)); if (product == 0) score += 1; if (serial == 0) score += 1; char monname[32]; - bool hasName = extractMonitorName(edid, bufSize, monname); + const bool hasName = extract_monitor_name(edid, bufSize, monname); if (!hasName) score += 1; // no way you dont have a EDID monitor name char propBuf[512]; - bool haveFriendly = getDevicePropertyA(devInfo, devData, SPDRP_FRIENDLYNAME, propBuf, sizeof(propBuf)); // friendly_name is often empty, like in Digital-Flachbildschirm monitors - bool haveDevDesc = getDevicePropertyA(devInfo, devData, SPDRP_DEVICEDESC, propBuf, sizeof(propBuf)); + const bool haveFriendly = get_device_property(devInfo, devData, SPDRP_FRIENDLYNAME, propBuf, sizeof(propBuf)); // friendly_name is often empty, like in Digital-Flachbildschirm monitors + const bool haveDevDesc = get_device_property(devInfo, devData, SPDRP_DEVICEDESC, propBuf, sizeof(propBuf)); if (!haveFriendly && !haveDevDesc) score += 1; if (used_heap) LocalFree(heap_buf); @@ -9871,122 +10047,64 @@ struct VM { enum class MBVendor { Unknown = 0, Intel = 1, AMD = 2 }; - auto detect_motherboard = []() -> MBVendor { - static constexpr const char* TOKENS[] = { - "host bridge", "northbridge", "southbridge", "pci bridge", "chipset", "pch", "fch", - "platform controller", "lpc", "sata controller", "ahci", "ide controller", "usb controller", - "xhci", "usb3", "usb 3.0", "usb 3", "pcie root", "pci express", " sata", nullptr + auto detect_motherboard = []() noexcept -> MBVendor { + static constexpr const wchar_t* TOKENS[] = { + L"host bridge", L"northbridge", L"southbridge", L"pci bridge", L"chipset", L"pch", L"fch", + L"platform controller", L"lpc", L"sata controller", L"ahci", L"ide controller", L"usb controller", + L"xhci", L"usb3", L"usb 3.0", L"usb 3", L"pcie root", L"pci express", L" sata", nullptr }; - static bool meta_ready = false; - static const char* token_ptrs[32]; - static int token_lens[32]; - static int token_count = 0; - static unsigned char first_unique[128]; - static int first_unique_count = 0; - - auto build_meta = [&]() { - if (meta_ready) return; - int i = 0; - for (; TOKENS[i]; ++i) {} - token_count = i; - assert(token_count < 32); - bool seen[128] = {}; - for (int t = 0; t < token_count; ++t) { - token_ptrs[t] = TOKENS[t]; - token_lens[t] = static_cast(std::strlen(TOKENS[t])); - unsigned char fc = static_cast(token_ptrs[t][0]); - if (fc < 128 && !seen[fc]) { - first_unique[first_unique_count++] = fc; - seen[fc] = true; - } - } - meta_ready = true; - }; + auto contains_token = [](const wchar_t* haystack) noexcept -> bool { + if (!haystack) return false; + for (const wchar_t* const* t = TOKENS; *t; ++t) { + const wchar_t* needle = *t; + const wchar_t* h = haystack; - auto ascii_lower = [](unsigned char c) -> unsigned char { - if (c >= 'A' && c <= 'Z') return static_cast(c + ('a' - 'A')); - return c; - }; + // naive scan is faster than BM/KMP for very short needles/haystacks + while (*h) { + const wchar_t* h_iter = h; + const wchar_t* n_iter = needle; - const size_t STACK_CAP = 4096; - auto wide_to_ascii = [&](const wchar_t* wptr, char* stackBuf, size_t stackCap, std::vector* heapBuf, size_t& outLen) -> const char* { - outLen = 0; - if (!wptr || *wptr == L'\0') return nullptr; - const wchar_t* p = wptr; - char* out = stackBuf; - size_t cap = stackCap; - while (*p) { - wchar_t wc = *p++; - unsigned char c; - if (wc <= 127) { - c = ascii_lower(static_cast(wc)); - } - else { - c = 0; - } - if (outLen >= cap) { - if (!heapBuf) return nullptr; - heapBuf->assign(stackBuf, stackBuf + cap); - out = heapBuf->data(); - cap = heapBuf->capacity(); - } - out[outLen++] = static_cast(c); - } - return out; - }; + while (*n_iter) { + wchar_t hc = *h_iter; + if (hc >= L'A' && hc <= L'Z') hc += 32; - using u32 = unsigned int; - auto __memchr = [&](const char* data, size_t len) -> u32 { - if (!data || len == 0) return 0; - build_meta(); - u32 mask = 0; - for (int fi = 0; fi < first_unique_count; ++fi) { - unsigned char fc = first_unique[fi]; - const void* cur = data; - size_t remaining = len; - while (remaining > 0) { - const void* found = memchr(cur, fc, remaining); - if (!found) break; - const char* pos = static_cast(found); - long long idx = pos - data; - for (int t = 0; t < token_count; ++t) { - if (static_cast(token_ptrs[t][0]) != fc) continue; - int tlen = token_lens[t]; - if (idx + static_cast(tlen) <= len) { - if (memcmp(data + idx, token_ptrs[t], static_cast(tlen)) == 0) { - mask |= (1u << t); - } - } + if (hc != *n_iter) break; + h_iter++; + n_iter++; } - remaining = len - (idx + 1); - cur = data + idx + 1; + + if (!*n_iter) return true; + h++; } } - return mask; + return false; }; - auto find_vendor_hex = [&](const wchar_t* wptr) -> u32 { + auto find_vendor_hex = [](const wchar_t* wptr) noexcept -> u32 { if (!wptr) return 0; const wchar_t* p = wptr; while (*p) { - const wchar_t c = *p; - if ((c | 0x20) == L'v') { - if ((p[1] | 0x20) == L'e' && (p[2] | 0x20) == L'n' && p[3] == L'_') { - const wchar_t* q = p + 4; - u32 val = 0; - int got = 0; - while (got < 4 && *q) { - wchar_t wc = *q; - u32 nib = 0; - if (wc >= L'0' && wc <= L'9') nib = static_cast(wc - L'0'); - else if ((wc | 0x20) >= L'a' && (wc | 0x20) <= L'f') nib = static_cast((wc | 0x20) - L'a' + 10); - else break; - val = static_cast((val << 4) | nib); - ++got; ++q; - } - if (got == 4) return val; + // Check for "VEN_" (case-insensitive) + if (((p[0] | 0x20) == L'v') && + ((p[1] | 0x20) == L'e') && + ((p[2] | 0x20) == L'n') && + (p[3] == L'_')) { + + const wchar_t* q = p + 4; + u32 val = 0; + int got = 0; + while (got < 4 && *q) { + const wchar_t c = *q; + u32 nib = 0; + if (c >= L'0' && c <= L'9') nib = static_cast(c - L'0'); + else if ((c | 0x20) >= L'a' && (c | 0x20) <= L'f') nib = static_cast((c | 0x20) - L'a' + 10); + else break; + + val = (val << 4) | nib; + ++got; ++q; } + if (got == 4) return val; } ++p; } @@ -9996,48 +10114,53 @@ struct VM { // setupapi stuff int intel_hits = 0; int amd_hits = 0; - char stack_buf[4096]{}; - std::vector heap_buf; - std::vector prop_buf; - auto scan_devices = [&](const GUID* classGuid, DWORD flags) { + wchar_t stack_buf[1024]{}; + std::vector heap_buf; // fallback for rare huge strings + + auto scan_devices = [&](const GUID* classGuid, DWORD flags) noexcept { HDEVINFO hDevInfo = SetupDiGetClassDevsW(classGuid, nullptr, nullptr, flags); if (hDevInfo == INVALID_HANDLE_VALUE) return; SP_DEVINFO_DATA devInfoData{}; devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); - DWORD reqSize = 0; for (DWORD i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &devInfoData); ++i) { - if (!SetupDiGetDeviceRegistryPropertyW(hDevInfo, &devInfoData, SPDRP_DEVICEDESC, nullptr, nullptr, 0, &reqSize)) { - if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) continue; - } + const wchar_t* wDesc = nullptr; + DWORD reqSize = 0; + DWORD propType = 0; - if (prop_buf.size() < reqSize) prop_buf.resize(reqSize); + if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, &devInfoData, SPDRP_DEVICEDESC, &propType, reinterpret_cast(stack_buf), sizeof(stack_buf), &reqSize)) { + wDesc = stack_buf; + } + else if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + if (heap_buf.size() < reqSize) heap_buf.resize(reqSize); + if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, &devInfoData, SPDRP_DEVICEDESC, &propType, heap_buf.data(), reqSize, nullptr)) { + wDesc = reinterpret_cast(heap_buf.data()); + } + } - if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, &devInfoData, SPDRP_DEVICEDESC, nullptr, prop_buf.data(), reqSize, nullptr)) { + // check if the description contains any interesting stuff + if (wDesc && contains_token(wDesc)) { - const wchar_t* wDesc = reinterpret_cast(prop_buf.data()); - size_t asciiLen = 0; - const char* asciiDesc = wide_to_ascii(wDesc, stack_buf, STACK_CAP, &heap_buf, asciiLen); + // if interesting get hwid to get vendor + const wchar_t* wHwId = nullptr; - // check if the description contains any interesting stuff - if (__memchr(asciiDesc, asciiLen)) { - // if interesting get hwid to get vendor - if (!SetupDiGetDeviceRegistryPropertyW(hDevInfo, &devInfoData, SPDRP_HARDWAREID, nullptr, nullptr, 0, &reqSize)) { - if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) continue; + if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, &devInfoData, SPDRP_HARDWAREID, &propType, reinterpret_cast(stack_buf), sizeof(stack_buf), &reqSize)) { + wHwId = stack_buf; + } + else if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + if (heap_buf.size() < reqSize) heap_buf.resize(reqSize); + if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, &devInfoData, SPDRP_HARDWAREID, &propType, heap_buf.data(), reqSize, nullptr)) { + wHwId = reinterpret_cast(heap_buf.data()); } + } - if (prop_buf.size() < reqSize) prop_buf.resize(reqSize); - - if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, &devInfoData, SPDRP_HARDWAREID, nullptr, prop_buf.data(), reqSize, nullptr)) { - const wchar_t* wHwId = reinterpret_cast(prop_buf.data()); - const u32 vid = find_vendor_hex(wHwId); - - if (vid == VID_INTEL) intel_hits++; - else if (vid == VID_AMD_ATI || vid == VID_AMD_MICRO) amd_hits++; - } + if (wHwId) { + const u32 vid = find_vendor_hex(wHwId); + if (vid == VID_INTEL) intel_hits++; + else if (vid == VID_AMD_ATI || vid == VID_AMD_MICRO) amd_hits++; } } } @@ -10097,35 +10220,37 @@ struct VM { * @implements VM::CLOCK */ [[nodiscard]] static bool clock() { - // The RTC (ACPI/CMOS RTC) timer can't be always detected via SetupAPI, it needs AML decode of the DSDT firmware table. + // The RTC (ACPI/CMOS RTC) timer can't be always detected via SetupAPI, it needs AML decode of the DSDT firmware table // The HPET (PNP0103) timer presence is already checked on VM::FIRMWARE - constexpr wchar_t pattern[] = L"PNP0100"; - constexpr size_t patLen = (sizeof(pattern) / sizeof(wchar_t)) - 1; - - auto tolower_ascii = [](wchar_t c) -> wchar_t { - return (c >= L'A' && c <= L'Z') ? static_cast(c + 32) : c; - }; + constexpr wchar_t pattern[] = L"pnp0100"; + constexpr size_t patLen = (sizeof(pattern) / sizeof(wchar_t)) - 1; - auto wcsstr_ci_ascii = [&](const wchar_t* hay) -> const wchar_t* { + auto wcsstr_ci_ascii = [&](const wchar_t* hay) noexcept -> const wchar_t* { if (!hay) return nullptr; + for (; *hay; ++hay) { - wchar_t h0 = tolower_ascii(*hay); - wchar_t p0 = tolower_ascii(pattern[0]); - if (h0 != p0) continue; + wchar_t h = *hay; + if (h >= L'A' && h <= L'Z') h += 32; + + if (h != pattern[0]) continue; + + size_t i = 1; + for (; i < patLen; ++i) { + wchar_t next_h = hay[i]; + + if (next_h == L'\0') return nullptr; + + if (next_h >= L'A' && next_h <= L'Z') next_h += 32; - const wchar_t* h = hay; - size_t i = 0; - for (; i < patLen; ++i, ++h) { - if (*h == L'\0') { i = SIZE_MAX; break; } - if (tolower_ascii(*h) != tolower_ascii(pattern[i])) break; + if (next_h != pattern[i]) break; } - if (i == patLen) return hay; // match - if (i == SIZE_MAX) return nullptr; + + if (i == patLen) return hay; } return nullptr; }; - HDEVINFO devs = SetupDiGetClassDevsW(nullptr, nullptr, nullptr, DIGCF_PRESENT); + const HDEVINFO devs = SetupDiGetClassDevsW(nullptr, nullptr, nullptr, DIGCF_PRESENT | DIGCF_ALLCLASSES); if (devs == INVALID_HANDLE_VALUE) return false; SP_DEVINFO_DATA devInfo{}; @@ -10144,7 +10269,7 @@ struct VM { if (!SetupDiGetDeviceRegistryPropertyW(devs, &devInfo, SPDRP_HARDWAREID, &propertyType, buffer, bufBytes, nullptr)) { - DWORD err = GetLastError(); + const DWORD err = GetLastError(); if (err == ERROR_INSUFFICIENT_BUFFER) { DWORD required = 0; SetupDiGetDeviceRegistryPropertyW(devs, &devInfo, SPDRP_HARDWAREID, @@ -10182,6 +10307,61 @@ struct VM { SetupDiDestroyDeviceInfoList(devs); return !found; } + + + /** + * @brief Check for anomalies in BIOS POST time + * @category Windows + * @implements VM::POST + */ + [[nodiscard]] static bool post() { + /* + * The motherboard must test and calibrate memory timings, which is time-consuming + * The system physically scans PCIe buses, initializes the GPU, powers up USB controllers, and waits for storage drives to report ready + * Fans must spin up, and capacitors must charge + * + * On VMs, RAM is simply a block of memory allocated by the host OS. There is no training or calibration required + * There are no drives to spin up, no fans to check, and no complex PCIe negotiation + * So at the end, we see cases like VirtualBox machines reporting 0.9s of last bios time, or QEMU machines with OVMF reporting 0s + */ + static constexpr wchar_t kSubKey[] = L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Power"; + static constexpr wchar_t kValueName[] = L"FwPOSTTime"; + HKEY hKey; + + long result = RegOpenKeyExW( + HKEY_LOCAL_MACHINE, + kSubKey, + 0, + KEY_QUERY_VALUE, + &hKey + ); + + if (result != ERROR_SUCCESS) { + return false; + } + + DWORD data = 0; + DWORD dataSize = sizeof(data); + + result = RegQueryValueExW( + hKey, + kValueName, + NULL, + NULL, + reinterpret_cast(&data), + &dataSize + ); + + RegCloseKey(hKey); + + if (result == ERROR_SUCCESS) { + if (data < 1500) { // 1.5s + return true; + } + } + + return false; + } // ADD NEW TECHNIQUE FUNCTION HERE #endif @@ -10880,30 +11060,36 @@ struct VM { // merge 2 brands, and make a single brand out of it. auto merge = [&](const char* a, const char* b, const char* result) -> void { - if ( - (brands.count(a) > 0) && - (brands.count(b) > 0) - ) { - brands.erase(a); - brands.erase(b); - brands.emplace(std::make_pair(result, 2)); - } + const auto it_a = brands.find(a); + if (it_a == brands.end()) return; + + const auto it_b = brands.find(b); + if (it_b == brands.end()) return; + + brands.erase(it_a); + brands.erase(it_b); + + brands.emplace(result, 2); }; // same as above, but for 3 auto triple_merge = [&](const char* a, const char* b, const char* c, const char* result) -> void { - if ( - (brands.count(a) > 0) && - (brands.count(b) > 0) && - (brands.count(c) > 0) - ) { - brands.erase(a); - brands.erase(b); - brands.erase(c); - brands.emplace(std::make_pair(result, 2)); - } - }; + const auto it_a = brands.find(a); + if (it_a == brands.end()) return; + + const auto it_b = brands.find(b); + if (it_b == brands.end()) return; + + const auto it_c = brands.find(c); + if (it_c == brands.end()) return; + // Only erase if ALL 3 were found + brands.erase(it_a); + brands.erase(it_b); + brands.erase(it_c); + + brands.emplace(result, 2); + }; // some edgecase handling for Hyper-V and VirtualPC since // they're very similar, and they're both from Microsoft (ew) @@ -11271,6 +11457,7 @@ struct VM { case EDID: return "EDID"; case CPU_HEURISTIC: return "CPU_HEURISTIC"; case CLOCK: return "CLOCK"; + case POST: return "POST"; // END OF TECHNIQUE LIST case DEFAULT: return "setting flag, error"; case ALL: return "setting flag, error"; @@ -11347,10 +11534,12 @@ struct VM { using table_t = std::map; - auto modify = [](table_t &table, const enum_flags flag, const u8 percent) -> void { - core::technique &tmp = table.at(flag); - table[flag].points = percent; - table[flag].run = tmp.run; + auto modify = [](table_t& table, const enum_flags flag, const u8 percent) noexcept -> void { + const auto it = table.find(flag); + + if (it != table.end()) { + it->second.points = percent; + } }; modify(core::technique_table, flag, percent); @@ -11508,11 +11697,11 @@ struct VM { #endif #if (VMA_CPP >= 17) - auto make_conclusion = [&](const std::string_view category) -> std::string { + auto make_conclusion = [&](std::string_view category) -> std::string { #else - auto make_conclusion = [&](const std::string &category) -> std::string { + auto make_conclusion = [&](const std::string& category) -> std::string { #endif - std::string addition = ""; + const char* addition = " a "; // this basically just fixes the grammatical syntax // by either having "a" or "an" before the VM brand @@ -11539,22 +11728,37 @@ struct VM { ) { addition = " an "; } - else { - addition = " a "; - } - + // this is basically just to remove the capital "U", // since it doesn't make sense to see "an Unknown" - if (brand_tmp == brands::NULL_BRAND) { - brand_tmp = "unknown"; - } + #if (VMA_CPP >= 17) + const std::string_view final_brand = (brand_tmp == brands::NULL_BRAND) ? "unknown" : std::string_view(brand_tmp); + #else + const char* final_brand = (brand_tmp == brands::NULL_BRAND) ? "unknown" : brand_tmp.c_str(); + #endif // Hyper-V artifacts are an exception due to how unique the circumstance is + const char* suffix = " VM"; if (brand_tmp == brands::HYPERV_ARTIFACT && percent_tmp != 100) { - return std::string(category) + addition + brand_tmp; - } else { - return std::string(category) + addition + brand_tmp + " VM"; + suffix = ""; + } + + std::string result; + #if (VMA_CPP >= 17) + result.reserve(category.length() + std::strlen(addition) + final_brand.length() + std::strlen(suffix)); + #else + result.reserve(category.length() + std::strlen(addition) + std::strlen(final_brand) + std::strlen(suffix)); + #endif + result.append(category); + result.append(addition); + result.append(final_brand); + result.append(suffix); + + if (brand_tmp == brands::NULL_BRAND) { + brand_tmp = "unknown"; } + + return result; }; if (core::is_enabled(flags, DYNAMIC)) { @@ -11805,10 +12009,11 @@ std::pair VM::core::technique_list[] = { #if (WINDOWS) std::make_pair(VM::TRAP, VM::core::technique(100, VM::trap)), std::make_pair(VM::ACPI_SIGNATURE, VM::core::technique(100, VM::acpi_signature)), - std::make_pair(VM::NVRAM, VM::core::technique(100, VM::nvram_vars)), + std::make_pair(VM::NVRAM, VM::core::technique(100, VM::nvram)), std::make_pair(VM::CLOCK, VM::core::technique(100, VM::clock)), std::make_pair(VM::POWER_CAPABILITIES, VM::core::technique(45, VM::power_capabilities)), std::make_pair(VM::CPU_HEURISTIC, VM::core::technique(90, VM::cpu_heuristic)), + std::make_pair(VM::POST, VM::core::technique(100, VM::post)), std::make_pair(VM::EDID, VM::core::technique(100, VM::edid)), std::make_pair(VM::BOOT_LOGO, VM::core::technique(100, VM::boot_logo)), std::make_pair(VM::GPU_CAPABILITIES, VM::core::technique(45, VM::gpu_capabilities)),