1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
//! # inside-vm //! //! Detect if code is running inside a virtual machine. //! //! ## How does it work //! //! Measure average cpu cycles when calling [`cpuid`](https://en.wikipedia.org/wiki/CPUID) and compare to a threshold, if the value is high assume code is running inside a VM. //! //! ## Quick Start //! //! ``` //! use inside_vm::{inside_vm, inside_vm_custom, cpuid_cycle_count_avg}; //! //! let inside = inside_vm(); //! println!("inside vm: {}", inside); //! //! let inside = inside_vm_custom(5, 100, 5, 1200); //! println!("inside vm: {}", inside); //! //! let average_cpu_cyles = cpuid_cycle_count_avg(5, 100, 5); //! println!("average __cpuid cpu cycles: {}", average_cpu_cyles); //! ``` //! //!## Credits //! //!https://evasions.checkpoint.com/techniques/timing.html#difference-vm-hosts use std::arch::x86_64::{CpuidResult, __cpuid, _rdtsc}; /// Compute cpuid cpu cycles average. /// /// Perform `low + samples + high` measurements, /// discard `low` and `high` (outliers), /// compute average using the remaining `samples` measurements. /// /// Prefer `inside_vm::inside_vm()` or `inside_vm::inside_vm_custom()`. /// /// This function uses `unsafe`. /// /// ``` /// use inside_vm::cpuid_cycle_count_avg; /// // perform 5 + 100 + 10 = 115 measurements /// // discard 5 lowest and 10 highest measurements /// // compute average over the 100 remaining measurements /// let avg = cpuid_cycle_count_avg(5, 100, 10); /// ``` pub fn cpuid_cycle_count_avg(low: usize, samples: usize, high: usize) -> u64 { let mut tsc1: u64; let mut tsc2: u64; let mut cycles: Vec<u64> = vec![]; let mut cpuid = CpuidResult { eax: 0, ebx: 0, ecx: 0, edx: 0, }; for _ in 0..(low + samples + high) { unsafe { tsc1 = _rdtsc(); // call to __cpuid would be optimized away by the compiler // if it were not for the hack used later cf HACK cpuid = __cpuid(0); tsc2 = _rdtsc(); } cycles.push(tsc2 - tsc1); } // remove low and high outliers, keep samples cycles.sort_unstable(); let cycles_without_outliers = &cycles[low..low + samples]; // compute average cycle count without outliers, make sure we do not divide by zero let avg = cycles_without_outliers.iter().sum::<u64>() / std::cmp::max(samples as u64, 1); // HACK: use __cpuid return value in order to prevent compiler from optimizing __cpuid call away avg + (cpuid.eax as u64 % 2) } /// Detect if inside vm by computing cpuid cpu cycles average and compare to `threshold`. /// /// Perform `low + samples + high` measurements, /// discard `low` and `high` (outliers), /// compute average using the remaining `samples` measurements. /// /// Compare average to `threshold`, if above return true else false. /// /// Example /// ``` /// use inside_vm::inside_vm_custom; /// let inside: bool = inside_vm::inside_vm_custom(5, 100, 5, 1_000); /// ``` pub fn inside_vm_custom(low: usize, samples: usize, high: usize, threshold: u64) -> bool { cpuid_cycle_count_avg(low, samples, high) > threshold } /// Compute cpuid cpu cycles average and compare to threshold. /// /// Same as `inside_vm_custom(5, 100, 5, 1_000)` /// /// Example: /// ``` /// use inside_vm::inside_vm; /// let inside: bool = inside_vm::inside_vm(); /// ``` pub fn inside_vm() -> bool { inside_vm_custom(5, 100, 5, 1_000) } #[cfg(test)] mod tests { use crate::cpuid_cycle_count_avg; #[test] fn test_cpuid_cycle_count_avg() { let avg = cpuid_cycle_count_avg(5, 100, 5); assert!(avg < 1000); // may fail if test is run on a VM } }