diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml index 0200d507..9d900b49 100644 --- a/.github/workflows/updater.yml +++ b/.github/workflows/updater.yml @@ -3,7 +3,7 @@ name: Python script to update documentations and header details on merge on: push: branches: - - main + - dev jobs: run-script: diff --git a/docs/documentation.md b/docs/documentation.md index 346afb9a..5587e81f 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -499,96 +499,95 @@ 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#L2092) | -| `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#L2110) | -| `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#L2184) | -| `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#L2215) | -| `VM::TIMER` | Check for timing anomalies in the system | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4475) | -| `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#L6696) | -| `VM::MAC` | Check if mac address starts with certain VM designated values | 🐧 | 20% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4878) | -| `VM::TEMPERATURE` | Check for device's temperature | 🐧 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5728) | -| `VM::SYSTEMD` | Check result from systemd-detect-virt tool | 🐧 | 35% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4759) | -| `VM::CVENDOR` | Check if the chassis vendor is a VM vendor | 🐧 | 65% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4783) | -| `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#L4808) | -| `VM::DOCKERENV` | Check if /.dockerenv or /.dockerinit file is present | 🐧 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4826) | -| `VM::DMIDECODE` | Check if dmidecode output matches a VM brand | 🐧 | 55% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4841) | -| `VM::DMESG` | Check if dmesg output matches a VM brand | 🐧 | 55% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4984) | -| `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#L5025) | -| `VM::DLL` | Check for VM-specific DLLs | 🪟 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6992) | -| `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#L6720) | -| `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#L7023) | -| `VM::POWER_CAPABILITIES` | Check what power states are enabled | 🪟 | 45% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7062) | -| `VM::PROCESSES` | Check for any VM processes that are active | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5739) | -| `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#L5035) | -| `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#L7122) | -| `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#L2243) | -| `VM::MAC_MEMSIZE` | Check if memory is too low for MacOS system | 🍏 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6753) | -| `VM::MAC_IOKIT` | Check MacOS' IO kit registry for VM-specific strings | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6786) | -| `VM::IOREG_GREP` | Check for VM-strings in ioreg commands for MacOS | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6883) | -| `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#L6940) | -| `VM::VPC_INVALID` | Check for official VPC method | 🪟 | 75% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7220) | -| `VM::SIDT` | Check for uncommon IDT virtual addresses | 🐧🪟 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5766) | -| `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#L7271) | -| `VM::SLDT` | Check for sldt instruction method | 🪟 | 50% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7339) | -| `VM::SMSW` | Check for SMSW assembly instruction technique | 🪟 | 50% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7394) | -| `VM::VMWARE_IOMEM` | Check for VMware string in /proc/iomem | 🐧 | 65% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5064) | -| `VM::VMWARE_IOPORTS` | Check for VMware string in /proc/ioports | 🐧 | 70% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5574) | -| `VM::VMWARE_SCSI` | Check for VMware string in /proc/scsi/scsi | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5373) | -| `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#L5392) | -| `VM::VMWARE_STR` | Check str assembly instruction method for VMware | 🪟 | 35% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7421) | -| `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#L7446) | -| `VM::MUTEX` | Check for mutex strings of VM brands | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7507) | -| `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#L2331) | -| `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#L3454) | -| `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#L3760) | -| `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#L7586) | -| `VM::CUCKOO_PIPE` | Check for Cuckoo specific piping mechanism | 🪟 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7642) | +| `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::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#L7698) | -| `VM::DEVICE_STRING` | Check if bogus device string would be accepted | 🪟 | 25% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7733) | -| `VM::BLUESTACKS_FOLDERS` | Check for the presence of BlueStacks-specific folders | 🐧 | 5% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5080) | -| `VM::CPUID_SIGNATURE` | Check for signatures in leaf 0x40000001 in CPUID | 🐧🪟🍏 | 95% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4422) | -| `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#L4451) | -| `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#L5161) | -| `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#L5190) | -| `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#L5218) | -| `VM::UML_CPU` | Check for the "UML" string in the CPU brand | 🐧 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5266) | -| `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#L5296) | -| `VM::VBOX_MODULE` | Check for a VBox kernel module | 🐧 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5350) | -| `VM::SYSINFO_PROC` | Check for potential VM info in /proc/sysinfo | 🐧 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5426) | -| `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#L5448) | -| `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#L5529) | -| `VM::PODMAN_FILE` | Check for podman file in /run/ | 🐧 | 5% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5559) | -| `VM::WSL_PROC` | Check for WSL or microsoft indications in /proc/ subdirectories | 🐧 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5591) | -| `VM::DRIVERS` | Check for VM-specific names for drivers | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7750) | -| `VM::DISK_SERIAL` | Check for serial numbers of virtual disks | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7848) | -| `VM::IVSHMEM` | Check for IVSHMEM device presence | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8067) | -| `VM::GPU_CAPABILITIES` | Check for GPU capabilities related to VMs | 🪟 | 45% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8166) | -| `VM::DEVICE_HANDLES` | Check for vm-specific devices | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8204) | -| `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#L5619) | -| `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#L8300) | -| `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#L8330) | -| `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#L5103) | -| `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#L8391) | -| `VM::FIRMWARE` | Check for VM signatures on all firmware tables | 🐧🪟 | 100% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5900) | -| `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#L5649) | -| `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#L8476) | -| `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#L5676) | -| `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#L6324) | -| `VM::ACPI_SIGNATURE` | Check for VM-specific ACPI device signatures | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8574) | -| `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#L8719) | -| `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#L8476) | -| `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#L8943) | -| `VM::DBVM` | Check if Dark Byte's VM is present | 🪟 | 150% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8990) | -| `VM::BOOT_LOGO` | Check boot logo for known VM images | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9108) | -| `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#L6967) | -| `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#L9200) | -| `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#L9369) | -| `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#L9616) | -| `VM::EDID` | Check for non-standard EDID configurations | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9627) | -| `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#L9764) | -| `VM::CLOCK` | Check the presence of system timers | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10255) | -| `VM::LBR` | Check if Last Branch Record MSRs are correctly virtualized | 🪟 | 95% | | | Currently investigating possible false flags with this | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10348) | +| `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) |
diff --git a/src/cli.cpp b/src/cli.cpp index fbce6532..973ddc22 100755 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -828,7 +828,6 @@ static void general() { checker(VM::EDID, "EDID"); checker(VM::CPU_HEURISTIC, "CPU heuristics"); checker(VM::CLOCK, "system timers"); - checker(VM::LBR, "LBR virtualization"); // 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 b66cd486..c2979d08 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 717 - * - struct for internal memoization => line 1160 - * - struct for internal utility functions => line 1290 - * - struct for internal core components => line 10552 - * - start of VM detection technique list => line 2087 - * - start of public VM detection functions => line 11045 - * - start of externally defined variables => line 12025 + * - 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 * * * ============================== EXAMPLE =================================== @@ -214,8 +214,8 @@ * the threshold becomes 300 instead of 150. */ - -#pragma once +#ifndef VMAWARE_HEADER +#define VMAWARE_HEADER #ifndef __VMAWARE_DEBUG__ #if defined(_DEBUG) /* MSVC Debug */ \ @@ -494,7 +494,7 @@ namespace brands { static constexpr const char* AZURE_HYPERV = "Microsoft Azure Hyper-V"; static constexpr const char* NANOVISOR = "Xbox NanoVisor (Hyper-V)"; static constexpr const char* SIMPLEVISOR = "SimpleVisor"; - static constexpr const char* HYPERV_ARTIFACT = "Hyper-V artifact (not an actual VM)"; + static constexpr const char* HYPERV_ARTIFACT = "Hyper-V artifact (host running Hyper-V)"; static constexpr const char* UML = "User-mode Linux"; static constexpr const char* POWERVM = "IBM PowerVM"; static constexpr const char* GCE = "Google Compute Engine (KVM)"; @@ -571,7 +571,6 @@ struct VM { EDID, CPU_HEURISTIC, CLOCK, - LBR, // Linux and Windows SIDT, @@ -1353,6 +1352,23 @@ struct VM { file.close(); return data; } + + [[nodiscard]] static bool exists(const char* path) { + #if (VMA_CPP >= 17) + return std::filesystem::exists(path); + #elif (VMA_CPP >= 11) + struct stat buffer; + return (stat(path, &buffer) == 0); + #endif + } + + static bool is_directory(const char* path) { + struct stat info; + if (stat(path, &info) != 0) { + return false; + } + return (info.st_mode & S_IFDIR); // check if directory + }; #endif // fetch the file but in binary form @@ -1377,24 +1393,6 @@ struct VM { return buffer; } -#if (LINUX) - [[nodiscard]] static bool exists(const char* path) { - #if (VMA_CPP >= 17) - return std::filesystem::exists(path); - #elif (VMA_CPP >= 11) - struct stat buffer; - return (stat(path, &buffer) == 0); - #endif - } - - static bool is_directory(const char* path) { - struct stat info; - if (stat(path, &info) != 0) { - return false; - } - return (info.st_mode & S_IFDIR); // check if directory - }; -#endif // wrapper for std::make_unique because it's not available for C++11 template @@ -1640,26 +1638,6 @@ struct VM { } - [[nodiscard]] static std::string get_hostname() { - #if (WINDOWS) - char ComputerName[MAX_COMPUTERNAME_LENGTH + 1]; - DWORD cbComputerName = sizeof(ComputerName); - - if (GetComputerNameA(ComputerName, &cbComputerName)) { - return std::string(ComputerName); - } - #elif (LINUX) - char hostname[HOST_NAME_MAX]; - - if (gethostname(hostname, sizeof(hostname)) == 0) { - return std::string(hostname); - } - #endif - - return std::string(); - } - - [[nodiscard]] static bool is_running_under_translator() { #if (WINDOWS && _WIN32_WINNT >= _WIN32_WINNT_WIN10) const HANDLE hCurrentProcess = reinterpret_cast(-1LL); @@ -1778,7 +1756,7 @@ struct VM { return eax; }; - hyperx_state state; + hyperx_state state = HYPERV_UNKNOWN; if (!is_root_partition()) { if (eax() == 11 && is_hyperv_present()) { @@ -1793,19 +1771,22 @@ struct VM { } } else { - // normally eax 12 const std::string brand_str = cpu::cpu_manufacturer(0x40000100); - + if (util::find(brand_str, "KVM")) { debug("HYPER_X: Detected Hyper-V enlightenments"); core::add(brands::QEMU_KVM_HYPERV); state = HYPERV_ENLIGHTENMENT; } - else { + // vendors can sometimes advertise 0x4000xxxx leaves for compatibility/enlightenment purposes without running a hypervisor + else if (eax() == 12) { // Windows machine running under Hyper-V type 1 debug("HYPER_X: Detected Hyper-V host machine"); core::add(brands::HYPERV_ARTIFACT); state = HYPERV_ARTIFACT_VM; + } else { + debug("HYPER_X: Hyper-V is not active"); + state = HYPERV_UNKNOWN; } } @@ -1814,6 +1795,210 @@ struct VM { return state; #endif } + + // 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 { + // it does 8 rounds of CRC32-C bit reflection recursively + static constexpr u32 crc32_bits(u32 crc, int bits) { + return (bits == 0) ? crc : + crc32_bits((crc >> 1) ^ ((crc & 1) ? 0x82F63B78u : 0), bits - 1); + } + + // over string + static constexpr u32 crc32_str(const char* s, u32 crc) { + return (*s == '\0') ? crc : + crc32_str(s + 1, crc32_bits(crc ^ static_cast(*s), 8)); + } + + static constexpr u32 get(const char* s) { + return crc32_str(s, 0); + } + }; + + // this forces the compiler to calculate the hash when initializing the array while staying C++11 compatible + struct thread_entry { + u32 hash; + u32 threads; + constexpr thread_entry(const char* m, u32 t) : hash(ConstexprHash::get(m)), threads(t) {} + }; + + enum class CpuType { + 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) { + // to save a few cycles + struct hasher { + static u32 crc32_sw(u32 crc, char data) { + crc ^= static_cast(data); + for (int i = 0; i < 8; ++i) + crc = (crc >> 1) ^ ((crc & 1) ? 0x82F63B78u : 0); + return crc; + } + + // For strings shorter than 16-32 bytes, the overhead of setting up the _mm_crc32_u64 (or 32) loop, then checking length, handling the tail bytes, and finally handling alignment, + // will always make it slower or equal to a simple unrolled u8 loop, and not every cpu model fits in u32/u64 + #if (CLANG || GCC) + __attribute__((__target__("crc32"))) + #endif + static u32 crc32_hw(u32 crc, char data) { + + return _mm_crc32_u8(crc, static_cast(data)); + } + + using hashfc = u32(*)(u32, char); + + static hashfc get() { + // yes, vmaware runs on dinosaur cpus without sse4.2 pretty often + i32 regs[4]; + cpu::cpuid(regs, 1); + const bool has_sse42 = (regs[2] & (1 << 20)) != 0; + + return has_sse42 ? crc32_hw : crc32_sw; + } + }; + + std::string model_string; + const char* debug_tag = ""; + + if (type == CpuType::AMD) { + if (!cpu::is_amd()) { + return false; + } + model_string = cpu::get_brand(); + debug_tag = "AMD_THREAD_MISMATCH"; + } + else { + if (!cpu::is_intel()) { + return false; + } + + const cpu::model_struct model = cpu::get_model(); + + if (!model.found) { + return false; + } + + if (type == CpuType::INTEL_I) { + if (!model.is_i_series) { + return false; + } + debug_tag = "INTEL_THREAD_MISMATCH"; + } + else { + if (!model.is_xeon) { + return false; + } + debug_tag = "XEON_THREAD_MISMATCH"; + } + model_string = model.string; + } + + if (model_string.empty()) return false; + + debug(debug_tag, ": CPU model = ", model_string); + + const char* str = model_string.c_str(); + u32 expected_threads = 0; + bool found = false; + size_t best_len = 0; + + // manual collision fix for Z1 Extreme (16) vs Z1 (12) + // this is a special runtime check because "z1" is a substring of "z1 extreme" tokens + // and both might be hashed. VMAware should prioritize 'extreme' if found + u32 z_series_threads = 0; + + const auto hash_func = hasher::get(); + + for (size_t i = 0; str[i] != '\0'; ) { + char c = str[i]; + if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) { + i++; + continue; + } + + u32 current_hash = 0; + size_t current_len = 0; + size_t j = i; + + while (true) { + char k = str[j]; + const bool is_valid = (k >= '0' && k <= '9') || + (k >= 'A' && k <= 'Z') || + (k >= 'a' && k <= 'z') || + (k == '-'); // models have hyphen + if (!is_valid) break; + + if (current_len >= max_model_len) { + while (str[j] != '\0' && str[j] != ' ') j++; // fast forward to space/null + break; + } + + /* + models are usually 8 or more bytes long, i.e. i9-10900K + so imagine we want to use u64, you hash the first 8 bytes i9-10900 + but then you are left with K. You have to handle the tail + fetching 8 bytes would include the characters after the token, corrupting the hash + so a byte-by-byte loop is the most optimal choice here + */ + + // convert to lowercase on-the-fly to match compile-time keys + if (type == CpuType::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 + current_hash = hash_func(current_hash, k); + current_len++; + j++; + + // only verify match if the token has ended (next char is not alphanumeric) + const char next = str[j]; + const bool next_is_alnum = (next >= '0' && next <= '9') || + (next >= 'A' && next <= 'Z') || + (next >= 'a' && next <= 'z'); + + 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; } + + // since it's a contiguous block of integers in .rodata/.rdata, this is extremely fast + for (size_t idx = 0; idx < db_size; ++idx) { + if (db[idx].hash == current_hash) { + if (current_len > best_len) { + best_len = current_len; + expected_threads = db[idx].threads; + found = true; + } + // since hashing implies uniqueness in this dataset, you might say we could break here, + // but we continue to ensure we find the longest substring match if overlaps exist, + // so like it finds both "i9-11900" and "i9-11900K" i.e. + } + } + } + } + i = j; + } + + // Z1 Extreme fix + if (type == CpuType::AMD && z_series_threads != 0 && expected_threads == 12) { + expected_threads = z_series_threads; + } + + if (found) { + const u32 actual = memo::threadcount::fetch(); + if (actual != expected_threads) { + debug(debug_tag, ": Expected threads -> ", expected_threads); + return true; + } + } + + return false; + } #if (WINDOWS) // retrieves the addresses of specified functions from a loaded module using the export directory, manual implementation of GetProcAddress @@ -2081,7 +2266,7 @@ struct VM { const HMODULE h = GetModuleHandleW(L"ntdll.dll"); if (h) cachedNtdll = h; return h; - } + } #endif }; @@ -2093,13 +2278,13 @@ struct VM { */ [[nodiscard]] static bool vmid() { #if (!x86) - return false; + return false; #else - return ( - cpu::vmid_template(0) || - cpu::vmid_template(cpu::leaf::hypervisor) || // 0x40000000 - cpu::vmid_template(cpu::leaf::hypervisor + 0x100) // 0x40000100 - ); + return ( + cpu::vmid_template(0) || + cpu::vmid_template(cpu::leaf::hypervisor) || // 0x40000000 + cpu::vmid_template(cpu::leaf::hypervisor + 0x100) // 0x40000100 + ); #endif } @@ -2334,53 +2519,10 @@ struct VM { #if (!x86) return false; #else - if (!cpu::is_intel()) { - return false; - } - - const cpu::model_struct model = cpu::get_model(); - - if (!model.found) { - return false; - } - - if (!model.is_i_series) { - return false; - } - - debug("INTEL_THREAD_MISMATCH: CPU model = ", model.string); - - // 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 { - // it does 8 rounds of CRC32-C bit reflection recursively - static constexpr u32 crc32_bits(u32 crc, int bits) { - return (bits == 0) ? crc : - crc32_bits((crc >> 1) ^ ((crc & 1) ? 0x82F63B78u : 0), bits - 1); - } - - // over string - static constexpr u32 crc32_str(const char* s, u32 crc) { - return (*s == '\0') ? crc : - crc32_str(s + 1, crc32_bits(crc ^ static_cast(*s), 8)); - } - - static constexpr u32 get(const char* s) { - return crc32_str(s, 0); - } - }; - - // this forces the compiler to calculate the hash when initializing the array while staying C++11 compatible - struct Entry { - u32 hash; - u32 threads; - constexpr Entry(const char* m, u32 t) : hash(ConstexprHash::get(m)), threads(t) {} - }; - // umap is not an option because it cannot be constexpr // constexpr is respected here even in c++ 11 and static solves stack overflow // c arrays have less construction overhead than std::array - static constexpr Entry thread_database[] = { + static constexpr util::thread_entry thread_database[] = { // i3 series { "i3-1000G1", 4 }, { "i3-1000G4", 4 }, @@ -3337,112 +3479,9 @@ struct VM { { "i9-9990XE", 28 } }; - // to save a few cycles static constexpr size_t MAX_INTEL_MODEL_LEN = 16; - struct hasher { - static u32 crc32_sw(u32 crc, char data) { - crc ^= static_cast(data); - for (int i = 0; i < 8; ++i) - crc = (crc >> 1) ^ ((crc & 1) ? 0x82F63B78u : 0); - return crc; - } - - #if (CLANG || GCC) - __attribute__((__target__("crc32"))) - #endif - static u32 crc32_hw(u32 crc, char data) { - return _mm_crc32_u8(crc, static_cast(data)); - } - - using hashfc = u32(*)(u32, char); - - static hashfc get() { - // yes, vmaware runs on dinosaur cpus without sse4.2 pretty often - i32 regs[4]; - cpu::cpuid(regs, 1); - const bool has_sse42 = (regs[2] & (1 << 20)) != 0; - - return has_sse42 ? crc32_hw : crc32_sw; - } - }; - - const char* str = model.string.c_str(); - u32 expected_threads = 0; - bool found = false; - size_t best_len = 0; - - const auto hash_func = hasher::get(); - - for (size_t i = 0; str[i] != '\0'; ) { - const char c = str[i]; - if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) { - i++; - continue; - } - - u32 current_hash = 0; - size_t current_len = 0; - size_t j = i; - - while (true) { - const char k = str[j]; - const bool is_valid = (k >= '0' && k <= '9') || - (k >= 'A' && k <= 'Z') || - (k >= 'a' && k <= 'z') || - (k == '-'); // models have hyphen - if (!is_valid) break; - - if (current_len >= MAX_INTEL_MODEL_LEN) { - while (str[j] != '\0' && str[j] != ' ') j++; // fast forward to space/null - break; - } - - /* - models are usually 8 or more bytes long, i.e. i9-10900K - so imagine we want to use u64, you hash the first 8 bytes i9-10900 - but then you are left with K. You have to handle the tail - fetching 8 bytes would include the characters after the token, corrupting the hash - so a byte-by-byte loop is the most optimal choice here - */ - - // 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 - current_hash = hash_func(current_hash, k); - current_len++; - j++; - - // only verify match if the token has ended (next char is not alphanumeric) - const char next = str[j]; - const bool next_is_alnum = (next >= '0' && next <= '9') || - (next >= 'A' && next <= 'Z') || - (next >= 'a' && next <= 'z'); - - if (!next_is_alnum) { - // since it's a contiguous block of integers in .rodata/.rdata, this is extremely fast - for (const auto& entry : thread_database) { - if (entry.hash == current_hash) { - if (current_len > best_len) { - best_len = current_len; - expected_threads = entry.threads; - found = true; - } - // since hashing implies uniqueness in this dataset, you might say we could break here, - // but we continue to ensure we find the longest substring match if overlaps exist, - // so like it finds both "i9-11900" and "i9-11900K" i.e. - } - } - } - } - i = j; - } - - if (found) { - const u32 actual = memo::threadcount::fetch(); - return actual != expected_threads; - } - - return false; + return util::verify_thread_count(thread_database, sizeof(thread_database) / sizeof(util::thread_entry), MAX_INTEL_MODEL_LEN, util::CpuType::INTEL_I); #endif } @@ -3457,53 +3496,10 @@ struct VM { #if (!x86) return false; #else - if (!cpu::is_intel()) { - return false; - } - - const cpu::model_struct model = cpu::get_model(); - - if (!model.found) { - return false; - } - - if (!model.is_xeon) { - return false; - } - - debug("XEON_THREAD_MISMATCH: CPU model = ", model.string); - - // 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 { - // it does 8 rounds of CRC32-C bit reflection recursively - static constexpr u32 crc32_bits(u32 crc, int bits) { - return (bits == 0) ? crc : - crc32_bits((crc >> 1) ^ ((crc & 1) ? 0x82F63B78u : 0), bits - 1); - } - - // over string - static constexpr u32 crc32_str(const char* s, u32 crc) { - return (*s == '\0') ? crc : - crc32_str(s + 1, crc32_bits(crc ^ static_cast(*s), 8)); - } - - static constexpr u32 get(const char* s) { - return crc32_str(s, 0); - } - }; - - // this forces the compiler to calculate the hash when initializing the array while staying C++11 compatible - struct Entry { - u32 hash; - u32 threads; - constexpr Entry(const char* m, u32 t) : hash(ConstexprHash::get(m)), threads(t) {} - }; - // umap is not an option because it cannot be constexpr // constexpr is respected here even in c++ 11 and static solves stack overflow // c arrays have less construction overhead than std::array - static constexpr Entry thread_database[] = { + static constexpr util::thread_entry thread_database[] = { // Xeon D { "D-1518", 8 }, { "D-1520", 8 }, @@ -3639,116 +3635,9 @@ struct VM { { "w9-3595X", 120 } }; - // to save a few cycles static constexpr size_t MAX_XEON_MODEL_LEN = 16; - struct hasher { - static u32 crc32_sw(u32 crc, char data) { - crc ^= static_cast(data); - for (int i = 0; i < 8; ++i) - crc = (crc >> 1) ^ ((crc & 1) ? 0x82F63B78u : 0); - return crc; - } - - #if (CLANG || GCC) - __attribute__((__target__("crc32"))) - #endif - static u32 crc32_hw(u32 crc, char data) { - return _mm_crc32_u8(crc, static_cast(data)); - } - - using hashfc = u32(*)(u32, char); - - static hashfc get() { - // yes, vmaware runs on dinosaur cpus without sse4.2 pretty often - i32 regs[4]; - cpu::cpuid(regs, 1); - const bool has_sse42 = (regs[2] & (1 << 20)) != 0; - - return has_sse42 ? crc32_hw : crc32_sw; - } - }; - - const std::string& cpu_full_name = model.string; - if (cpu_full_name.empty()) return false; - - const char* str = cpu_full_name.c_str(); - u32 expected_threads = 0; - bool found = false; - size_t best_len = 0; - - const auto hash_func = hasher::get(); - - for (size_t i = 0; str[i] != '\0'; ) { - const char c = str[i]; - if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) { - i++; - continue; - } - - u32 current_hash = 0; - size_t current_len = 0; - size_t j = i; - - while (true) { - const char k = str[j]; - const bool is_valid = (k >= '0' && k <= '9') || - (k >= 'A' && k <= 'Z') || - (k >= 'a' && k <= 'z') || - (k == '-'); - if (!is_valid) break; - - if (current_len >= MAX_XEON_MODEL_LEN) { - while (str[j] != '\0' && str[j] != ' ') j++; // fast forward to space/null - break; - } - - /* - models are usually 8 or more bytes long, i.e. i9-10900K - so imagine we want to use u64, you hash the first 8 bytes i9-10900 - but then you are left with K. You have to handle the tail - fetching 8 bytes would include the characters after the token, corrupting the hash - so a byte-by-byte loop is the most optimal choice here - */ - - // 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 - current_hash = hash_func(current_hash, k); - current_len++; - j++; - - // only verify match if the token has ended (next char is not alphanumeric) - const char next = str[j]; - const bool next_is_alnum = (next >= '0' && next <= '9') || - (next >= 'A' && next <= 'Z') || - (next >= 'a' && next <= 'z'); - - if (!next_is_alnum) { - // since it's a contiguous block of integers in .rodata/.rdata, this is extremely fast - for (const auto& entry : thread_database) { - if (entry.hash == current_hash) { - if (current_len > best_len) { - best_len = current_len; - expected_threads = entry.threads; - found = true; - } - // since hashing implies uniqueness in this dataset, you might say we could break here, - // but we continue to ensure we find the longest substring match if overlaps exist, - // so like it finds both "i9-11900" and "i9-11900K" i.e. - } - } - } - } - i = j; - } - - if (found) { - const u32 actual = memo::threadcount::fetch(); - debug("XEON_THREAD_MISMATCH: Expected threads -> ", expected_threads); - return actual != expected_threads; - } - - return false; + return util::verify_thread_count(thread_database, sizeof(thread_database) / sizeof(util::thread_entry), MAX_XEON_MODEL_LEN, util::CpuType::INTEL_XEON); #endif } @@ -3762,38 +3651,10 @@ struct VM { [[nodiscard]] static bool amd_thread_mismatch() { #if (!x86) return false; - #else - if (!cpu::is_amd()) { - return false; - } - - std::string model_str = cpu::get_brand(); - - static constexpr size_t MAX_AMD_TOKEN_LEN = 24; // "threadripper" is long - - struct ConstexprHash { - static constexpr u32 crc32_bits(u32 crc, int bits) { - return (bits == 0) ? crc : - crc32_bits((crc >> 1) ^ ((crc & 1) ? 0x82F63B78u : 0), bits - 1); - } - static constexpr u32 crc32_str(const char* s, u32 crc) { - return (*s == '\0') ? crc : - crc32_str(s + 1, crc32_bits(crc ^ static_cast(*s), 8)); - } - static constexpr u32 get(const char* s) { - return crc32_str(s, 0); - } - }; - - struct Entry { - u32 hash; - u32 threads; - constexpr Entry(const char* m, u32 t) : hash(ConstexprHash::get(m)), threads(t) {} - }; - + #else // Database is reduced to identifying suffixes (last token of the original strings) // for example handles "ryzen 5 3600" by matching "3600", which is unique in context - static constexpr Entry db_entries[] = { + static constexpr util::thread_entry thread_database[] = { // 3015/3020 { "3015ce", 4 }, { "3015e", 4 }, @@ -4304,112 +4165,9 @@ struct VM { { "z2", 16 } }; - struct hasher { - static u32 crc32_sw(u32 crc, char data) { - crc ^= static_cast(data); - for (int i = 0; i < 8; ++i) - crc = (crc >> 1) ^ ((crc & 1) ? 0x82F63B78u : 0); - return crc; - } - - #if (CLANG || GCC) - __attribute__((__target__("crc32"))) - #endif - static u32 crc32_hw(u32 crc, char data) { - return _mm_crc32_u8(crc, static_cast(data)); - } - - using hashfc = u32(*)(u32, char); - - static hashfc get() { - i32 regs[4]; - cpu::cpuid(regs, 1); - const bool has_sse42 = (regs[2] & (1 << 20)) != 0; - return has_sse42 ? crc32_hw : crc32_sw; - } - }; - - debug("AMD_THREAD_MISMATCH: CPU model = ", model_str); - - const char* str = model_str.c_str(); - u32 expected_threads = 0; - bool found = false; - size_t best_len = 0; - - const auto hash_func = hasher::get(); - - // manual collision fix for Z1 Extreme (16) vs Z1 (12) - // this is a special runtime check because "z1" is a substring of "z1 extreme" tokens - // and both might be hashed. VMAware should prioritize 'extreme' if found - u32 z_series_threads = 0; + static constexpr size_t MAX_AMD_MODEL_LEN = 24; // "threadripper" is long - for (size_t i = 0; str[i] != '\0'; ) { - char c = str[i]; - if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) { - i++; - continue; - } - - u32 current_hash = 0; - size_t current_len = 0; - size_t j = i; - - while (true) { - char k = str[j]; - const bool is_valid = (k >= '0' && k <= '9') || - (k >= 'A' && k <= 'Z') || - (k >= 'a' && k <= 'z') || - (k == '-'); - if (!is_valid) break; - - if (current_len >= MAX_AMD_TOKEN_LEN) { - while (str[j] != '\0' && str[j] != ' ') j++; - break; - } - - // convert to lowercase on-the-fly to match compile-time keys - if (k >= 'A' && k <= 'Z') k += 32; - - current_hash = hash_func(current_hash, k); - current_len++; - j++; - - // boundary check - const char next = str[j]; - const bool next_is_alnum = (next >= '0' && next <= '9') || - (next >= 'A' && next <= 'Z') || - (next >= 'a' && next <= 'z'); - - if (!next_is_alnum) { - // Check specific Z1 Extreme token - // Hash for "extreme" (CRC32-C) is 0x3D09D5B4 - if (current_hash == 0x3D09D5B4) { z_series_threads = 16; } - - for (const auto& entry : db_entries) { - if (entry.hash == current_hash) { - if (current_len > best_len) { - best_len = current_len; - expected_threads = entry.threads; - found = true; - } - } - } - } - } - i = j; - } - - // Z1 Extreme fix - if (z_series_threads != 0 && expected_threads == 12) { - expected_threads = z_series_threads; - } - - if (found) { - const u32 actual = memo::threadcount::fetch(); - return actual != expected_threads; - } - - return false; + return util::verify_thread_count(thread_database, sizeof(thread_database) / sizeof(util::thread_entry), MAX_AMD_MODEL_LEN, util::CpuType::INTEL_XEON); #endif } @@ -4482,7 +4240,7 @@ struct VM { } u16 cycleThreshold = 1200; if (util::hyper_x() == HYPERV_ARTIFACT_VM) { - cycleThreshold = 15000; // if we're running under Hyper-V, attempt to detect nested virtualization only + cycleThreshold = 8000; // if we're running under Hyper-V, attempt to detect nested virtualization only } #if (WINDOWS) @@ -5865,8 +5623,29 @@ struct VM { * @category Windows, Linux * @implements VM::HYPERV_HOSTNAME */ - static bool hyperv_hostname() { - const std::string hostname = util::get_hostname(); + [[nodiscard]] static bool hyperv_hostname() { + std::string hostname; + + #if (WINDOWS) + char buf[MAX_COMPUTERNAME_LENGTH + 1]; + DWORD len = sizeof(buf); + + if (GetComputerNameA(buf, &len)) { + hostname.assign(buf, len); + } + else { + return false; + } + #elif (LINUX) + char buf[HOST_NAME_MAX]; + + if (gethostname(buf, sizeof(buf)) == 0) { + hostname = buf; + } + else { + return false; + } + #endif const char* prefix = "runnervm"; const std::size_t prefix_len = std::strlen(prefix); @@ -5882,8 +5661,7 @@ struct VM { } for (std::size_t i = prefix_len; i < hostname.size(); ++i) { - unsigned char c = static_cast(hostname[i]); - if (!std::isalnum(c)) { + if (!std::isalnum(static_cast(hostname[i]))) { return false; } } @@ -9627,76 +9405,101 @@ struct VM { * @implements VM::EDID */ [[nodiscard]] static bool edid() { - auto decodeManufacturerFast = [](const BYTE* edid) __declspec(noinline) -> std::array { - // edid[8..9] big-endian word - const uint16_t word = static_cast((edid[8] << 8) | edid[9]); - const uint8_t c1 = static_cast((word >> 10) & 0x1F); - const uint8_t c2 = static_cast((word >> 5) & 0x1F); - const uint8_t c3 = static_cast((word >> 0) & 0x1F); - std::array out{ {'?','?','?','\0'} }; - if (c1 >= 1 && c1 <= 26) out[0] = static_cast('A' + c1 - 1); - if (c2 >= 1 && c2 <= 26) out[1] = static_cast('A' + c2 - 1); - if (c3 >= 1 && c3 <= 26) out[2] = static_cast('A' + c3 - 1); - return out; + auto decodeManufacturer = [](const BYTE* edid, char out[4]) { + const u16 word = static_cast((edid[8] << 8) | edid[9]); + const u8 c1 = static_cast((word >> 10) & 0x1F); + const u8 c2 = static_cast((word >> 5) & 0x1F); + const u8 c3 = static_cast((word >> 0) & 0x1F); + 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 isThreeUpperAlphaFast = [](const std::array& m) -> bool { + auto isThreeUpperAlpha = [](const char m[4]) -> bool { return (m[0] >= 'A' && m[0] <= 'Z') && (m[1] >= 'A' && m[1] <= 'Z') && (m[2] >= 'A' && m[2] <= 'Z'); }; - auto descHasUpperPrefixMonitorFast = [](const char* desc) -> bool { - if (!desc || desc[0] == '\0') return false; - // Two tails to search: " Monitor" and " Display" (leading space) - const char* tails[] = { " Monitor", " Display" }; - for (const char* tail : tails) { - const char* p = strstr(desc, tail); - while (p) { - // ensure not at position 0 - if (p != desc) { - // walk backwards counting uppercase letters - const char* start = p; - size_t len = 0; - while (start > desc) { - --start; - unsigned char uc = static_cast(*start); - if (uc >= 'A' && uc <= 'Z') { - ++len; - if (len > 8) break; - } - else break; + auto edidChecksumValid = [](const BYTE* edid, size_t len) -> bool { + if (len < 128) return false; + u8 sum = 0; + for (size_t i = 0; i < 128; ++i) sum = static_cast(sum + edid[i]); + 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; } - if (len >= 4 && len <= 8) { - // verify all are uppercase (we already did while walking) - return true; + 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; } - p = strstr(p + 1, tail); } } + out[0] = '\0'; return false; }; - auto getDevicePropertyA = [](HDEVINFO devInfo, SP_DEVINFO_DATA& devData, DWORD propId, std::string& out) -> bool { - char small[512] = {}; - DWORD needed = 0; - if (SetupDiGetDeviceRegistryPropertyA(devInfo, &devData, propId, nullptr, reinterpret_cast(small), sizeof(small), &needed)) { - out.assign(small); - return true; - } - const DWORD err = GetLastError(); - if (err == ERROR_INSUFFICIENT_BUFFER && needed > 0 && needed < 65536) { - std::vector big(needed + 1); - if (SetupDiGetDeviceRegistryPropertyA(devInfo, &devData, propId, nullptr, reinterpret_cast(big.data()), static_cast(big.size()), &needed)) { - big[big.size() - 1] = '\0'; - out.assign(big.data()); + 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)); + }; + + auto getDevicePropertyA = [](HDEVINFO devInfo, SP_DEVINFO_DATA& devData, DWORD propId, + char* outBuf, DWORD outBufSize) -> bool { + DWORD needed = 0; + if (SetupDiGetDeviceRegistryPropertyA(devInfo, &devData, propId, nullptr, + reinterpret_cast(outBuf), outBufSize, &needed)) { + outBuf[outBufSize - 1] = '\0'; return true; } - } - out.clear(); - return false; - }; + const DWORD err = GetLastError(); + if (err == ERROR_INSUFFICIENT_BUFFER && needed > 0 && needed < 65536) { + HLOCAL h = LocalAlloc(LMEM_FIXED, static_cast(needed) + 1); + if (!h) return false; + if (SetupDiGetDeviceRegistryPropertyA(devInfo, &devData, propId, nullptr, + reinterpret_cast(h), needed, &needed)) { + DWORD toCopy = (needed < outBufSize - 1) ? needed : outBufSize - 1; + memcpy(outBuf, h, toCopy); + outBuf[toCopy] = '\0'; + LocalFree(h); + return true; + } + LocalFree(h); + } + outBuf[0] = '\0'; + return false; + }; const HDEVINFO devInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_MONITOR, nullptr, nullptr, DIGCF_PRESENT); if (devInfo == INVALID_HANDLE_VALUE) return false; @@ -9704,47 +9507,86 @@ struct VM { SP_DEVINFO_DATA devData{}; devData.cbSize = sizeof(devData); + const int threshold = 3; + for (DWORD index = 0; SetupDiEnumDeviceInfo(devInfo, index, &devData); ++index) { - // open registry key for device const HKEY hDevKey = SetupDiOpenDevRegKey(devInfo, &devData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); - if (hDevKey == INVALID_HANDLE_VALUE) continue; + if (hDevKey == INVALID_HANDLE_VALUE) { + devData = {}; + devData.cbSize = sizeof(devData); + continue; + } - BYTE buffer[2048]; - DWORD bufSize = static_cast(sizeof(buffer)); - const LONG rc = RegQueryValueExA(hDevKey, "EDID", nullptr, nullptr, buffer, &bufSize); + BYTE edid_stack[256]; + DWORD bufSize = static_cast(sizeof(edid_stack)); + LONG rc = RegQueryValueExA(hDevKey, "EDID", nullptr, nullptr, edid_stack, &bufSize); RegCloseKey(hDevKey); - if (rc != ERROR_SUCCESS || bufSize < 128) continue; - const BYTE* edid = buffer; - // standard header - if (!(edid[0] == 0x00 && edid[1] == 0xFF && edid[2] == 0xFF && edid[3] == 0xFF && - edid[4] == 0xFF && edid[5] == 0xFF && edid[6] == 0xFF && edid[7] == 0x00)) { + BYTE* edid = nullptr; + bool used_heap = false; + BYTE* heap_buf = nullptr; + if (rc == ERROR_SUCCESS && bufSize >= 128) { + edid = edid_stack; + } + else if (rc == ERROR_MORE_DATA) { + if (bufSize > 0 && bufSize < 65536) { + heap_buf = static_cast(LocalAlloc(LMEM_FIXED, bufSize)); + if (heap_buf) { + DWORD bufSize2 = bufSize; + const HKEY hDevKey2 = SetupDiOpenDevRegKey(devInfo, &devData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); + if (hDevKey2 != INVALID_HANDLE_VALUE) { + if (RegQueryValueExA(hDevKey2, "EDID", nullptr, nullptr, heap_buf, &bufSize2) == ERROR_SUCCESS && bufSize2 >= 128) { + edid = heap_buf; + used_heap = true; + bufSize = bufSize2; + } + RegCloseKey(hDevKey2); + } + if (!edid) { LocalFree(heap_buf); heap_buf = nullptr; } + } + } + } + + if (!edid) { + devData = {}; + devData.cbSize = sizeof(devData); continue; } - const uint8_t yearOffset = edid[0x11]; // 1990 + yearOffset - // those don't need device properties - const auto manufacturer = decodeManufacturerFast(edid); - const bool vendor_nonstandard = !isThreeUpperAlphaFast(manufacturer); - const bool year_in_range = (yearOffset >= 25 && yearOffset <= 35); // 2015..2025 + // header check + if (!(edid[0] == 0x00 && edid[1] == 0xFF && edid[2] == 0xFF && edid[3] == 0xFF + && edid[4] == 0xFF && edid[5] == 0xFF && edid[6] == 0xFF && edid[7] == 0x00)) { + if (used_heap) LocalFree(heap_buf); + devData = {}; + devData.cbSize = sizeof(devData); + continue; + } - if (!year_in_range) continue; + int score = 0; - if (vendor_nonstandard) { - SetupDiDestroyDeviceInfoList(devInfo); - return true; - } + if (!edidChecksumValid(edid, bufSize)) score += 1; + + char manu[4]; + decodeManufacturer(edid, manu); + if (!isThreeUpperAlpha(manu)) score += 1; - std::string friendly, devdesc; - // query Friendly name first cuz more likely to be present - getDevicePropertyA(devInfo, devData, SPDRP_FRIENDLYNAME, friendly); - if (friendly.empty()) getDevicePropertyA(devInfo, devData, SPDRP_DEVICEDESC, devdesc); + 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)); + if (product == 0) score += 1; + if (serial == 0) score += 1; - const char* descriptor = nullptr; - if (!friendly.empty()) descriptor = friendly.c_str(); - else if (!devdesc.empty()) descriptor = devdesc.c_str(); + char monname[32]; + bool hasName = extractMonitorName(edid, bufSize, monname); + if (!hasName) score += 1; // no way you dont have a EDID monitor name - if (descriptor && descHasUpperPrefixMonitorFast(descriptor)) { + 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)); + if (!haveFriendly && !haveDevDesc) score += 1; + + if (used_heap) LocalFree(heap_buf); + + if (score >= threshold) { SetupDiDestroyDeviceInfoList(devInfo); return true; } @@ -10340,205 +10182,6 @@ struct VM { SetupDiDestroyDeviceInfoList(devs); return !found; } - - - /** - * @brief Check if Last Branch Record MSRs are correctly virtualized - * @category Windows - * @implements VM::LBR - * @note Currently investigating possible false flags with this - */ - [[nodiscard]] static bool lbr() { - #if (x86) - const HMODULE ntdll = util::get_ntdll(); - if (!ntdll) return false; - - const char* names[] = { - "NtAllocateVirtualMemory", - "NtFreeVirtualMemory", - "NtFlushInstructionCache", - "RtlAddVectoredExceptionHandler", - "RtlRemoveVectoredExceptionHandler", - "NtCreateThreadEx", - "NtGetContextThread", - "NtSetContextThread", - "NtResumeThread", - "NtWaitForSingleObject", - "NtClose", - "NtProtectVirtualMemory" - }; - void* funcs[ARRAYSIZE(names)] = {}; - util::get_function_address(ntdll, names, funcs, ARRAYSIZE(names)); - - using NtAllocateVirtualMemory_t = NTSTATUS(__stdcall*)(HANDLE, PVOID*, ULONG_PTR, PSIZE_T, ULONG, ULONG); - using NtFreeVirtualMemory_t = NTSTATUS(__stdcall*)(HANDLE, PVOID*, PSIZE_T, ULONG); - using NtFlushInstructionCache_t = NTSTATUS(__stdcall*)(HANDLE, PVOID, SIZE_T); - using RtlAddVectoredExceptionHandler_t = PVOID(__stdcall*)(ULONG, PVECTORED_EXCEPTION_HANDLER); - using RtlRemoveVectoredExceptionHandler_t = ULONG(__stdcall*)(PVOID); - using NtCreateThreadEx_t = NTSTATUS(__stdcall*)(PHANDLE, ACCESS_MASK, PVOID, HANDLE, PVOID, PVOID, BOOLEAN, ULONG_PTR, SIZE_T, SIZE_T, PVOID); - using NtGetContextThread_t = NTSTATUS(__stdcall*)(HANDLE, PCONTEXT); - using NtSetContextThread_t = NTSTATUS(__stdcall*)(HANDLE, PCONTEXT); - using NtResumeThread_t = NTSTATUS(__stdcall*)(HANDLE, PULONG); - using NtWaitForSingleObject_t = NTSTATUS(__stdcall*)(HANDLE, BOOLEAN, PLARGE_INTEGER); - using NtClose_t = NTSTATUS(__stdcall*)(HANDLE); - using NtProtectVirtualMemory_t = NTSTATUS(__stdcall*)(HANDLE, PVOID*, PSIZE_T, ULONG, PULONG); - - const auto pNtAllocateVirtualMemory = reinterpret_cast(funcs[0]); - const auto pNtFreeVirtualMemory = reinterpret_cast(funcs[1]); - const auto pNtFlushInstructionCache = reinterpret_cast(funcs[2]); - const auto pRtlAddVectoredExceptionHandler = reinterpret_cast(funcs[3]); - const auto pRtlRemoveVectoredExceptionHandler = reinterpret_cast(funcs[4]); - const auto pNtCreateThreadEx = reinterpret_cast(funcs[5]); - const auto pNtGetContextThread = reinterpret_cast(funcs[6]); - const auto pNtSetContextThread = reinterpret_cast(funcs[7]); - const auto pNtResumeThread = reinterpret_cast(funcs[8]); - const auto pNtWaitForSingleObject = reinterpret_cast(funcs[9]); - const auto pNtClose = reinterpret_cast(funcs[10]); - const auto pNtProtectVirtualMemory = reinterpret_cast(funcs[11]); - - if (!pNtAllocateVirtualMemory || !pNtFreeVirtualMemory || !pNtFlushInstructionCache || - !pRtlAddVectoredExceptionHandler || !pRtlRemoveVectoredExceptionHandler || - !pNtCreateThreadEx || !pNtGetContextThread || !pNtSetContextThread || - !pNtResumeThread || !pNtWaitForSingleObject || !pNtClose || !pNtProtectVirtualMemory) { - return false; - } - - // ICEBP because the kernel interrupt handler that inserts the LastBranchFromIp into EXCEPTION_RECORD->ExceptionInformation[0] is the INT 01 handler - constexpr unsigned char codeBytes[] = { 0xE8,0x00,0x00,0x00,0x00, 0xF1, 0xC3 }; // CALL next ; ICEBP ; RET - const SIZE_T codeSize = sizeof(codeBytes); - const HANDLE hCurrentProcess = reinterpret_cast(-1LL); - - PVOID controlBase = nullptr; - SIZE_T controlSize = sizeof(PVOID); - NTSTATUS st = pNtAllocateVirtualMemory(hCurrentProcess, &controlBase, 0, &controlSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - if (st != 0 || !controlBase) return false; - *reinterpret_cast(controlBase) = nullptr; - - PVOID execBase = nullptr; - SIZE_T allocSize = codeSize; - st = pNtAllocateVirtualMemory(hCurrentProcess, &execBase, 0, &allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - if (st != 0 || !execBase) { - SIZE_T tmp = controlSize; - pNtFreeVirtualMemory(hCurrentProcess, &controlBase, &tmp, MEM_RELEASE); - return false; - } - - unsigned char* dst = reinterpret_cast(execBase); - for (SIZE_T i = 0; i < codeSize; ++i) dst[i] = codeBytes[i]; - - ULONG oldProtect = 0; - SIZE_T protectSize = allocSize; - PVOID protectBase = execBase; - st = pNtProtectVirtualMemory(hCurrentProcess, &protectBase, &protectSize, PAGE_EXECUTE_READ, &oldProtect); - if (st != 0) { - SIZE_T tmpExec = allocSize; - pNtFreeVirtualMemory(hCurrentProcess, &execBase, &tmpExec, MEM_RELEASE); - SIZE_T tmpControl = controlSize; - pNtFreeVirtualMemory(hCurrentProcess, &controlBase, &tmpControl, MEM_RELEASE); - return false; - } - - pNtFlushInstructionCache(hCurrentProcess, execBase, codeSize); - - // local static pointer to control slot so lambda can access a stable address - static PVOID g_control_slot = nullptr; - g_control_slot = controlBase; - - auto veh_lambda = [](PEXCEPTION_POINTERS ep) -> LONG { - if (!ep || !ep->ExceptionRecord) return EXCEPTION_CONTINUE_SEARCH; - if (ep->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP) return EXCEPTION_CONTINUE_SEARCH; - - ULONG_PTR info0 = 0; - if (ep->ExceptionRecord->NumberParameters > 0) info0 = ep->ExceptionRecord->ExceptionInformation[0]; - if (info0 && g_control_slot) { - PVOID expected = nullptr; - _InterlockedCompareExchangePointer(reinterpret_cast(g_control_slot), reinterpret_cast(info0), expected); - } - return EXCEPTION_CONTINUE_EXECUTION; - }; - - // Register VEH - const PVECTORED_EXCEPTION_HANDLER veh_fn = static_cast(veh_lambda); - const PVOID vehHandle = pRtlAddVectoredExceptionHandler(1, veh_fn); - if (!vehHandle) { - SIZE_T tmp = allocSize; - pNtFreeVirtualMemory(hCurrentProcess, &execBase, &tmp, MEM_RELEASE); - tmp = controlSize; - pNtFreeVirtualMemory(hCurrentProcess, &controlBase, &tmp, MEM_RELEASE); - return false; - } - - // create suspended thread - HANDLE hThread = nullptr; - NTSTATUS ntres = pNtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, nullptr, hCurrentProcess, execBase, nullptr, TRUE, 0, 0, 0, nullptr); - if (ntres != 0 || !hThread) { - pRtlRemoveVectoredExceptionHandler(vehHandle); - SIZE_T tmp = allocSize; - pNtFreeVirtualMemory(hCurrentProcess, &execBase, &tmp, MEM_RELEASE); - tmp = controlSize; - pNtFreeVirtualMemory(hCurrentProcess, &controlBase, &tmp, MEM_RELEASE); - return false; - } - - // set debug bits + TF on suspended thread - CONTEXT ctx; - ZeroMemory(&ctx, sizeof(ctx)); - ctx.ContextFlags = CONTEXT_CONTROL | CONTEXT_DEBUG_REGISTERS; - ntres = pNtGetContextThread(hThread, &ctx); - if (ntres != 0) { - pNtClose(hThread); - pRtlRemoveVectoredExceptionHandler(vehHandle); - SIZE_T tmp = allocSize; - pNtFreeVirtualMemory(hCurrentProcess, &execBase, &tmp, MEM_RELEASE); - tmp = controlSize; - pNtFreeVirtualMemory(hCurrentProcess, &controlBase, &tmp, MEM_RELEASE); - return false; - } - ctx.Dr7 |= (1ull << 8) | (1ull << 9); // LBR only would be enough - ctx.EFlags |= 0x100; - ntres = pNtSetContextThread(hThread, &ctx); - if (ntres != 0) { - pNtClose(hThread); - pRtlRemoveVectoredExceptionHandler(vehHandle); - SIZE_T tmp = allocSize; - pNtFreeVirtualMemory(hCurrentProcess, &execBase, &tmp, MEM_RELEASE); - tmp = controlSize; - pNtFreeVirtualMemory(hCurrentProcess, &controlBase, &tmp, MEM_RELEASE); - return false; - } - - // resume and wait - ULONG suspendCount = 0; - ntres = pNtResumeThread(hThread, &suspendCount); - if (ntres != 0) { - pNtClose(hThread); - pRtlRemoveVectoredExceptionHandler(vehHandle); - SIZE_T tmp = allocSize; - pNtFreeVirtualMemory(hCurrentProcess, &execBase, &tmp, MEM_RELEASE); - tmp = controlSize; - pNtFreeVirtualMemory(hCurrentProcess, &controlBase, &tmp, MEM_RELEASE); - return false; - } - ntres = pNtWaitForSingleObject(hThread, FALSE, nullptr); - - // read slot (pointer-sized) so if null then no LBR observed - const PVOID slot_val = *reinterpret_cast(controlBase); - - // cleanup - pRtlRemoveVectoredExceptionHandler(vehHandle); - pNtClose(hThread); - SIZE_T tmpSize = allocSize; - pNtFreeVirtualMemory(hCurrentProcess, &execBase, &tmpSize, MEM_RELEASE); - tmpSize = controlSize; - pNtFreeVirtualMemory(hCurrentProcess, &controlBase, &tmpSize, MEM_RELEASE); - g_control_slot = nullptr; - - // a breakpoint set anywhere in this function before slot_val is read will cause the kernel to not deliver any LBR info, thereby returning true - return (slot_val == nullptr); - #else - return false; - #endif - } // ADD NEW TECHNIQUE FUNCTION HERE #endif @@ -11628,7 +11271,6 @@ struct VM { case EDID: return "EDID"; case CPU_HEURISTIC: return "CPU_HEURISTIC"; case CLOCK: return "CLOCK"; - case LBR: return "LBR"; // END OF TECHNIQUE LIST case DEFAULT: return "setting flag, error"; case ALL: return "setting flag, error"; @@ -12167,7 +11809,6 @@ std::pair VM::core::technique_list[] = { 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::LBR, VM::core::technique(95, VM::lbr)), 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)), @@ -12278,3 +11919,5 @@ table_t VM::core::technique_table = []() -> table_t { } return table; }(); + +#endif // include guard end