1use crate::core::add_brand_score;
8use crate::cpu;
9use crate::memo;
10use crate::types::VMBrand;
11use crate::util;
12
13pub fn vmid() -> bool {
18 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
19 return false;
20
21 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
22 {
23 for leaf in (0x4000_0000u32..=0x4000_0010).step_by(0x10) {
25 let (found, brand) = cpu::vmid_template(leaf);
26 if found {
27 add_brand_score(brand, 0);
28 return true;
29 }
30 }
31 false
32 }
33}
34
35pub fn cpu_brand() -> bool {
39 let brand = match memo::get_cpu_brand() {
41 Some(b) => b,
42 None => {
43 let b = cpu::cpu_brand_string();
44 memo::set_cpu_brand(b.clone());
45 b
46 }
47 };
48
49 if brand.is_empty() {
50 return false;
51 }
52
53 static KEYWORDS: &[(&str, VMBrand)] = &[
54 ("QEMU", VMBrand::QEMU),
55 ("KVM", VMBrand::KVM),
56 ("Virtual CPU", VMBrand::QEMUKVM),
57 ("VMware", VMBrand::VMware),
58 ("VirtualBox", VMBrand::VBox),
59 ("Hyper-V", VMBrand::HyperV),
60 ("BOCHS", VMBrand::Bochs),
61 ("Xen", VMBrand::Xen),
62 ("bhyve", VMBrand::Bhyve),
63 ("ACRN", VMBrand::ACRN),
64 ("GenuineIntel", VMBrand::Invalid), ("AuthenticAMD", VMBrand::Invalid), ];
67
68 let brand_lc = brand.to_lowercase();
69 for &(kw, vm_brand) in KEYWORDS {
70 if brand_lc.contains(&kw.to_lowercase()) {
71 if vm_brand != VMBrand::Invalid {
72 add_brand_score(vm_brand, 0);
73 return true;
74 }
75 }
76 }
77 false
78}
79
80pub fn hypervisor_bit() -> bool {
89 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
90 return false;
91
92 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
93 {
94 use crate::types::HyperXState;
95 if crate::util::hyper_x() == HyperXState::Enlightenment {
97 return false;
98 }
99 let ecx = cpu::cpuid(1, 0).ecx;
100 (ecx >> 31) & 1 != 0
101 }
102}
103
104pub fn hypervisor_str() -> bool {
111 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
112 return false;
113
114 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
115 {
116 use crate::types::HyperXState;
117 if crate::util::hyper_x() == HyperXState::Enlightenment {
118 return false;
119 }
120
121 if !cpu::is_leaf_supported(0x4000_0000) {
122 return false;
123 }
124 let r = cpu::cpuid(0x4000_0000, 0);
125 let vendor = cpu::vendor_string(r.ebx, r.ecx, r.edx);
126
127 let is_cpu_vendor = vendor.starts_with("GenuineIntel")
129 || vendor.starts_with("AuthenticAMD")
130 || vendor.starts_with("HygonGenuine");
131
132 if !vendor.trim_matches('\0').is_empty() && !is_cpu_vendor {
133 let (found, brand) = cpu::vmid_template(0x4000_0000);
134 if found {
135 add_brand_score(brand, 0);
136 }
137 return true;
138 }
139 false
140 }
141}
142
143pub fn bochs_cpu() -> bool {
147 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
148 return false;
149
150 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
151 {
152 if !cpu::is_intel() {
153 return false;
154 }
155
156 let brand = match memo::get_cpu_brand() {
160 Some(b) => b,
161 None => {
162 let b = cpu::cpu_brand_string();
163 memo::set_cpu_brand(b.clone());
164 b
165 }
166 };
167
168 if brand.contains("BOCHSCPU") || brand.to_lowercase().contains("bochs") {
169 add_brand_score(VMBrand::Bochs, 0);
170 return true;
171 }
172
173 let steps = cpu::fetch_steppings();
175 if steps.family == 6 && steps.model == 2 && steps.extmodel == 0 {
176 if brand.is_empty() {
179 add_brand_score(VMBrand::Bochs, 0);
180 return true;
181 }
182 }
183
184 false
185 }
186}
187
188pub fn timer() -> bool {
193 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
194 return false;
195
196 #[cfg(target_arch = "x86_64")]
197 {
198 use std::arch::x86_64::{_mm_lfence, _rdtsc};
199
200 const ITERATIONS: u64 = 10;
201 const THRESHOLD_CYCLES: u64 = 750;
202
203 let mut total: u64 = 0;
204 unsafe {
205 for _ in 0..ITERATIONS {
206 _mm_lfence();
207 let t1 = _rdtsc();
208 std::arch::x86_64::__cpuid(0);
210 _mm_lfence();
211 let t2 = _rdtsc();
212 total = total.saturating_add(t2.wrapping_sub(t1));
213 }
214 }
215 let avg = total / ITERATIONS;
216 avg > THRESHOLD_CYCLES
217 }
218
219 #[cfg(target_arch = "x86")]
220 {
221 false }
224}
225
226pub fn thread_mismatch() -> bool {
232 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
233 return false;
234
235 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
236 {
237 let brand = match memo::get_cpu_brand() {
238 Some(b) => b,
239 None => {
240 let b = cpu::cpu_brand_string();
241 memo::set_cpu_brand(b.clone());
242 b
243 }
244 };
245
246 let expected = match cpu::lookup_expected_threads(&brand) {
248 Some(n) => n,
249 None => return false, };
251
252 let actual = util::get_logical_cpu_count();
253
254 expected != actual && actual < expected
256 }
257}
258
259pub fn cpuid_signature() -> bool {
263 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
264 return false;
265
266 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
267 {
268 let eax1 = cpu::cpuid(1, 0).eax;
270 let family = (eax1 >> 8) & 0xF;
271 let model = (eax1 >> 4) & 0xF;
272
273 static SIG_TABLE: &[(u32, u32, VMBrand)] = &[
278 (0x4000_0000, 0x4000_0001, VMBrand::VMware),
280 ];
281
282 let _ = SIG_TABLE;
283
284 if cpu::is_leaf_supported(0x4000_0010) {
286 let r = cpu::cpuid(0x4000_0010, 0);
287 if r.eax != 0 {
288 add_brand_score(VMBrand::VMware, 0);
290 return true;
291 }
292 }
293
294 if cpu::is_leaf_supported(0x4000_0001) {
296 let r = cpu::cpuid(0x4000_0001, 0);
297 let base_vendor = {
300 let r0 = cpu::cpuid(0x4000_0000, 0);
301 cpu::vendor_string(r0.ebx, r0.ecx, r0.edx)
302 };
303 if base_vendor.contains("KVMKVMKVM") {
304 let kvm_features = r.eax;
305 if kvm_features != 0 {
306 add_brand_score(VMBrand::KVM, 0);
307 return true;
308 }
309 }
310 }
311
312 let _ecx1 = cpu::cpuid(1, 0).ecx;
315 if family == 15 && model == 0 {
320 add_brand_score(VMBrand::Bochs, 0);
321 return true;
322 }
323
324 false
325 }
326}
327
328pub fn kgt_signature() -> bool {
332 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
333 return false;
334
335 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
336 {
337 if !cpu::is_leaf_supported(0x4000_0001) {
338 return false;
339 }
340 let eax = cpu::cpuid(0x4000_0001, 0).eax;
341 if eax == 0x4D49_4B54 {
343 add_brand_score(VMBrand::IntelKGT, 0);
344 return true;
345 }
346 false
347 }
348}
349
350#[cfg(any(target_os = "linux", target_os = "macos"))]
354pub fn thread_count() -> bool {
355 let os_threads = util::get_logical_cpu_count();
356
357 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
358 {
359 if cpu::is_leaf_supported(0x0B) {
360 let r = cpu::cpuid(0x0B, 1); let cpuid_threads = r.ebx & 0xFFFF;
362 if cpuid_threads > 0 && cpuid_threads != os_threads {
363 return true;
364 }
365 }
366 }
367
368 os_threads <= 1
370}
371
372#[cfg(any(windows, target_os = "linux"))]
376pub fn azure() -> bool {
377 #[cfg(windows)]
378 {
379 crate::techniques::win::azure()
380 }
381 #[cfg(not(windows))]
382 {
383 let mfr = util::read_file("/sys/class/dmi/id/chassis_vendor")
385 .unwrap_or_default();
386 let host = util::read_file("/etc/hostname")
387 .unwrap_or_default();
388 if mfr.contains("Microsoft") && host.to_lowercase().contains("azure") {
389 add_brand_score(VMBrand::AzureHyperV, 0);
390 return true;
391 }
392 false
393 }
394}
395
396#[cfg(any(windows, target_os = "linux"))]
398pub fn system_registers() -> bool {
399 #[cfg(windows)]
400 {
401 crate::techniques::win::system_registers()
402 }
403 #[cfg(not(windows))]
404 {
405 false
406 }
407}
408
409#[cfg(any(windows, target_os = "linux"))]
411pub fn firmware() -> bool {
412 #[cfg(windows)]
413 {
414 crate::techniques::win::firmware()
415 }
416 #[cfg(not(windows))]
417 {
418 static PATHS: &[(&str, &[(&str, VMBrand)])] = &[
420 ("/sys/class/dmi/id/bios_vendor", &[
421 ("SeaBIOS", VMBrand::QEMU),
422 ("VBOX", VMBrand::VBox),
423 ("bochs", VMBrand::Bochs),
424 ("Parallels", VMBrand::Parallels),
425 ]),
426 ("/sys/class/dmi/id/sys_vendor", &[
427 ("QEMU", VMBrand::QEMU),
428 ("VMware", VMBrand::VMware),
429 ("VirtualBox", VMBrand::VBox),
430 ("Xen", VMBrand::Xen),
431 ("KVM", VMBrand::KVM),
432 ("Microsoft", VMBrand::HyperV),
433 ("innotek", VMBrand::VBox),
434 ("Parallels", VMBrand::Parallels),
435 ]),
436 ("/sys/class/dmi/id/product_name", &[
437 ("Virtual Machine", VMBrand::HyperV),
438 ("VMware", VMBrand::VMware),
439 ("VirtualBox", VMBrand::VBox),
440 ("KVM", VMBrand::KVM),
441 ("BHYVE", VMBrand::Bhyve),
442 ("QEMU", VMBrand::QEMU),
443 ("Bochs", VMBrand::Bochs),
444 ("Standard PC", VMBrand::QEMU),
445 ]),
446 ];
447
448 for &(path, table) in PATHS {
449 if let Some(content) = util::read_file(path) {
450 let lower = content.to_lowercase();
451 for &(kw, brand) in table {
452 if lower.contains(&kw.to_lowercase()) {
453 add_brand_score(brand, 0);
454 return true;
455 }
456 }
457 }
458 }
459 false
460 }
461}
462
463#[cfg(any(windows, target_os = "linux"))]
465pub fn devices() -> bool {
466 #[cfg(windows)]
467 {
468 crate::techniques::win::devices()
469 }
470 #[cfg(not(windows))]
471 {
472 static VM_VENDOR_IDS: &[(&str, VMBrand)] = &[
474 ("0x15ad", VMBrand::VMware), ("0x80ee", VMBrand::VBox), ("0x1af4", VMBrand::QEMU), ("0x1414", VMBrand::HyperV), ("0x5853", VMBrand::Xen), ("0x1ab8", VMBrand::Parallels),("0x1b36", VMBrand::QEMU), ];
482
483 let pci_dir = std::path::Path::new("/sys/bus/pci/devices");
484 if !pci_dir.exists() {
485 return false;
486 }
487 if let Ok(entries) = std::fs::read_dir(pci_dir) {
488 for entry in entries.flatten() {
489 let uevent = entry.path().join("vendor");
490 if let Ok(data) = std::fs::read_to_string(&uevent) {
491 let lower = data.trim().to_lowercase();
492 for &(vid, brand) in VM_VENDOR_IDS {
493 if lower == vid {
494 add_brand_score(brand, 0);
495 return true;
496 }
497 }
498 }
499 }
500 }
501 false
502 }
503}