1use crate::engine::TechniqueResult;
5use crate::cpu;
6use crate::brands;
7use crate::util;
8
9pub fn vmid() -> TechniqueResult {
12 #[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
13 {
14 return TechniqueResult::not_detected();
15 }
16
17 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
18 {
19 let leaves_to_check: Vec<u32> = {
21 let mut v = vec![0u32];
22 for leaf in (0x4000_0000u32..=0x4000_0100).step_by(0x100) {
23 v.push(leaf);
24 }
25 v
26 };
27
28 for &leaf_id in &leaves_to_check {
29 if leaf_id != 0 && !cpu::is_leaf_supported(leaf_id) {
30 continue;
31 }
32
33 let brand_str = cpu::cpu_manufacturer(leaf_id);
34 if brand_str.is_empty() {
35 continue;
36 }
37
38 if brand_str == "Microsoft Hv" {
40 return TechniqueResult::detected_with_brands(brands::HYPERV, brands::VPC);
41 }
42 if brand_str.contains("KVM") {
43 return TechniqueResult::detected_with_brand(brands::KVM);
44 }
45
46 let brand_map: &[(&str, &str)] = &[
47 ("VMwareVMware", brands::VMWARE),
48 ("VBoxVBoxVBox", brands::VBOX),
49 ("TCGTCGTCGTCG", brands::QEMU),
50 ("XenVMMXenVMM", brands::XEN),
51 ("Linux KVM Hv", brands::KVM_HYPERV),
52 (" prl hyperv ", brands::PARALLELS),
53 (" lrpepyh vr", brands::PARALLELS),
54 ("bhyve bhyve ", brands::BHYVE),
55 ("BHyVE BHyVE ", brands::BHYVE),
56 ("ACRNACRNACRN", brands::ACRN),
57 (" QNXQVMBSQG ", brands::QNX),
58 ("___ NVMM ___", brands::NVMM),
59 ("OpenBSDVMM58", brands::BSD_VMM),
60 ("HAXMHAXMHAXM", brands::INTEL_HAXM),
61 ("UnisysSpar64", brands::UNISYS),
62 ("SRESRESRESRE", brands::LMHS),
63 ("EVMMEVMMEVMM", brands::INTEL_KGT),
64 ("LKVMLKVMLKVM", brands::LKVM),
65 ("Neko Project", brands::NEKO_PROJECT),
66 ("NoirVisor ZT", brands::NOIRVISOR),
67 ];
68
69 for &(pattern, brand) in brand_map {
70 if brand_str == pattern {
71 return TechniqueResult::detected_with_brand(brand);
72 }
73 }
74
75 if brand_str.contains("QXNQSBMV") {
76 return TechniqueResult::detected_with_brand(brands::QNX);
77 }
78 if brand_str.contains("Apple VZ") {
79 return TechniqueResult::detected_with_brand(brands::APPLE_VZ);
80 }
81 }
82
83 TechniqueResult::not_detected()
84 }
85}
86
87pub fn cpu_brand() -> TechniqueResult {
89 let brand = cpu::get_brand();
90 if brand == "Unknown" || brand.is_empty() {
91 return TechniqueResult::not_detected();
92 }
93
94 let lower = brand.to_lowercase();
95
96 let vm_strings: &[(&str, &str)] = &[
97 ("qemu", brands::QEMU),
98 ("virtual", brands::NULL_BRAND),
99 ("vmware", brands::VMWARE),
100 ("virtualbox", brands::VBOX),
101 ("vbox", brands::VBOX),
102 ("kvm", brands::KVM),
103 ("xen", brands::XEN),
104 ("bochs", brands::BOCHS),
105 ("parallels", brands::PARALLELS),
106 ("bhyve", brands::BHYVE),
107 ("hyperv", brands::HYPERV),
108 ];
109
110 for &(pattern, brand) in vm_strings {
111 if lower.contains(pattern) {
112 if brand == brands::NULL_BRAND {
113 return TechniqueResult::detected();
114 }
115 return TechniqueResult::detected_with_brand(brand);
116 }
117 }
118
119 TechniqueResult::not_detected()
120}
121
122pub fn hypervisor_bit() -> TechniqueResult {
125 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
126 {
127 let r = cpu::cpuid(1, 0);
128 let hv_bit = (r.ecx >> 31) & 1;
129 if hv_bit == 1 {
130 return TechniqueResult::detected();
131 }
132 }
133 TechniqueResult::not_detected()
134}
135
136pub fn hypervisor_str() -> TechniqueResult {
139 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
140 {
141 if !cpu::is_leaf_supported(cpu::leaf::HYPERVISOR) {
142 return TechniqueResult::not_detected();
143 }
144
145 let brand = cpu::cpu_manufacturer(cpu::leaf::HYPERVISOR);
146 if brand.len() > 2 && !brand.trim().is_empty() {
147 return TechniqueResult::detected();
148 }
149 }
150 TechniqueResult::not_detected()
151}
152
153pub fn timer() -> TechniqueResult {
155 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
156 {
157 let iterations = 10u32;
159 let mut total: u64 = 0;
160
161 for _ in 0..iterations {
162 let start: u64;
163 let end: u64;
164
165 #[cfg(target_arch = "x86_64")]
166 unsafe {
167 start = std::arch::x86_64::_rdtsc();
168 let _ = std::arch::x86_64::__cpuid(0);
170 end = std::arch::x86_64::_rdtsc();
171 }
172
173 #[cfg(target_arch = "x86")]
174 unsafe {
175 start = std::arch::x86::_rdtsc();
176 let _ = std::arch::x86::__cpuid(0);
177 end = std::arch::x86::_rdtsc();
178 }
179
180 total += end.wrapping_sub(start);
181 }
182
183 let avg = total / iterations as u64;
184 if avg > 1000 {
186 return TechniqueResult::detected();
187 }
188 }
189 TechniqueResult::not_detected()
190}
191
192pub fn thread_count() -> TechniqueResult {
194 let count = util::thread_count();
195 if count <= 2 {
196 return TechniqueResult::detected();
197 }
198 TechniqueResult::not_detected()
199}
200
201pub fn thread_mismatch() -> TechniqueResult {
203 let brand = cpu::get_brand();
204 let count = util::thread_count();
205
206 if brand == "Unknown" || brand.is_empty() {
207 return TechniqueResult::not_detected();
208 }
209
210 let lower = brand.to_lowercase();
213
214 let expected_min = if lower.contains("ryzen 9") || lower.contains("i9-") || lower.contains("xeon") {
215 8
216 } else if lower.contains("ryzen 7") || lower.contains("i7-") {
217 6
218 } else if lower.contains("ryzen 5") || lower.contains("i5-") {
219 4
220 } else {
221 return TechniqueResult::not_detected();
222 };
223
224 if count < expected_min {
225 return TechniqueResult::detected();
226 }
227
228 TechniqueResult::not_detected()
229}
230
231pub fn cpuid_signature() -> TechniqueResult {
233 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
234 {
235 if !cpu::is_leaf_supported(0x4000_0001) {
236 return TechniqueResult::not_detected();
237 }
238
239 let r = cpu::cpuid(0x4000_0001, 0);
240 if r.eax != 0 {
242 return TechniqueResult::detected();
243 }
244 }
245 TechniqueResult::not_detected()
246}
247
248pub fn bochs_cpu() -> TechniqueResult {
250 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
251 {
252 if !cpu::is_leaf_supported(cpu::leaf::PROC_EXT) {
253 return TechniqueResult::not_detected();
254 }
255
256 let r = cpu::cpuid(cpu::leaf::PROC_EXT, 0);
257
258 if cpu::is_leaf_supported(cpu::leaf::AMD_EASTER_EGG) {
260 let easter = cpu::cpuid(cpu::leaf::AMD_EASTER_EGG, 0);
261 if easter.ecx == 0x4d414821 {
263 return TechniqueResult::detected_with_brand(brands::BOCHS);
264 }
265 }
266
267 if cpu::is_intel() {
270 let amd_features = r.ecx & (1 << 6); if amd_features != 0 {
272 return TechniqueResult::detected_with_brand(brands::BOCHS);
273 }
274 }
275 }
276 TechniqueResult::not_detected()
277}
278
279pub fn kgt_signature() -> TechniqueResult {
281 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
282 {
283 if !cpu::is_leaf_supported(cpu::leaf::HYPERVISOR) {
284 return TechniqueResult::not_detected();
285 }
286
287 let brand = cpu::cpu_manufacturer(cpu::leaf::HYPERVISOR);
288 if brand == "EVMMEVMMEVMM" {
289 return TechniqueResult::detected_with_brand(brands::INTEL_KGT);
290 }
291 }
292 TechniqueResult::not_detected()
293}
294
295#[cfg(any(target_os = "linux", target_os = "windows"))]
297pub fn firmware() -> TechniqueResult {
298 #[cfg(target_os = "linux")]
299 {
300 let acpi_paths = [
302 "/sys/firmware/acpi/tables/DSDT",
303 "/sys/firmware/acpi/tables/SLIC",
304 "/sys/firmware/acpi/tables/MSDM",
305 ];
306
307 for path in &acpi_paths {
308 if let Ok(data) = std::fs::read(path) {
309 let s = String::from_utf8_lossy(&data).to_lowercase();
310 if s.contains("vmware") {
311 return TechniqueResult::detected_with_brand(brands::VMWARE);
312 }
313 if s.contains("vbox") || s.contains("virtualbox") {
314 return TechniqueResult::detected_with_brand(brands::VBOX);
315 }
316 if s.contains("qemu") {
317 return TechniqueResult::detected_with_brand(brands::QEMU);
318 }
319 if s.contains("hyper-v") || s.contains("microsoft") {
320 return TechniqueResult::detected_with_brand(brands::HYPERV);
321 }
322 }
323 }
324
325 if let Some(vendor) = util::linux::read_dmi_field("sys_vendor") {
327 let v = vendor.to_lowercase();
328 if v.contains("vmware") { return TechniqueResult::detected_with_brand(brands::VMWARE); }
329 if v.contains("qemu") { return TechniqueResult::detected_with_brand(brands::QEMU); }
330 if v.contains("virtualbox") || v.contains("innotek") { return TechniqueResult::detected_with_brand(brands::VBOX); }
331 if v.contains("microsoft") { return TechniqueResult::detected_with_brand(brands::HYPERV); }
332 if v.contains("xen") { return TechniqueResult::detected_with_brand(brands::XEN); }
333 if v.contains("parallels") { return TechniqueResult::detected_with_brand(brands::PARALLELS); }
334 }
335 }
336
337 #[cfg(target_os = "windows")]
338 {
339 if let Some(bios_vendor) = util::win::read_registry_string(
341 "HKLM",
342 r"HARDWARE\DESCRIPTION\System\BIOS",
343 "SystemManufacturer",
344 ) {
345 let v = bios_vendor.to_lowercase();
346 if v.contains("vmware") { return TechniqueResult::detected_with_brand(brands::VMWARE); }
347 if v.contains("qemu") { return TechniqueResult::detected_with_brand(brands::QEMU); }
348 if v.contains("virtualbox") || v.contains("innotek") { return TechniqueResult::detected_with_brand(brands::VBOX); }
349 if v.contains("microsoft") { return TechniqueResult::detected_with_brand(brands::HYPERV); }
350 if v.contains("xen") { return TechniqueResult::detected_with_brand(brands::XEN); }
351 if v.contains("parallels") { return TechniqueResult::detected_with_brand(brands::PARALLELS); }
352 }
353 }
354
355 TechniqueResult::not_detected()
356}
357
358#[cfg(any(target_os = "linux", target_os = "windows"))]
360pub fn pci_devices() -> TechniqueResult {
361 #[cfg(target_os = "linux")]
362 {
363 let vm_pci_vendors: &[(&str, &str)] = &[
365 ("15ad", brands::VMWARE), ("80ee", brands::VBOX), ("1af4", brands::QEMU), ("1414", brands::HYPERV), ("5853", brands::XEN), ("1ab8", brands::PARALLELS),];
372
373 if let Ok(entries) = std::fs::read_dir("/sys/bus/pci/devices") {
374 for entry in entries.flatten() {
375 let vendor_path = format!("{}/vendor", entry.path().display());
376 if let Ok(vendor) = std::fs::read_to_string(&vendor_path) {
377 let vendor = vendor.trim().trim_start_matches("0x").to_lowercase();
378 for &(vid, brand) in vm_pci_vendors {
379 if vendor == vid {
380 return TechniqueResult::detected_with_brand(brand);
381 }
382 }
383 }
384 }
385 }
386 }
387
388 #[cfg(target_os = "windows")]
389 {
390 let subkeys = util::win::enum_registry_subkeys(
392 "HKLM",
393 r"SYSTEM\CurrentControlSet\Enum\PCI",
394 );
395 for key in &subkeys {
396 let lower = key.to_lowercase();
397 if lower.contains("ven_15ad") { return TechniqueResult::detected_with_brand(brands::VMWARE); }
398 if lower.contains("ven_80ee") { return TechniqueResult::detected_with_brand(brands::VBOX); }
399 if lower.contains("ven_1af4") { return TechniqueResult::detected_with_brand(brands::QEMU); }
400 if lower.contains("ven_1414") { return TechniqueResult::detected_with_brand(brands::HYPERV); }
401 if lower.contains("ven_5853") { return TechniqueResult::detected_with_brand(brands::XEN); }
402 }
403 }
404
405 TechniqueResult::not_detected()
406}
407
408#[cfg(any(target_os = "linux", target_os = "windows"))]
410pub fn system_registers() -> TechniqueResult {
411 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
414 {
415 let r = cpu::cpuid(1, 0);
417 let hv_present = (r.ecx >> 31) & 1 == 1;
418
419 if hv_present {
420 let start: u64;
423 let end: u64;
424
425 #[cfg(target_arch = "x86_64")]
426 unsafe {
427 start = std::arch::x86_64::_rdtsc();
428 let _ = std::arch::x86_64::__cpuid(0x4000_0000);
429 end = std::arch::x86_64::_rdtsc();
430 }
431 #[cfg(target_arch = "x86")]
432 unsafe {
433 start = std::arch::x86::_rdtsc();
434 let _ = std::arch::x86::__cpuid(0x4000_0000);
435 end = std::arch::x86::_rdtsc();
436 }
437
438 let elapsed = end.wrapping_sub(start);
439 if elapsed > 500 {
440 return TechniqueResult::detected();
441 }
442 }
443 }
444 TechniqueResult::not_detected()
445}
446
447#[cfg(any(target_os = "linux", target_os = "windows"))]
449pub fn azure() -> TechniqueResult {
450 if let Some(hostname) = util::get_hostname() {
451 let lower = hostname.to_lowercase();
453 if lower.contains("azure") || lower.starts_with("az-") {
454 return TechniqueResult::detected_with_brand(brands::AZURE_HYPERV);
455 }
456 }
457 TechniqueResult::not_detected()
458}