diff --git a/README.md b/README.md index fbd4b59b..0917657a 100755 --- a/README.md +++ b/README.md @@ -225,15 +225,17 @@ If you want to learn about the architecture and design of the library, head over How is it developed?
-> Based on online research (ranging from science papers to things like private game hacking forums and discord servers), we try to identify the methods currently used to hide VMs and investigate generic detections capable of detecting them, while constantly tracking their activity to ensure we stay one step ahead. +> Based on online research (ranging from scientific papers to private game-hacking forums and Discord servers), we identify the methods currently used to hide VMs and investigate generic detection techniques capable of finding them, while continuously tracking their activity so we stay one step ahead. > -> Once we have developed production-level code, we upload it to the dev branch to start testing it in real environments, where products using our library on hundreds or even thousands of devices run our detection algorithms and silently alert us if a VM has been detected, to be later manually verified for false positives. -> -> If we believe that false positives have been corrected based on experimental tests and online evidence in public documentation and databases, we merge the changes to the main branch, assigning the new detections a score, taking into account their effectiveness, reliability, and their operation in conjunction with the rest of the techniques. +> Once we have production-ready code, we upload it directly to the main branch and begin testing in real environments. +> +> Products that include our library run our detection algorithms on hundreds or even thousands of devices and quietly report back if a VM is detected; those reports are later manually checked for false positives. +> +> If experimental tests and public documentation/databases indicate that false positives have been resolved, we keep the changes in main and assign scores to new detections based on their effectiveness, reliability, and how they operate together with other techniques. > -> Other situations (such as false flags, compilation errors, possible vulnerabilities, etc.) are immediately merged into the main branch. +> Other situations (false flags, compilation errors, possible vulnerabilities, etc.) are also merged into main immediately. > -> Once the library has undergone sufficient modifications compared to previous versions, we place the library in the releases section, explaining these changes in detail. +> When the library has accumulated enough changes compared to previous versions, we publish a release and explain those changes in detail. diff --git a/README_CN.md b/README_CN.md index 2a143cac..725d2692 100644 --- a/README_CN.md +++ b/README_CN.md @@ -220,15 +220,15 @@ endif()
它是如何开发的? -> 基于线上研究(涵盖从学术论文到私人游戏黑客论坛及Discord社群等渠道),我们持续追踪当前用于隐藏虚拟机的方法,并研究能检测它们的通用方案,以此确保我们始终保持技术领先。 -> -> 当我们完成生产级代码开发后,会将其上传至开发分支进行真实环境测试——通过数百乃至数千台设备运行我们的检测算法,并在识别到虚拟机时静默上报,后续由人工核验误报情况。 -> -> 若通过实验测试及公开文档/数据库的线上证据确认误报已修正,我们会将变更合并至主分支,并根据新检测技术的有效性、可靠性及与其他技术的协同表现进行综合评分。 -> -> 其他特殊情况(如误报标记、编译错误、潜在漏洞等)则立即合并至主分支。 -> -> 当库版本累积足够改进后,我们会在发布区详细说明所有变更内容。 +> 基于线上研究(涵盖学术论文到私人游戏破解论坛及 Discord 社群等渠道),我们识别当前用于隐藏虚拟机的方法,并研究能够检测它们的通用检测手段,同时持续追踪其活动以保持领先。 +> +> 当我们完成生产级代码后,会直接将其上传到 main 分支并在真实环境开始测试。使用我们库的产品会在数百甚至数千台设备上运行检测算法,若检测到虚拟机会静默上报;这些上报随后由人工核验误报。 +> +> 如果实验测试和公开文档/数据库的线上证据表明误报已被修正,我们会将变更保留在 main,并根据新检测项的有效性、可靠性及与其他技术的协同表现对其进行评分。 +> +> 其他情况(例如误报标记、编译错误、潜在漏洞等)也会立即合并到 main。 +> +> 当库相比之前的版本积累了足够的改进后,我们会发布新版本,并在发布说明中详细说明这些更改。
diff --git a/README_FR.md b/README_FR.md index 43386624..a6e56e4c 100644 --- a/README_FR.md +++ b/README_FR.md @@ -215,15 +215,17 @@ Si vous voulez comprendre l’architecture et la conception de la bibliothèque, Comment est-il développé?
-> À partir de recherches en ligne (articles scientifiques, forums de piratage de jeux privés, serveurs Discord, etc.), nous identifions les méthodes utilisées pour dissimuler les VM et étudions les systèmes de détection génériques capables de les repérer. Nous surveillons en permanence leur activité pour garder une longueur d'avance. -> -> Une fois le code prêt pour la production, nous le téléchargeons sur la branche de développement (`dev`) pour le tester en conditions réelles. Sur des centaines, voire des milliers d'appareils, les produits utilisant notre bibliothèque exécutent nos algorithmes de détection et nous alertent discrètement en cas de détection d'une VM. Les faux positifs sont ensuite vérifiés manuellement. +> À partir de recherches en ligne (articles scientifiques, forums privés de piratage de jeux, serveurs Discord, etc.), nous identifions les méthodes utilisées pour dissimuler les VM et étudions des techniques de détection générales capables de les repérer, tout en surveillant en permanence leur activité pour garder une longueur d'avance. > -> Si nous estimons que les faux positifs ont été corrigés grâce à des tests expérimentaux et des preuves en ligne issues de la documentation et des bases de données publiques, nous intégrons les modifications à la branche `main`, en attribuant un score aux nouvelles détections. Ce score tient compte de leur efficacité, de leur fiabilité et de leur fonctionnement en combinaison avec les autres techniques. -> -> Les autres situations (telles que les faux positifs, les erreurs de compilation, les vulnérabilités potentielles, etc.) sont immédiatement résolues et intégrées sur `main`. +> Une fois le code prêt pour la production, nous le téléversons directement sur la branche main et commençons les tests en conditions réelles. +> +> Les produits intégrant notre bibliothèque exécutent nos algorithmes de détection sur des centaines voire des milliers d'appareils et nous signalent discrètement toute détection de VM ; ces signalements sont ensuite vérifiés manuellement pour détecter d'éventuels faux positifs. +> +> Si les tests expérimentaux et les preuves issues de la documentation et des bases de données publiques confirment que les faux positifs ont été corrigés, nous conservons les modifications sur main et attribuons un score aux nouvelles détections selon leur efficacité, leur fiabilité et leur interaction avec les autres techniques. +> +> D'autres situations (faux positifs, erreurs de compilation, vulnérabilités potentielles, etc.) sont également intégrées immédiatement sur main. > -> Une fois que la bibliothèque a subi suffisamment de modifications par rapport aux versions précédentes, nous la publions dans la section des versions, en expliquant ces modifications en détail. +> Quand la bibliothèque a accumulé suffisamment de modifications par rapport aux versions précédentes, nous publions une release et détaillons les changements dans les notes de version. diff --git a/README_KR.md b/README_KR.md index f24aa558..841163c5 100644 --- a/README_KR.md +++ b/README_KR.md @@ -187,14 +187,6 @@ endif() -
오픈 소스 프로젝트는 라이브러리를 더 취약하게 만들지 않을까요? @@ -221,15 +213,15 @@ endif() 어떻게 개발되나요?
-> 학술 논문부터 개인 게임 해킹 포럼, Discord 커뮤니티에 이르기까지 다양한 채널을 통한 온라인 연구를 바탕으로, 우리는 가상 머신을 숨기는 데 사용되는 최신 기법을 지속적으로 추적하고 이를 감지할 수 있는 일반적인 솔루션을 연구하여 항상 기술의 선두를 유지합니다. -> -> 프로덕션 퀄리티의 코드 개발을 완료하면 실제 테스트를 위해 개발 브랜치에 업로드합니다. 수백, 수천 대의 장치에서 감지 알고리즘을 실행하고 가상 머신이 감지되면 자동으로 보고한 후, 오탐지에 대한 수동 검증을 수행합니다. +> 학술 논문에서 개인 게임 해킹 포럼, Discord 커뮤니티에 이르기까지 온라인 조사를 바탕으로 가상 머신을 숨기는 최신 기법을 식별하고, 이를 탐지할 수 있는 일반적 방법을 연구하며 그 활동을 지속적으로 추적해 기술 우위를 유지합니다. > -> 실험 테스트와 공개 문서/데이터베이스의 온라인 증거를 토대로 오탐지가 수정된 것으로 확인되면 변경 사항을 메인 브렌치에 병합합니다. 또한 새로운 탐지 기술에 효과성, 신뢰성, 다른 기술과의 시너지 효과 바탕으로 포괄적인 점수를 부여합니다. -> -> 기타 특수한 경우(예: 오탐지, 컴파일 오류, 잠재적 취약점 등)는 즉시 메인 브랜치에 병합됩니다. -> -> 라이브러리 버전에 충분한 개선 사항이 누적되면 릴리즈 되며, 릴리즈 페이지에서 모든 변경 사항을 상세히 기술합니다. +> 프로덕션 품질의 코드가 준비되면 이를 main 분기에 직접 업로드하고 실제 환경에서 테스트를 시작합니다. 우리 라이브러리를 포함한 제품들은 수백에서 수천 대의 장치에서 탐지 알고리즘을 실행하며, 가상 머신이 감지되면 조용히 보고하고 해당 보고서는 이후 수동으로 오탐 여부를 검증합니다. +> +> 실험 테스트 및 공개 문서/데이터베이스의 증거로 오탐이 수정된 것이 확인되면 변경사항은 main에 남겨지며, 새 탐지 항목에는 유효성, 신뢰성, 다른 기법과의 상호작용을 고려한 점수가 부여됩니다. +> +> 오탐, 컴파일 오류, 잠재적 취약점 등 다른 상황도 즉시 main에 통합됩니다. +> +> 라이브러리가 이전 버전 대비 충분한 개선을 누적하면 릴리스를 게시하고 변경 사항을 상세히 설명합니다.
diff --git a/docs/documentation.md b/docs/documentation.md index eefc0d24..ab8a0a03 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -510,90 +510,90 @@ VMAware provides a convenient way to not only check for VMs, but also have the f | Flag alias | Description | Supported platforms | Certainty | Admin? | 32-bit only? | Notes | Code implementation | | ---------- | ----------- | ------------------- | --------- | ------ | ------------ | ----- | ------------------- | -| `VM::VMID` | Check CPUID output of manufacturer ID for known VMs/hypervisors at leaf 0 and 0x40000000-0x40000100 | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4282) | -| `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#L4300) | -| `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#L4374) | -| `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#L4400) | -| `VM::TIMER` | Check for timing anomalies in the system | 🐧🪟🍏 | 150% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4586) | -| `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#L7269) | -| `VM::MAC` | Check if mac address starts with certain VM designated values | 🐧 | 20% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5326) | -| `VM::TEMPERATURE` | Check for device's temperature | 🐧 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6178) | -| `VM::SYSTEMD` | Check result from systemd-detect-virt tool | 🐧 | 35% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5207) | -| `VM::CVENDOR` | Check if the chassis vendor is a VM vendor | 🐧 | 65% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5231) | -| `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#L5256) | -| `VM::DOCKERENV` | Check if /.dockerenv or /.dockerinit file is present | 🐧 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5274) | -| `VM::DMIDECODE` | Check if dmidecode output matches a VM brand | 🐧 | 55% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5289) | -| `VM::DMESG` | Check if dmesg output matches a VM brand | 🐧 | 55% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5432) | -| `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#L5473) | -| `VM::DLL` | Check for VM-specific DLLs | 🪟 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7569) | -| `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#L7293) | -| `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#L7600) | -| `VM::POWER_CAPABILITIES` | Check what power states are enabled | 🪟 | 45% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7639) | -| `VM::PROCESSES` | Check for any VM processes that are active | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6189) | -| `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#L5483) | -| `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#L7699) | -| `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#L4428) | -| `VM::MAC_MEMSIZE` | Check if memory is too low for MacOS system | 🍏 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7329) | -| `VM::MAC_IOKIT` | Check MacOS' IO kit registry for VM-specific strings | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7362) | -| `VM::IOREG_GREP` | Check for VM-strings in ioreg commands for MacOS | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7459) | -| `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#L7500) | -| `VM::VPC_INVALID` | Check for official VPC method | 🪟 | 75% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7808) | +| `VM::VMID` | Check CPUID output of manufacturer ID for known VMs/hypervisors at leaf 0 and 0x40000000-0x40000100 | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4284) | +| `VM::CPU_BRAND` | Check if CPU brand model contains any VM-specific string snippets | 🐧🪟🍏 | 95% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4302) | +| `VM::HYPERVISOR_BIT` | Check if hypervisor feature bit in CPUID ECX bit 31 is enabled (always false for physical CPUs) | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4376) | +| `VM::HYPERVISOR_STR` | Check for hypervisor brand string length (would be around 2 characters in a host machine) | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4402) | +| `VM::TIMER` | Check for timing anomalies in the system | 🐧🪟🍏 | 150% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4588) | +| `VM::THREAD_COUNT` | Check if there are only 1 or 2 threads, which is a common pattern in VMs with default settings, nowadays physical CPUs should have at least 4 threads for modern CPUs | 🐧🪟🍏 | 35% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7227) | +| `VM::MAC` | Check if mac address starts with certain VM designated values | 🐧 | 20% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5284) | +| `VM::TEMPERATURE` | Check for device's temperature | 🐧 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6136) | +| `VM::SYSTEMD` | Check result from systemd-detect-virt tool | 🐧 | 35% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5165) | +| `VM::CVENDOR` | Check if the chassis vendor is a VM vendor | 🐧 | 65% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5189) | +| `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#L5214) | +| `VM::DOCKERENV` | Check if /.dockerenv or /.dockerinit file is present | 🐧 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5232) | +| `VM::DMIDECODE` | Check if dmidecode output matches a VM brand | 🐧 | 55% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5247) | +| `VM::DMESG` | Check if dmesg output matches a VM brand | 🐧 | 55% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5390) | +| `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#L5431) | +| `VM::DLL` | Check for VM-specific DLLs | 🪟 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7527) | +| `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#L7251) | +| `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#L7558) | +| `VM::POWER_CAPABILITIES` | Check what power states are enabled | 🪟 | 45% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7597) | +| `VM::PROCESSES` | Check for any VM processes that are active | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6147) | +| `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#L5441) | +| `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#L7657) | +| `VM::BOCHS_CPU` | Check for various Bochs-related emulation oversights through CPU checks | 🐧🪟🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4430) | +| `VM::MAC_MEMSIZE` | Check if memory is too low for MacOS system | 🍏 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7287) | +| `VM::MAC_IOKIT` | Check MacOS' IO kit registry for VM-specific strings | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7320) | +| `VM::IOREG_GREP` | Check for VM-strings in ioreg commands for MacOS | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7417) | +| `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#L7458) | +| `VM::VPC_INVALID` | Check for official VPC method | 🪟 | 75% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7766) | | `VM::SYSTEM_REGISTERS` | | | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L1) | -| `VM::VMWARE_IOMEM` | Check for VMware string in /proc/iomem | 🐧 | 65% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5512) | -| `VM::VMWARE_IOPORTS` | Check for VMware string in /proc/ioports | 🐧 | 70% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6024) | -| `VM::VMWARE_SCSI` | Check for VMware string in /proc/scsi/scsi | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5821) | -| `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#L5840) | -| `VM::VMWARE_STR` | Check str assembly instruction method for VMware | 🪟 | 35% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7859) | -| `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#L7884) | -| `VM::MUTEX` | Check for mutex strings of VM brands | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7945) | -| `VM::THREAD_MISMATCH` | Check if the system's thread count matches the expected thread count for the detected CPU model | 🐧🪟🍏 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4508) | -| `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#L8031) | -| `VM::CUCKOO_PIPE` | Check for Cuckoo specific piping mechanism | 🪟 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8087) | -| `VM::AZURE` | Check for default Azure hostname format (Azure uses Hyper-V as their base VM brand) | 🐧🪟 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6426) | -| `VM::DISPLAY` | Check for display configurations commonly found in VMs | 🪟 | 25% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8143) | -| `VM::DEVICE_STRING` | Check if bogus device string would be accepted | 🪟 | 25% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8178) | -| `VM::BLUESTACKS_FOLDERS` | Check for the presence of BlueStacks-specific folders | 🐧 | 5% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5528) | -| `VM::CPUID_SIGNATURE` | Check for signatures in leaf 0x40000001 in CPUID | 🐧🪟🍏 | 95% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4536) | -| `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#L4562) | -| `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#L5609) | -| `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#L5638) | -| `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#L5666) | -| `VM::UML_CPU` | Check for the "UML" string in the CPU brand | 🐧 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5714) | -| `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#L5744) | -| `VM::VBOX_MODULE` | Check for a VBox kernel module | 🐧 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5798) | -| `VM::SYSINFO_PROC` | Check for potential VM info in /proc/sysinfo | 🐧 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5874) | -| `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#L5896) | -| `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#L5979) | -| `VM::PODMAN_FILE` | Check for podman file in /run/ | 🐧 | 5% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6009) | -| `VM::WSL_PROC` | Check for WSL or microsoft indications in /proc/ subdirectories | 🐧 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6041) | -| `VM::DRIVERS` | Check for VM-specific names for drivers | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8195) | -| `VM::DISK_SERIAL` | Check for serial numbers of virtual disks | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8293) | -| `VM::IVSHMEM` | Check for IVSHMEM device presence | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8532) | -| `VM::GPU_CAPABILITIES` | Check for GPU capabilities related to VMs | 🪟 | 45% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8638) | -| `VM::DEVICE_HANDLES` | Check for vm-specific devices | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8676) | -| `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#L6069) | -| `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#L8779) | -| `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#L8809) | -| `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#L5551) | -| `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#L8877) | -| `VM::FIRMWARE` | Check for VM signatures on all firmware tables | 🐧🪟 | 100% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6486) | -| `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#L6099) | -| `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#L8971) | -| `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#L6126) | -| `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#L6913) | -| `VM::ACPI_SIGNATURE` | Check for VM-specific ACPI device signatures | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9079) | -| `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#L9224) | -| `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#L8971) | -| `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#L9499) | -| `VM::DBVM` | Check if Dark Byte's VM is present | 🪟 | 150% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9546) | -| `VM::BOOT_LOGO` | Check boot logo for known VM images | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9665) | -| `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#L7544) | -| `VM::KERNEL_OBJECTS` | Check for any signs of VMs in Windows kernel object entities | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9768) | -| `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#L9954) | -| `VM::SMBIOS_INTEGRITY` | Check if SMBIOS is malformed/corrupted in a way that is typical for VMs | 🪟 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10502) | -| `VM::EDID` | Check for non-standard EDID configurations | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10513) | -| `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#L10769) | -| `VM::CLOCK` | Check the presence of system timers | 🪟 | 90% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L11237) | +| `VM::VMWARE_IOMEM` | Check for VMware string in /proc/iomem | 🐧 | 65% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5470) | +| `VM::VMWARE_IOPORTS` | Check for VMware string in /proc/ioports | 🐧 | 70% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5982) | +| `VM::VMWARE_SCSI` | Check for VMware string in /proc/scsi/scsi | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5779) | +| `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#L5798) | +| `VM::VMWARE_STR` | Check str assembly instruction method for VMware | 🪟 | 35% | | 32-bit | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7817) | +| `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#L7842) | +| `VM::MUTEX` | Check for mutex strings of VM brands | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7903) | +| `VM::THREAD_MISMATCH` | Check if the system's thread count matches the expected thread count for the detected CPU model | 🐧🪟🍏 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4510) | +| `VM::CUCKOO_DIR` | Check for cuckoo directory using crt and WIN API directory functions | 🪟 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L7989) | +| `VM::CUCKOO_PIPE` | Check for Cuckoo specific piping mechanism | 🪟 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8045) | +| `VM::AZURE` | Check for default Azure hostname format (Azure uses Hyper-V as their base VM brand) | 🐧🪟 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6384) | +| `VM::DISPLAY` | Check for display configurations commonly found in VMs | 🪟 | 25% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8101) | +| `VM::DEVICE_STRING` | Check if bogus device string would be accepted | 🪟 | 25% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8136) | +| `VM::BLUESTACKS_FOLDERS` | Check for the presence of BlueStacks-specific folders | 🐧 | 5% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5486) | +| `VM::CPUID_SIGNATURE` | Check for signatures in leaf 0x40000001 in CPUID | 🐧🪟🍏 | 95% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4538) | +| `VM::KGT_SIGNATURE` | Check for Intel KGT (Trusty branch) hypervisor signature in CPUID | 🐧🪟🍏 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L4564) | +| `VM::QEMU_VIRTUAL_DMI` | Check for presence of QEMU in the /sys/devices/virtual/dmi/id directory | 🐧 | 40% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5567) | +| `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#L5596) | +| `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#L5624) | +| `VM::UML_CPU` | Check for the "UML" string in the CPU brand | 🐧 | 80% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5672) | +| `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#L5702) | +| `VM::VBOX_MODULE` | Check for a VBox kernel module | 🐧 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5756) | +| `VM::SYSINFO_PROC` | Check for potential VM info in /proc/sysinfo | 🐧 | 15% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5832) | +| `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#L5854) | +| `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#L5937) | +| `VM::PODMAN_FILE` | Check for podman file in /run/ | 🐧 | 5% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5967) | +| `VM::WSL_PROC` | Check for WSL or microsoft indications in /proc/ subdirectories | 🐧 | 30% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L5999) | +| `VM::DRIVERS` | Check for VM-specific names for drivers | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8153) | +| `VM::DISK_SERIAL` | Check for serial numbers of virtual disks | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8251) | +| `VM::IVSHMEM` | Check for IVSHMEM device presence | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8490) | +| `VM::GPU_CAPABILITIES` | Check for GPU capabilities related to VMs | 🪟 | 45% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8596) | +| `VM::DEVICE_HANDLES` | Check for vm-specific devices | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8634) | +| `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#L6027) | +| `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#L8737) | +| `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#L8767) | +| `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#L5509) | +| `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#L8835) | +| `VM::FIRMWARE` | Check for VM signatures on all firmware tables | 🐧🪟 | 100% | Admin | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L6444) | +| `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#L6057) | +| `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#L8929) | +| `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#L6084) | +| `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#L6871) | +| `VM::ACPI_SIGNATURE` | Check for VM-specific ACPI device signatures | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9037) | +| `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#L9182) | +| `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#L8929) | +| `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#L9457) | +| `VM::DBVM` | Check if Dark Byte's VM is present | 🪟 | 150% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9504) | +| `VM::BOOT_LOGO` | Check boot logo for known VM images | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9623) | +| `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#L7502) | +| `VM::KERNEL_OBJECTS` | Check for any signs of VMs in Windows kernel object entities | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9726) | +| `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#L9912) | +| `VM::SMBIOS_INTEGRITY` | Check if SMBIOS is malformed/corrupted in a way that is typical for VMs | 🪟 | 50% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10460) | +| `VM::EDID` | Check for non-standard EDID configurations | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10471) | +| `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#L10727) | +| `VM::CLOCK` | Check the presence of system timers | 🪟 | 90% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L11195) |
diff --git a/src/cli.cpp b/src/cli.cpp index 13e429d3..ef184e06 100755 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -22,25 +22,26 @@ * - License: MIT */ -#include -#include #include -#include #include #if (defined(__GNUC__) || defined(__linux__)) - #include #define CLI_LINUX 1 #else #define CLI_LINUX 0 #endif +#if (defined(__APPLE__) || defined(__APPLE_CPP__) || defined(__MACH__) || defined(__DARWIN)) + #define CLI_APPLE 1 + #include +#else + #define CLI_APPLE 0 +#endif #if (defined(_MSC_VER) || defined(_WIN32) || defined(_WIN64) || defined(__MINGW32__)) #define CLI_WINDOWS 1 #define WIN32_LEAN_AND_MEAN #define NOMINMAX - #include #else #define CLI_WINDOWS 0 #endif @@ -63,6 +64,7 @@ std::string grey = "\x1B[38;2;108;108;108m"; using u8 = std::uint8_t; using u16 = std::uint16_t; using u32 = std::uint32_t; +using u64 = std::uint64_t; using i32 = std::int32_t; enum arg_enum : u8 { @@ -141,6 +143,179 @@ class win_ansi_enabler_t }; #endif +struct SHA256 { + u8 buf[64] = {}; // message block buffer + u32 len = 0; // bytes currently in buf + u64 bits = 0; // total bits processed + u32 s[8] = {}; // from h0 to h7 + + // Initialize state to SHA-256 IVs so that compiler doesn't complain + void init() { + len = 0; + bits = 0; + s[0] = 0x6a09e667; + s[1] = 0xbb67ae85; + s[2] = 0x3c6ef372; + s[3] = 0xa54ff53a; + s[4] = 0x510e527f; + s[5] = 0x9b05688c; + s[6] = 0x1f83d9ab; + s[7] = 0x5be0cd19; + } + + // bitwise helpers + static u32 rotr(u32 x, int n) { return (x >> n) | (x << (32 - n)); } + static u32 ch(u32 x, u32 y, u32 z) { return (x & y) ^ (~x & z); } + static u32 maj(u32 x, u32 y, u32 z) { return (x & y) ^ (x & z) ^ (y & z); } + static u32 ep0(u32 x) { return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22); } + static u32 ep1(u32 x) { return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25); } + static u32 sig0(u32 x) { return rotr(x, 7) ^ rotr(x, 18) ^ (x >> 3); } + static u32 sig1(u32 x) { return rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10); } + + // we need to process one 512-bit block from buf + void transform() { + static const u32 k[64] = { + 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, + 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, + 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da, + 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967, + 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85, + 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070, + 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3, + 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2 + }; + u32 m[64]; + for (u32 i = 0, j = 0; i < 16; ++i, j += 4) { + m[i] = (u32)buf[j] << 24 | (u32)buf[j + 1] << 16 | (u32)buf[j + 2] << 8 | (u32)buf[j + 3]; + } + for (u32 i = 16; i < 64; ++i) { + m[i] = sig1(m[i - 2]) + m[i - 7] + sig0(m[i - 15]) + m[i - 16]; + } + u32 a = s[0]; + u32 b = s[1]; + u32 c = s[2]; + u32 d = s[3]; + u32 e = s[4]; + u32 f = s[5]; + u32 g = s[6]; + u32 h = s[7]; + for (u32 i = 0; i < 64; ++i) { + u32 t1 = h + ep1(e) + ch(e, f, g) + k[i] + m[i]; + u32 t2 = ep0(a) + maj(a, b, c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + s[0] += a; + s[1] += b; + s[2] += c; + s[3] += d; + s[4] += e; + s[5] += f; + s[6] += g; + s[7] += h; + } + + // arbitrary bytes into the digest + void update(const u8* data, size_t n) { + for (size_t i = 0; i < n; ++i) { + buf[len++] = data[i]; + if (len == 64) { + transform(); + bits += 512; + len = 0; + } + } + } + + // 32-byte digest IN big-endian + void final(u8 out[32]) { + size_t i = len; + if (len < 56) { + buf[i++] = 0x80; + while (i < 56) buf[i++] = 0; + } + else { + buf[i++] = 0x80; + while (i < 64) buf[i++] = 0; + transform(); + for (size_t j = 0; j < 56; ++j) buf[j] = 0; + } + bits += (u64)len * 8; + for (int j = 0; j < 8; ++j) { + buf[63 - j] = (u8)((bits >> (8 * j)) & 0xFF); + } + transform(); + for (i = 0; i < 4; ++i) { + for (size_t j = 0; j < 8; ++j) { + out[i + j * 4] = (u8)((s[j] >> (24 - i * 8)) & 0xFF); + } + } + } +}; + +static std::string exe_path() { +#if (CLI_WINDOWS) + std::vector buf(32768); + DWORD r = GetModuleFileNameA(NULL, buf.data(), (DWORD)buf.size()); + if (r == 0 || r >= buf.size()) return {}; + return std::string(buf.data(), r); +#elif (CLI_APPLE) + uint32_t sz = 0; + _NSGetExecutablePath(nullptr, &sz); + std::vector b(sz); + if (_NSGetExecutablePath(b.data(), &sz) != 0) return {}; + std::vector resolved(PATH_MAX); + if (realpath(b.data(), resolved.data())) { + return std::string(resolved.data()); + } + return std::string(b.data()); +#else + std::vector b(PATH_MAX); + ssize_t l = ::readlink("/proc/self/exe", b.data(), b.size() - 1); + if (l <= 0) return {}; + b[(size_t)l] = '\0'; + std::vector resolved(PATH_MAX); + if (realpath(b.data(), resolved.data())) { + return std::string(resolved.data()); + } + return std::string(b.data()); +#endif +} + +std::string compute_self_sha256() { + std::string path = exe_path(); + if (path.empty()) return {}; + std::ifstream ifs(path, std::ios::binary); + if (!ifs) return {}; + SHA256 sha; + sha.init(); + + std::vector chunk(64 * 1024); + while (ifs) { + ifs.read(chunk.data(), static_cast(chunk.size())); + std::streamsize r = ifs.gcount(); + if (r > 0) { + sha.update(reinterpret_cast(chunk.data()), static_cast(r)); + } + } + + u8 digest[32]; + sha.final(digest); + static const char hex[] = "0123456789abcdef"; + std::string out; + out.reserve(64); + for (int i = 0; i < 32; ++i) { + out.push_back(hex[(digest[i] >> 4) & 0xF]); + out.push_back(hex[digest[i] & 0xF]); + } + return out; +} [[noreturn]] static void help(void) { std::cout << @@ -220,76 +395,75 @@ static const char* color(const u8 score) { [[noreturn]] static void brand_list() { std::cout << -R"(VirtualBox -VMware -VMware Express -VMware ESX -VMware GSX -VMware Workstation -VMware Fusion -bhyve -QEMU -KVM -KVM Hyper-V Enlightenment -QEMU+KVM Hyper-V Enlightenment -QEMU+KVM -Virtual PC -Microsoft Hyper-V -Microsoft Virtual PC/Hyper-V -Parallels -Xen HVM -ACRN -QNX hypervisor -Hybrid Analysis -Sandboxie -Docker -Wine -Anubis -JoeBox -ThreatExpert -CWSandbox -Comodo -Bochs -Lockheed Martin LMHS -NVMM -OpenBSD VMM -Intel HAXM -Unisys s-Par -Cuckoo -BlueStacks -Jailhouse -Apple VZ -Intel KGT (Trusty) -Microsoft Azure Hyper-V -Xbox NanoVisor (Hyper-V) -SimpleVisor -Hyper-V artifact (host with Hyper-V enabled) -User-mode Linux -IBM PowerVM -Google Compute Engine (KVM) -OpenStack (KVM) -KubeVirt (KVM) -AWS Nitro System (KVM-based) -Podman -WSL -OpenVZ -ANY.RUN -Barevisor -HyperPlatform -MiniVisor -Intel TDX -LKVM -AMD SEV -AMD SEV-ES -AMD SEV-SNP -Neko Project II -NoirVisor -Qihoo 360 Sandbox -nsjail -DBVM -UTM -)"; - + R"(VirtualBox + VMware + VMware Express + VMware ESX + VMware GSX + VMware Workstation + VMware Fusion + bhyve + QEMU + KVM + KVM Hyper-V Enlightenment + QEMU+KVM Hyper-V Enlightenment + QEMU+KVM + Virtual PC + Microsoft Hyper-V + Microsoft Virtual PC/Hyper-V + Parallels + Xen HVM + ACRN + QNX hypervisor + Hybrid Analysis + Sandboxie + Docker + Wine + Anubis + JoeBox + ThreatExpert + CWSandbox + Comodo + Bochs + Lockheed Martin LMHS + NVMM + OpenBSD VMM + Intel HAXM + Unisys s-Par + Cuckoo + BlueStacks + Jailhouse + Apple VZ + Intel KGT (Trusty) + Microsoft Azure Hyper-V + Xbox NanoVisor (Hyper-V) + SimpleVisor + Hyper-V artifact (host with Hyper-V enabled) + User-mode Linux + IBM PowerVM + Google Compute Engine (KVM) + OpenStack (KVM) + KubeVirt (KVM) + AWS Nitro System (KVM-based) + Podman + WSL + OpenVZ + ANY.RUN + Barevisor + HyperPlatform + MiniVisor + Intel TDX + LKVM + AMD SEV + AMD SEV-ES + AMD SEV-SNP + Neko Project II + NoirVisor + Qihoo 360 Sandbox + nsjail + DBVM + UTM + )"; std::exit(0); } @@ -687,7 +861,7 @@ static void general( const VM::enum_flags dynamic ) { bool notes_enabled = false; - + if (arg_bitset.test(NO_ANSI)) { detected = ("[ DETECTED ]"); not_detected = ("[NOT DETECTED]"); @@ -723,6 +897,11 @@ static void general( } #endif + const std::string hash = compute_self_sha256(); + if (!hash.empty()) { + std::cout << "SHA256: " << hash << '\n'; + } + const auto t1 = std::chrono::high_resolution_clock::now(); checker(VM::VMID, "VMID"); diff --git a/src/vmaware.hpp b/src/vmaware.hpp index 679e0aa4..b6e77c02 100644 --- a/src/vmaware.hpp +++ b/src/vmaware.hpp @@ -54,14 +54,14 @@ * * * ============================== SECTIONS ================================== - * - enums for publicly accessible techniques => line 545 - * - struct for internal cpu operations => line 716 - * - struct for internal memoization => line 3040 - * - struct for internal utility functions => line 3222 - * - struct for internal core components => line 11349 - * - start of VM detection technique list => line 4277 - * - start of public VM detection functions => line 11727 - * - start of externally defined variables => line 12735 + * - enums for publicly accessible techniques => line 546 + * - struct for internal cpu operations => line 718 + * - struct for internal memoization => line 3042 + * - struct for internal utility functions => line 3224 + * - struct for internal core components => line 11344 + * - start of VM detection technique list => line 4279 + * - start of public VM detection functions => line 11722 + * - start of externally defined variables => line 12742 * * * ============================== EXAMPLE =================================== @@ -347,8 +347,8 @@ #endif #if (VMA_CPP >= 17) #include - #include - #endif + #include +#endif #ifdef __VMAWARE_DEBUG__ #include #include @@ -659,6 +659,7 @@ struct VM { static constexpr u8 INVALID = 255; // explicit invalid technique macro static constexpr u16 base_technique_count = HIGH_THRESHOLD; // original technique count, constant on purpose (can also be used as a base count value if custom techniques are added) static constexpr u16 maximum_points = 5510; // theoretical total points if all VM detections returned true (which is practically impossible) + static constexpr u16 threshold_score = 150; // standard threshold score static constexpr u16 high_threshold_score = 300; // new threshold score from 150 to 300 if VM::HIGH_THRESHOLD flag is enabled static constexpr bool SHORTCUT = true; // macro for whether VM::core::run_all() should take a shortcut by skipping the rest of the techniques if the threshold score is already met @@ -4601,7 +4602,7 @@ struct VM { return false; } // will be used in cpuid measurements later - u16 cycle_threshold = 800; + u16 cycle_threshold = 800; // average latency of a VMX/SVM VMEXIT if (util::hyper_x() == HYPERV_ARTIFACT_VM) { cycle_threshold = 3250; // if we're running under Hyper-V, make VMAware detect nested virtualization } @@ -4689,85 +4690,6 @@ struct VM { // ================ START OF TIMING ATTACKS ================ #if (WINDOWS) - const DWORD procCount = static_cast(GetActiveProcessorCount(ALL_PROCESSOR_GROUPS)); - if (procCount == 0) return false; - - // QPC frequency - LARGE_INTEGER freq; - if (!QueryPerformanceFrequency(&freq)) // NtPowerInformation and NtQueryPerformanceCounter are avoided as some hypervisors downscale tsc only if we triggered a context switch from userspace - return false; - - // on modern Intel/AMD hardware with an invariant/constant TSC we can measure once (pin to a single core like we did before) - // and treat that value as the system TSC rate, we do not need to iterate every logical CPU - // if the CPU is old and doesn't have invariant TSC, they will not have a hybrid architecture either (cores with different frequencies) - // this was verified in both AMD and Intel, for example Intel since Nehalem - // the idea is to detect the clock speed of the fastest core, corroborate with our db if its downscaled (sign of rdtsc patch) and detect the kernel patch - // we do not use the slowest (E-Core) even if it would be more idle and probably have less kernel noise, because someone could just trap on the fast cores - // and downscale their TSC until it matches the slowest cores, defeating our detection - // this could've been prevented if theres a possibility to always ask the Windows kernel for the type of core we're running under, - // but this proved to not be reliable always, specially on AMD - - // calculates the invariant TSC base rate (on modern CPUs), not the dynamic core frequency, similar to what CallNtPowerInformation would give you - LARGE_INTEGER t1q, t2q; - thread_local u32 aux = 0; - const u64 t1 = __rdtsc(); - QueryPerformanceCounter(&t1q); // uses RDTSCP under the hood unless platformclock (a bcdedit setting) is set, which then would use HPET or ACPI PM via NtQueryPerformanceCounter - SleepEx(50, 0); // 50ms under more than 100000 tests was enough to get stable results on modern Windows systems, even under heavy load - QueryPerformanceCounter(&t2q); - const u64 t2 = __rdtscp(&aux); - - // this thread is pinned to the first CPU core due to the previous SetThreadAffinityMask call, meaning this calculation and cpu::get_cpu_base_speed() will report the same speed - // (normally) P-cores are in lower indexes, althought we don't really care about which type of vCPU VMAware will be pinned under - // pinning to index 0 is also good to keep the check compatible with dinosaur (single-core) systems - const double elapsedSec = double(t2q.QuadPart - t1q.QuadPart) / double(freq.QuadPart); // the performance counter frequency is always 10MHz when running under Hyper-V - const double tscHz = double(t2 - t1) / elapsedSec; - const double tscMHz = tscHz / 1e6; - - // even if it sounds unbelievable, this will NOT be affected even if in the BIOS the "by core usage" frequency scaling or SpeedStep (or equivalent) is enabled, and even under heavy loads - debug("TIMER: Current CPU base speed -> ", tscMHz, " MHz"); // it wont also be affected if we tell our OS to use the HPET timer instead of TSC - - if (tscMHz < 800.0 || tscMHz >= 7000) { // i9-14900KS has 6.2 GHz; 9 9950X3D has 5.7 GHz - debug("TIMER: TSC is spoofed"); - return true; - } - - const auto& info = VM::cpu::analyze_cpu(); - if (info.found) { - if (info.base_clock_mhz == 0) { - debug("TIMER: Processor's true base speed not available for this CPU"); - } - else if (info.base_clock_mhz < 800.0) { - debug("TIMER: RDTSC seems to be intercepted by an hypervisor"); - return true; - } - else { - debug("TIMER: Processor's true base speed -> ", static_cast(info.base_clock_mhz), " MHz"); - - constexpr u32 check_leaf = 0x80000007u; - constexpr double INVARIANT_TSC_DELTA = 250.0; - constexpr double LEGACY_DELTA = 650.0; - - if (cpu::is_leaf_supported(check_leaf)) { - u32 a = 0, b = 0, c = 0, d = 0; - cpu::cpuid(a, b, c, d, check_leaf); - const bool hasInvariantTsc = (d & (1u << 8)) != 0; - - if (hasInvariantTsc) { - debug("TIMER: CPU supports invariant TSC"); - if (tscMHz <= info.base_clock_mhz - INVARIANT_TSC_DELTA) return true; - } - else { - debug("TIMER: CPU does not support invariant TSC"); - if (tscMHz <= info.base_clock_mhz - LEGACY_DELTA) return true; - } - } - - constexpr double delta = 250.0; - if (tscMHz <= info.base_clock_mhz - delta) - return true; - } - } - /* TSC offseting detection */ // This detection uses two clocks and two loops, a loop and a timer that the hypervisor can spoof and a second loop/timer that the hypervisor cannot // When the TSC is "hooked", the hypervisor usually downscales the result to hide the time passed or doesnt let TSC advance for the time it was vm-exiting @@ -4776,90 +4698,6 @@ struct VM { // The hypervisor cannot easily rewind the system wall clock (second loop, QIT/KUSER_SHARED_DATA) without causing system instability (network timeouts, audio lag, etc) static thread_local volatile u64 g_sink = 0; // thread_local volatile so that it doesnt need to be captured by the lambda - // First we start by randomizing counts WITHOUT syscalls and WITHOUT using instructions that can be trapped by hypervisors, this was a hard task - struct entropy_provider { - // prevent inlining so optimizer can't fold this easily - #if (MSVC && !CLANG) - __declspec(noinline) - #else - __attribute__((noinline)) - #endif - ULONG64 operator()() const noexcept { - // TO prevent hoisting across this call - std::atomic_signal_fence(std::memory_order_seq_cst); - - // start state (golden ratio) - volatile ULONG64 v = 0x9E3779B97F4A7C15ULL; - - // mix in addresses (ASLR gives entropy but if ASLR disabled or bypassed we have some tricks still) - // Take addresses of various locals/statics and mark some volatile so they cannot be optimized away - volatile int local_static = 0; // local volatile (stack-like) - static volatile int module_static = 0; // static in function scope (image address) - auto probe_lambda = []() noexcept {}; // stack-local lambda object - uintptr_t pa = reinterpret_cast(&v); - uintptr_t pb = reinterpret_cast(&local_static); - uintptr_t pc = reinterpret_cast(&module_static); - uintptr_t pd = reinterpret_cast(&probe_lambda); - - v ^= static_cast(pa) + 0x9E3779B97F4A7C15ULL + (v << 6) + (v >> 2); - v ^= static_cast(pb) + (v << 7); - v ^= static_cast(pc) + (v >> 11); - v ^= static_cast(pd) + 0xBF58476D1CE4E5B9ULL; - - // dependent operations on volatile locals to prevent elimination - for (int i = 0; i < 24; ++i) { - volatile int stack_local = i ^ static_cast(v); - // take address each iteration and fold it in - uintptr_t la = reinterpret_cast(&stack_local); - v ^= (static_cast(la) + (static_cast(i) * 0x9E3779B97F4A7CULL)); - // dependent shifts to spread any small differences - v ^= (v << ((i & 31))); - v ^= (v >> (((i + 13) & 31))); - // so compiler can't remove the local entirely - std::atomic_signal_fence(std::memory_order_seq_cst); - } - - // final avalanche! (as said before, just in case ASLR can be folded) - v ^= (v << 13); - v ^= (v >> 7); - v ^= (v << 17); - v *= 0x2545F4914F6CDD1DULL; - v ^= (v >> 33); - - // another compiler fence to prevent hoisting results - std::atomic_signal_fence(std::memory_order_seq_cst); - - return static_cast(v); - } - }; - - // Use rejection sampling as before to avoid modulo bias - auto rng = [](ULONG64 min, ULONG64 max, auto getrand) noexcept -> ULONG64 { - const ULONG64 range = max - min + 1; - const ULONG64 limit = (~0ULL) - ((~0ULL) % range); - for (;;) { - const ULONG64 r = getrand(); - if (r < limit) return min + (r % range); - // small local mix to change subsequent outputs (still in user-mode and not a syscall) - volatile ULONG64 scrub = r; - scrub ^= (scrub << 11); - scrub ^= (scrub >> 9); - (void)scrub; - } - }; - - const entropy_provider entropyProv{}; - // QueryInterruptTime uses 100-nanosecond units, but the value in KUSER_SHARED_DATA is only updated every 15.625ms (the default system timer tick) - // For example, 10000000 iterations at @ 3GHz is around 33ms - // 100000 iterations of XOR would run too quickly (0.03ms), we need at least one tick, so we put 60000000 (20-30ms avg) - // 100000 iterations of CPUID takes roughly 100ms-200ms (which is safe for QIT) - // However, we need both loops to execute for roughly the same Wall Clock time (~60-80ms) to ensure CPU Frequency (Turbo) remains consistent - // 1. CPUID Loop: 130000 iterations * 2000 cycles = 260M cycles - // 2. XOR Loop: 260M cycles / 18 cycles per iter (volatile overhead) = 14.5M iterations - // the goal is to ensure we pass the QIT 15.6ms resolution update threshold from usermode while minimizing thermal frequency drift - const ULONG64 count_first = rng(130000ULL, 135000ULL, [&entropyProv]() noexcept { return entropyProv(); }); - const ULONG64 count_second = rng(14000000ULL, 15000000ULL, [&entropyProv]() noexcept { return entropyProv(); }); - // the reason why we use CPUID rather than RDTSC is because RDTSC is a conditionally exiting instruction, and you can modify the guest TSC without trapping it auto vm_exit = []() noexcept -> u64 { volatile int regs[4] = { 0 }; // doesn't need to be as elaborated as the next cpuid_lambda we will use to calculate the real latency @@ -4882,72 +4720,66 @@ struct VM { volatile fn_t xor_ptr = +xor_lambda; volatile u64 dummy = 0; - // run the XOR loop briefly to force CPU out of sleep states/lower frequencies - // This reduces the variance (jitter) between the two measurement loops - // and confuses hypervisors targetting this check who might try to advance TSC when XOR might be running - for (ULONG64 x = 0; x < 10000000; ++x) { - dummy += xor_ptr(); - } + // 6 ticks * 15.6ms ~= 100ms + auto accumulate_and_measure = [&](volatile fn_t func_ptr) -> u64 { + u64 total_tsc = 0; + u64 total_qit = 0; + u64 ticks_captured = 0; + constexpr u64 TARGET_TICKS = 6; + + // We continue until we have captured enough full tick windows + while (ticks_captured < TARGET_TICKS) { + u64 start_wait, now_wait; + + // Wait for QIT tick edge to avoid granularity errors + // syncing ensures we always start the measurement at the exact edge of a QIT update, eliminating jitter + QueryInterruptTime(&start_wait); + do { + _mm_pause(); // hint to CPU we-re spin-waiting + QueryInterruptTime(&now_wait); // never touches RDTSC/RDTSCP or transitions to kernel-mode, just reads from KUSER_SHARED_DATA + } while (now_wait == start_wait); + + // start of a new tick window + const u64 qit_start = now_wait; + const u64 tsc_start = __rdtsc(); + + u64 qit_current; + // run until the tick updates again + do { + // unroll slightly to reduce overhead + dummy += func_ptr(); dummy += func_ptr(); + dummy += func_ptr(); dummy += func_ptr(); + dummy += func_ptr(); dummy += func_ptr(); + + QueryInterruptTime(&qit_current); + } while (qit_current == qit_start); + + // end of tick window + const u64 tsc_end = __rdtsc(); + + const u64 delta_qit = qit_current - qit_start; + const u64 delta_tsc = tsc_end - tsc_start; + + // we need to accumulate results, the more we do it, the more the hypervisor will downclock the TSC + if (delta_qit > 0) { + total_qit += delta_qit; + total_tsc += delta_tsc; + ticks_captured++; + } + } - // first measurement - ULONG64 beforeqit = 0; - // Wait for QIT tick edge to avoid granularity errors - // if our loop takes 20ms, we might capture one tick (15.6ms reported) or two ticks (31.2ms reported) depending on exactly when we started relative to the system timer interrupt - // this causes the denominator in our ratio to jump by 50-100%, causing a delta artifact of exactly 32 (that would still be too small for the ratio diff to trigger, but anyways) - // syncing ensures we always start the measurement at the exact edge of a QIT update, eliminating this jitter - { - ULONG64 start_wait, now_wait; - QueryInterruptTime(&start_wait); - do { - _mm_pause(); // hint to CPU we-re spin-waiting - QueryInterruptTime(&now_wait); // never touches RDTSC/RDTSCP or transitions to kernel-mode, just reads from KUSER_SHARED_DATA, reason why we use it - } while (now_wait == start_wait); - beforeqit = now_wait; - } - // using only one rdtsc call here is harmless - // and it is intentional instead of manually computing with the frequency we got before (to avoid spoofing) - const ULONG64 beforetsc = __rdtsc(); - - for (ULONG64 x = 0; x < count_first; ++x) { - dummy += cp_ptr(); // this loop will be intercepted by a RDTSC trap, downscaling our TSC - } - - // the kernel routine that backs up this api runs at CLOCK_LEVEL(13), only preempted by IPI, POWER_LEVEL and NMIs - // meaning it's highly accurate even with kernel noise, hence we don't need cluster or median computations to get precise ratios - ULONG64 afterqit = 0; - QueryInterruptTime(&afterqit); - const ULONG64 aftertsc = __rdtsc(); - - const ULONG64 dtsc1 = aftertsc - beforetsc; - const ULONG64 dtq1 = afterqit - beforeqit; - const ULONG64 firstRatio = (dtq1 != 0) ? (dtsc1 / dtq1) : 0ULL; - - // second measurement - ULONG64 beforeqit2 = 0; - // wait for QIT tick edge for the second measurement as well - { - ULONG64 start_wait, now_wait; - QueryInterruptTime(&start_wait); - do { - _mm_pause(); - QueryInterruptTime(&now_wait); - } while (now_wait == start_wait); - beforeqit2 = now_wait; - } - const ULONG64 beforetsc2 = __rdtsc(); + // Total TSC Cycles / Total QIT Units + if (total_qit == 0) return 0; + return total_tsc / total_qit; + }; - for (ULONG64 x = 0; x < count_second; ++x) { - dummy += xor_ptr(); // this loop won't be intercepted, it never switches to kernel-mode - } - VMAWARE_UNUSED(dummy); + // first measurement (CPUID / VMEXIT) + const ULONG64 firstRatio = accumulate_and_measure(cp_ptr); - ULONG64 afterqit2 = 0; - QueryInterruptTime(&afterqit2); - const ULONG64 aftertsc2 = __rdtsc(); + // second measurement (XOR / ALU) + const ULONG64 secondRatio = accumulate_and_measure(xor_ptr); - const ULONG64 dtsc2 = aftertsc2 - beforetsc2; - const ULONG64 dtq2 = afterqit2 - beforeqit2; - const ULONG64 secondRatio = (dtq2 != 0) ? (dtsc2 / dtq2) : 0ULL; + VMAWARE_UNUSED(dummy); /* branchless absolute difference is like: mask = -(uint64_t)(firstRatio < secondRatio) -> 0 or 0xFFFFFFFFFFFFFFFF @@ -4973,9 +4805,8 @@ struct VM { // contrary to what someone could think, under heavy load the ratio will be more close to 0, it will also be closer to 0 if we assign CPUs to a VM in our host machine // it will increase if the BIOS/UEFI is configured to run the TSC by "core usage", which is why we use this threshold check based on a lot of empirical data // it increases because the CPUID instruction forces the CPU pipeline to drain and serialize (heavy workload), while the XOR loop is a tight arithmetic loop (throughput workload). - // CPUs will boost to different frequencies for these two scenarios (for example 4.2GHz for XOR vs 4.0GHz for CPUID) + // CPUs will boost to different frequencies for these two scenarios // A difference of 5-10% in ratio (15-30 points) or even more is normal behavior on bare metal - // lastly, we might see a small ratio always depending on which part of the tick we exactly start the measurement, which is the most important reason why we need a threshold if (difference >= 100) { debug("TIMER: An hypervisor has been detected intercepting TSC"); return true; // both ratios will always differ if TSC is downscaled, since the hypervisor can't account for the XOR/NOP loop @@ -4988,6 +4819,7 @@ struct VM { // we used a rng before running the traditional rdtsc-cpuid-rdtsc trick // sometimes not intercepted in some hvs (like VirtualBox) under compat mode + thread_local u32 aux = 0; auto cpuid = [&](unsigned int leaf) noexcept -> u64 { #if (MSVC) // make regs volatile so writes cannot be optimized out, if this isn't added and the code is compiled in release mode, cycles would be around 40 even under Hyper-V @@ -4996,7 +4828,7 @@ struct VM { _mm_lfence(); // read start time - u64 t1 = __rdtsc(); + const u64 t1 = __rdtsc(); // prevent the compiler from moving the __cpuid call before the t1 read COMPILER_BARRIER(); @@ -5006,7 +4838,7 @@ struct VM { COMPILER_BARRIER(); // the idea is to let rdtscp internally wait until cpuid is executed rather than using another memory barrier - u64 t2 = __rdtscp(&aux); + const u64 t2 = __rdtscp(&aux); // ensure the read of t2 doesn't bleed into future instructions _mm_lfence(); @@ -5026,7 +4858,7 @@ struct VM { volatile unsigned int a, b, c, d; // this differs from the code above because a, b, c and d are effectively "used" - // because the compiler must honor the write to a volatile variable. + // the compiler must honor the write to a volatile variable asm volatile("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "a"(leaf) @@ -5037,8 +4869,8 @@ struct VM { asm volatile("rdtscp" : "=a"(lo2), "=d"(hi2) :: "rcx", "memory"); asm volatile("lfence" ::: "memory"); - u64 t1 = (u64(hi1) << 32) | lo1; - u64 t2 = (u64(hi2) << 32) | lo2; + const u64 t1 = (u64(hi1) << 32) | lo1; + const u64 t2 = (u64(hi2) << 32) | lo2; return t2 - t1; #endif @@ -5053,75 +4885,200 @@ struct VM { std::vector s = samples_in; std::sort(s.begin(), s.end()); // ascending - // trivial small-sample handling + // tiny-sample short-circuits if (N <= 4) return s.front(); - // Compute gaps between consecutive sorted samples - std::vector gaps; - gaps.reserve(N - 1); - for (size_t i = 1; i < N; ++i) gaps.push_back(s[i] - s[i - 1]); - - // median gap via nth_element - std::vector gaps_copy = gaps; - const size_t mid_idx = gaps_copy.size() / 2; - std::nth_element(gaps_copy.begin(), gaps_copy.begin() + static_cast(mid_idx), gaps_copy.end()); - const u64 median_gap = gaps_copy[mid_idx]; + // median (and works for sorted input) + auto median_of_sorted = [](const std::vector& v, size_t lo, size_t hi) -> u64 { + // this is the median of v[lo..hi-1], requires 0 <= lo < hi + const size_t len = hi - lo; + if (len == 0) return 0; + const size_t mid = lo + (len / 2); + if (len & 1) return v[mid]; + return (v[mid - 1] + v[mid]) / 2; + }; - // heuristics / parameters - constexpr double GAP_FACTOR = 5.0; // require gap >= GAP_FACTOR * median_gap - constexpr u64 GAP_ABS_MIN = 50; // or an absolute minimum gap - constexpr double LOW_PERCENTILE = 0.10; // fallback if no gap found - constexpr double TRIM_RATIO = 0.10; // trimmed mean ratio inside cluster - constexpr double MIN_CLEAN_FRACTION = 0.05; // require at least this fraction to accept cluster + // the robust center: median M and MAD -> approximate sigma + const u64 M = median_of_sorted(s, 0, s.size()); + std::vector absdev; + absdev.reserve(N); + for (size_t i = 0; i < N; ++i) { + const u64 d = (s[i] > M) ? (s[i] - M) : (M - s[i]); + absdev.push_back(d); + } + std::sort(absdev.begin(), absdev.end()); + const u64 MAD = median_of_sorted(absdev, 0, absdev.size()); + // convert MAD to an approximate standard-deviation-like measure + const long double kMADtoSigma = 1.4826L; // consistent for normal approx + const long double sigma = (MAD == 0) ? 1.0L : (static_cast(MAD) * kMADtoSigma); + + // find the densest small-valued cluster by sliding a fixed-count window + // this locates the most concentrated group of samples (likely it would be the true VMEXIT cluster) + // const size_t frac_win = (N * 8 + 99) / 100; // ceil(N * 0.08) + // const size_t win = std::min(N, std::max(MIN_WIN, frac_win)); + const size_t MIN_WIN = 10; + const size_t win = std::min( + N, + std::max( + MIN_WIN, + static_cast(std::ceil(static_cast(N) * 0.08)) + ) + ); + size_t best_i = 0; + u64 best_span = (s.back() - s.front()) + 1; // large initial + for (size_t i = 0; i + win <= N; ++i) { + const u64 span = s[i + win - 1] - s[i]; + if (span < best_span) { + best_span = span; + best_i = i; + } + } + + // expand the initial window greedily while staying "tight" + // allow expansion while adding samples does not more than multiply the span by EXPAND_FACTOR + constexpr long double EXPAND_FACTOR = 1.5L; + size_t cluster_lo = best_i; + size_t cluster_hi = best_i + win; // exclusive + // expand left + while (cluster_lo > 0) { + const u64 new_span = s[cluster_hi - 1] - s[cluster_lo - 1]; + if (static_cast(new_span) <= EXPAND_FACTOR * static_cast(best_span) || + (s[cluster_hi - 1] <= (s[cluster_lo - 1] + static_cast(std::ceil(3.0L * sigma))))) { + --cluster_lo; + best_span = std::min(best_span, new_span); + } + else break; + } + // expand right + while (cluster_hi < N) { + const u64 new_span = s[cluster_hi] - s[cluster_lo]; + if (static_cast(new_span) <= EXPAND_FACTOR * static_cast(best_span) || + (s[cluster_hi] <= (s[cluster_lo] + static_cast(std::ceil(3.0L * sigma))))) { + ++cluster_hi; + best_span = std::min(best_span, new_span); + } + else break; + } + + const size_t cluster_size = (cluster_hi > cluster_lo) ? (cluster_hi - cluster_lo) : 0; + + // cluster must be reasonably dense and cover a non-negligible portion of samples, so this is pure sanity checks + const double fraction_in_cluster = static_cast(cluster_size) / static_cast(N); + const size_t MIN_CLUSTER = std::min(static_cast(std::max(5, static_cast(N / 50))), N); // at least 2% or 5 elements + if (cluster_size < MIN_CLUSTER || fraction_in_cluster < 0.02) { + // low-percentile (10th) trimmed median + const size_t fallback_count = std::max(1, static_cast(std::floor(static_cast(N) * 0.10))); + // median of lowest fallback_count elements (if fallback_count==1 that's smallest) + if (fallback_count == 1) return s.front(); + const size_t mid = fallback_count / 2; + if (fallback_count & 1) return s[mid]; + return (s[mid - 1] + s[mid]) / 2; + } + + // now we try to get a robust estimate inside the cluster, trimmed mean (10% trim) centered on cluster + const size_t trim_count = static_cast(std::floor(static_cast(cluster_size) * 0.10)); + size_t lo = cluster_lo + trim_count; + size_t hi = cluster_hi - trim_count; // exclusive + if (hi <= lo) { + // degenerate -> median of cluster + return median_of_sorted(s, cluster_lo, cluster_hi); + } + + // sum with long double to avoid overflow and better rounding + long double sum = 0.0L; + for (size_t i = lo; i < hi; ++i) sum += static_cast(s[i]); + const long double avg = sum / static_cast(hi - lo); + u64 result = static_cast(std::llround(avg)); + + // final sanity adjustments: + // if the computed result is suspiciously far from the global median (e.g., > +6*sigma) + // clamp toward the median to avoid choosing a high noisy cluster by mistake + const long double diff_from_med = static_cast(result) - static_cast(M); + if (diff_from_med > 0 && diff_from_med > (6.0L * sigma)) { + // clamp to median + 4*sigma (conservative) + result = static_cast(std::llround(static_cast(M) + 4.0L * sigma)); + } + + // Also, if result is zero (shouldn't be) or extremely small, return a smallest observed sample + if (result == 0) result = s.front(); - const u64 gap_threshold = static_cast(std::max(static_cast(GAP_ABS_MIN), std::ceil(GAP_FACTOR * static_cast(median_gap)))); + return result; + }; - // find first "large" gap and split index is i+1 (samples[0..i] is low cluster) - size_t split_index = 0; - for (size_t i = 0; i < gaps.size(); ++i) { - if (gaps[i] >= gap_threshold) { split_index = i + 1; break; } - } + // First we start by randomizing counts WITHOUT syscalls and WITHOUT using instructions that can be trapped by hypervisors, this was a hard task + struct entropy_provider { + // prevent inlining so optimizer can't fold this easily + #if (MSVC && !CLANG) + __declspec(noinline) + #else + __attribute__((noinline)) + #endif + ULONG64 operator()() const noexcept { + // TO prevent hoisting across this call + std::atomic_signal_fence(std::memory_order_seq_cst); + + // start state (golden ratio) + volatile ULONG64 v = 0x9E3779B97F4A7C15ULL; + + // mix in addresses (ASLR gives entropy but if ASLR disabled or bypassed we have some tricks still) + // Take addresses of various locals/statics and mark some volatile so they cannot be optimized away + volatile int local_static = 0; // local volatile (stack-like) + static volatile int module_static = 0; // static in function scope (image address) + auto probe_lambda = []() noexcept {}; // stack-local lambda object + uintptr_t pa = reinterpret_cast(&v); + uintptr_t pb = reinterpret_cast(&local_static); + uintptr_t pc = reinterpret_cast(&module_static); + uintptr_t pd = reinterpret_cast(&probe_lambda); + + v ^= static_cast(pa) + 0x9E3779B97F4A7C15ULL + (v << 6) + (v >> 2); + v ^= static_cast(pb) + (v << 7); + v ^= static_cast(pc) + (v >> 11); + v ^= static_cast(pd) + 0xBF58476D1CE4E5B9ULL; + + // dependent operations on volatile locals to prevent elimination + for (int i = 0; i < 24; ++i) { + volatile int stack_local = i ^ static_cast(v); + // take address each iteration and fold it in + uintptr_t la = reinterpret_cast(&stack_local); + v ^= (static_cast(la) + (static_cast(i) * 0x9E3779B97F4A7CULL)); + // dependent shifts to spread any small differences + v ^= (v << ((i & 31))); + v ^= (v >> (((i + 13) & 31))); + // so compiler can't remove the local entirely + std::atomic_signal_fence(std::memory_order_seq_cst); + } - // fallback to low-percentile if no clear gap - if (split_index == 0) { - split_index = static_cast(std::max(1, static_cast(std::floor(static_cast(N) * LOW_PERCENTILE)))); - } + // final avalanche! (as said before, just in case ASLR can be folded) + v ^= (v << 13); + v ^= (v >> 7); + v ^= (v << 17); + v *= 0x2545F4914F6CDD1DULL; + v ^= (v >> 33); - if (split_index > N) split_index = N; - size_t cluster_size = split_index; + // another compiler fence to prevent hoisting results + std::atomic_signal_fence(std::memory_order_seq_cst); - // if cluster is too small relative to N, use percentile fallback - if (static_cast(cluster_size) / static_cast(N) < MIN_CLEAN_FRACTION) { - cluster_size = static_cast(std::max(1, static_cast(std::floor(static_cast(N) * LOW_PERCENTILE)))); - if (cluster_size > N) cluster_size = N; + return static_cast(v); } + }; - // compute robust estimate for cluster s[0 ... cluster_size-1] - u64 result = 0; - if (cluster_size >= 10) { - const size_t trim = static_cast(std::floor(static_cast(cluster_size) * TRIM_RATIO)); - const size_t lo = trim; - const size_t hi = cluster_size - trim; // exclusive - if (hi <= lo) { - // degenerate which is cluster median - const size_t mid = cluster_size / 2; - result = (cluster_size % 2) ? s[mid] : ((s[mid - 1] + s[mid]) / 2); - } - else { - unsigned long long sum = 0; - for (size_t i = lo; i < hi; ++i) sum += s[i]; - result = static_cast(static_cast(sum) / static_cast(hi - lo) + 0.5); - } + // rejection sampling as before to avoid modulo bias + auto rng = [](ULONG64 min, ULONG64 max, auto getrand) noexcept -> ULONG64 { + const ULONG64 range = max - min + 1; + const ULONG64 limit = (~0ULL) - ((~0ULL) % range); + for (;;) { + const ULONG64 r = getrand(); + if (r < limit) return min + (r % range); + // small local mix to change subsequent outputs (still in user-mode and not a syscall) + volatile ULONG64 scrub = r; + scrub ^= (scrub << 11); + scrub ^= (scrub >> 9); + (void)scrub; } - else { - // small cluster which is median of cluster - const size_t mid = cluster_size / 2; - result = (cluster_size % 2) ? s[mid] : ((s[mid - 1] + s[mid]) / 2); - } - - return result; }; + const entropy_provider entropyProv{}; + // Intel leaves on an AMD CPU and viceversa will still work for this probe // for leafs like 0 that just returns static data, like "AuthenticAMD" or "GenuineIntel", a fast exit path could be made // for other leaves like the extended state that rely on dynamic system states like APIC IDs and XState, kernel data locks are required @@ -5142,12 +5099,12 @@ struct VM { }; constexpr size_t n_leaves = sizeof(leaves) / sizeof(leaves[0]); - constexpr u16 iterations = 100; + const size_t iterations = static_cast(rng(100, 200, [&entropyProv]() noexcept { return entropyProv(); })); // pre-allocate sample buffer and touch pages to avoid page faults by MMU during measurement std::vector samples; samples.resize(n_leaves * iterations); - for (size_t i = 0; i < samples.size(); ++i) samples[i] = 0; // or RtlSecureZeroMemory (memset) + for (size_t i = 0; i < samples.size(); ++i) samples[i] = 0; // or RtlSecureZeroMemory (memset) if Windows /* * We want to move our thread from the Running state to the Waiting state @@ -5192,7 +5149,7 @@ struct VM { return true; } else if (cpuid_latency <= 25) { - // cpuid is fully serializing, not even old CPUs have this low average cycles in real-world scenarios + // cpuid is fully serializing, no CPU have this low average cycles in real-world scenarios // however, in patches, zero or even negative deltas can be seen oftenly return true; } @@ -11246,6 +11203,43 @@ struct VM { return false; } + // Surface Pro models typically do not have PIT + { + const char* manufacturer = nullptr; + const char* model = nullptr; + if (util::get_manufacturer_model(&manufacturer, &model)) { + auto ci_contains = [](const char* hay, const char* needle) noexcept -> bool { + if (!hay || !needle || !*hay || !*needle) return false; + const unsigned char* h = reinterpret_cast(hay); + const unsigned char* n = reinterpret_cast(needle); + const size_t nlen = strlen(reinterpret_cast(n)); + for (; *h; ++h) { + size_t i = 0; + for (;; ++i) { + unsigned char hc = h[i]; + unsigned char nc = n[i]; + if (!nc) return false; // matched whole needle + if (!hc) break; // hay ended + // ascii lowercase + if (hc >= 'A' && hc <= 'Z') hc += 32; + if (nc >= 'A' && nc <= 'Z') nc += 32; + if (hc != nc) break; + } + if (i == nlen) return false; + } + return false; + }; + + const bool model_has_surface = ci_contains(model, "surface"); + const bool model_has_pro = ci_contains(model, "pro"); + const bool man_is_microsoft = ci_contains(manufacturer, "microsoft"); + + if (model_has_surface && (model_has_pro || man_is_microsoft)) { + return false; + } + } + } + // 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 check was removed, more info at: https://github.com/kernelwernel/VMAware/pull/616 // Here, we check for the PIT/AT timer (PC-class System Timer) @@ -11467,7 +11461,7 @@ struct VM { static u16 run_all(const flagset& flags, const bool shortcut = false) { u16 points = 0; - u16 threshold_points = 150; + u16 threshold_points = threshold_score; // set it to 300 if high threshold is enabled if (core::is_enabled(flags, HIGH_THRESHOLD)) { @@ -11818,8 +11812,7 @@ struct VM { return brand(flags); } - - static std::string brand(const flagset &flags = core::generate_default()) { + static std::string brand(const flagset& flags = core::generate_default()) { // is the multiple setting flag enabled? const bool is_multiple = core::is_enabled(flags, MULTIPLE); @@ -11832,7 +11825,8 @@ struct VM { debug("VM::brand(): returned multi brand from cache"); return memo::multi_brand::fetch(); } - } else { + } + else { if (memo::brand::is_cached()) { debug("VM::brand(): returned brand from cache"); return memo::brand::fetch(); @@ -11897,7 +11891,9 @@ struct VM { } // if there's only a single brand, return it immediately - if (active_count == 1) { + // We skip this early return if the single brand is HYPERV_ARTIFACT, + // so that the removal logic at the end of the function can process it + if (active_count == 1 && active_brands[0].first != TMP_HYPERV_ARTIFACT) { return active_brands[0].first; } @@ -11967,9 +11963,7 @@ struct VM { merge(TMP_VPC, TMP_HYPERV, TMP_HYPERV_VPC); } else if (idx_hv != -1 && idx_vpc == -1) { - // before, if counts differ (and one is 0), we erased VPC - // but if VPC is -1, it's already "erased" - // so logic handled by merge check essentially + // logic handled by merge check essentially } // Brand post-processing / merging @@ -12000,9 +11994,20 @@ struct VM { merge(TMP_VMWARE_HARD, TMP_GSX, TMP_VMWARE_HARD); merge(TMP_VMWARE_HARD, TMP_WORKSTATION, TMP_VMWARE_HARD); + // determine threshold (150 or 300) + u16 confirmed_vm_threshold = threshold_score; + if (core::is_enabled(flags, HIGH_THRESHOLD)) { + confirmed_vm_threshold = high_threshold_score; + } + + // check if Hyper-V artifact is present const int idx_art = find_index(TMP_HYPERV_ARTIFACT); - if (idx_art != -1 && score > 0) { - remove_at(idx_art); + if (idx_art != -1) { + // If score confirms it is a VM, remove the "Artifact" label (because we're in a VM, not in a host machine) + // so it falls back to "Unknown" if no other brands exist + if (score >= confirmed_vm_threshold) { + remove_at(idx_art); + } } if (active_count > 1) { @@ -12010,7 +12015,7 @@ struct VM { const brand_element_t& a, const brand_element_t& b ) { - return a.second > b.second; + return a.second > b.second; }); } @@ -12025,7 +12030,8 @@ struct VM { memo::brand::store(active_brands[0].first); debug("VM::brand(): cached brand string"); return memo::brand::fetch(); - } else { + } + else { char* buffer = memo::multi_brand::brand_cache; buffer[0] = '\0'; const size_t buf_size = sizeof(memo::multi_brand::brand_cache); @@ -12068,7 +12074,7 @@ struct VM { [[assume(points < maximum_points)]]; #endif - u16 threshold = 150; + u16 threshold = threshold_score; // if high threshold is set, the bar // will be 300. If not, leave it as 150 @@ -12111,7 +12117,7 @@ struct VM { #endif u8 percent = 0; - u16 threshold = 150; + u16 threshold = threshold_score; // set to 300 if high threshold is enabled if (core::is_enabled(flags, HIGH_THRESHOLD)) {