Skip to main content

vmaware/
core.rs

1//! Detection engine – mirrors vmaware_core.c.
2//!
3//! Contains the technique dispatch table, brand scoreboard, and the main
4//! `run_all` / `detect` / `get_percentage` / `get_brand` functions.
5
6use crate::memo;
7use crate::techniques;
8use crate::types::{Flagset, Technique, VMBrand, HIGH_THRESHOLD_SCORE, THRESHOLD_SCORE};
9use crate::util;
10
11use std::sync::Mutex;
12
13// ── Brand scoreboard ──────────────────────────────────────────────────────────
14
15#[derive(Clone, Default)]
16struct BrandEntry {
17    brand: VMBrand,
18    score: u32,
19}
20
21static BRAND_SCOREBOARD: Mutex<Vec<BrandEntry>> = Mutex::new(Vec::new());
22
23/// Add a brand to the scoreboard (score unchanged at 0 if brand absent).
24pub fn add_brand(brand: VMBrand) {
25    let mut board = BRAND_SCOREBOARD.lock().unwrap();
26    if !board.iter().any(|e| e.brand == brand) {
27        board.push(BrandEntry { brand, score: 0 });
28    }
29}
30
31/// Add `score` points to `brand` in the scoreboard.
32pub fn add_brand_score(brand: VMBrand, score: u32) {
33    let mut board = BRAND_SCOREBOARD.lock().unwrap();
34    if let Some(e) = board.iter_mut().find(|e| e.brand == brand) {
35        e.score += score;
36    } else {
37        board.push(BrandEntry { brand, score });
38    }
39}
40
41/// Reset the scoreboard and all memo caches.
42pub fn reset() {
43    BRAND_SCOREBOARD.lock().unwrap().clear();
44    memo::reset_all();
45}
46
47// ── Technique dispatch table ──────────────────────────────────────────────────
48
49struct TechEntry {
50    id: Technique,
51    points: u32,
52    func: fn() -> bool,
53}
54
55macro_rules! entry {
56    ($id:expr, $pts:expr, $fn:expr) => {
57        TechEntry { id: $id, points: $pts, func: $fn }
58    };
59}
60
61fn build_table() -> Vec<TechEntry> {
62    use techniques::cross::*;
63#[cfg(windows)]
64        use techniques::win::*;
65
66    #[cfg(target_os = "linux")]
67    use techniques::linux::*;
68
69    vec![
70        // ── Cross-platform CPUID techniques ──────────────────────────────────
71        entry!(Technique::Vmid,           100, vmid),
72        entry!(Technique::CpuBrand,        95, cpu_brand),
73        entry!(Technique::HypervisorBit,  100, hypervisor_bit),
74        entry!(Technique::HypervisorStr,  100, hypervisor_str),
75        entry!(Technique::BochsCpu,       100, bochs_cpu),
76        entry!(Technique::Timer,          100, timer),
77        entry!(Technique::ThreadMismatch,  50, thread_mismatch),
78        entry!(Technique::CpuidSignature,  95, cpuid_signature),
79        entry!(Technique::KgtSignature,    80, kgt_signature),
80
81        // ── Windows-only techniques ───────────────────────────────────────────
82        #[cfg(windows)]
83        entry!(Technique::Dll,             45, dll),
84        #[cfg(windows)]
85        entry!(Technique::Wine,            85, wine),
86        #[cfg(windows)]
87        entry!(Technique::PowerCapabilities, 35, power_capabilities),
88        #[cfg(windows)]
89        entry!(Technique::Gamarue,         40, gamarue),
90        #[cfg(windows)]
91        entry!(Technique::VpcInvalid,      75, vpc_invalid),
92        #[cfg(windows)]
93        entry!(Technique::VmwareStr,       45, vmware_str),
94        #[cfg(windows)]
95        entry!(Technique::VmwareBackdoor, 100, vmware_backdoor),
96        #[cfg(windows)]
97        entry!(Technique::Mutex,           85, mutex),
98        #[cfg(windows)]
99        entry!(Technique::CuckooDir,       15, cuckoo_dir),
100        #[cfg(windows)]
101        entry!(Technique::CuckooPipe,      20, cuckoo_pipe),
102        #[cfg(windows)]
103        entry!(Technique::Display,         35, display),
104        #[cfg(windows)]
105        entry!(Technique::DeviceString,    35, device_string),
106        #[cfg(windows)]
107        entry!(Technique::Drivers,         65, drivers),
108        #[cfg(windows)]
109        entry!(Technique::DiskSerial,      60, disk_serial),
110        #[cfg(windows)]
111        entry!(Technique::VirtualRegistry, 65, virtual_registry),
112        #[cfg(windows)]
113        entry!(Technique::Audio,           35, audio),
114        #[cfg(windows)]
115        entry!(Technique::AcpiSignature,   80, acpi_signature),
116        #[cfg(windows)]
117        entry!(Technique::Trap,           100, trap),
118        #[cfg(windows)]
119        entry!(Technique::Ud,             100, ud),
120        #[cfg(windows)]
121        entry!(Technique::Blockstep,      100, blockstep),
122        #[cfg(windows)]
123        entry!(Technique::BootLogo,        10, boot_logo),
124        #[cfg(windows)]
125        entry!(Technique::KernelObjects,   50, kernel_objects),
126        #[cfg(windows)]
127        entry!(Technique::Nvram,          100, nvram),
128        #[cfg(windows)]
129        entry!(Technique::Edid,            55, edid),
130        #[cfg(windows)]
131        entry!(Technique::Clock,           65, clock),
132        #[cfg(windows)]
133        entry!(Technique::Handles,         25, handles),
134        #[cfg(windows)]
135        entry!(Technique::VirtualProcessors, 30, virtual_processors),
136        #[cfg(windows)]
137        entry!(Technique::HypervisorQuery, 65, hypervisor_query),
138        #[cfg(windows)]
139        entry!(Technique::Ivshmem,         65, ivshmem),
140        #[cfg(windows)]
141        entry!(Technique::GpuCapabilities, 35, gpu_capabilities),
142        #[cfg(windows)]
143        entry!(Technique::CpuHeuristic,    30, cpu_heuristic),
144        #[cfg(windows)]
145        entry!(Technique::DbvmHypercall,  100, dbvm_hypercall),
146        #[cfg(windows)]
147        entry!(Technique::Msr,             65, msr),
148        #[cfg(windows)]
149        entry!(Technique::KvmInterception, 65, kvm_interception),
150        #[cfg(windows)]
151        entry!(Technique::Breakpoint,      65, breakpoint),
152
153        // ── Linux + Windows techniques ────────────────────────────────────────
154        #[cfg(any(windows, target_os = "linux"))]
155        entry!(Technique::SystemRegisters, 35, system_registers),
156        #[cfg(any(windows, target_os = "linux"))]
157        entry!(Technique::Firmware,        80, firmware),
158        #[cfg(any(windows, target_os = "linux"))]
159        entry!(Technique::Devices,         35, devices),
160        #[cfg(any(windows, target_os = "linux"))]
161        entry!(Technique::Azure,           25, azure),
162
163        // ── Linux-only techniques ─────────────────────────────────────────────
164        #[cfg(target_os = "linux")]
165        entry!(Technique::SmbiosVmBit,     35, smbios_vm_bit),
166        #[cfg(target_os = "linux")]
167        entry!(Technique::Kmsg,            30, kmsg),
168        #[cfg(target_os = "linux")]
169        entry!(Technique::Cvendor,         65, cvendor),
170        #[cfg(target_os = "linux")]
171        entry!(Technique::QemuFwCfg,       40, qemu_fw_cfg),
172        #[cfg(target_os = "linux")]
173        entry!(Technique::Systemd,         30, systemd),
174        #[cfg(target_os = "linux")]
175        entry!(Technique::Ctype,           25, ctype),
176        #[cfg(target_os = "linux")]
177        entry!(Technique::Dockerenv,       95, dockerenv),
178        #[cfg(target_os = "linux")]
179        entry!(Technique::Dmidecode,       55, dmidecode),
180        #[cfg(target_os = "linux")]
181        entry!(Technique::Dmesg,           65, dmesg),
182        #[cfg(target_os = "linux")]
183        entry!(Technique::Hwmon,           25, hwmon),
184        #[cfg(target_os = "linux")]
185        entry!(Technique::LinuxUserHost,   35, linux_user_host),
186        #[cfg(target_os = "linux")]
187        entry!(Technique::VmwareIomem,     65, vmware_iomem),
188        #[cfg(target_os = "linux")]
189        entry!(Technique::VmwareIoports,   65, vmware_ioports),
190        #[cfg(target_os = "linux")]
191        entry!(Technique::VmwareScsi,      40, vmware_scsi),
192        #[cfg(target_os = "linux")]
193        entry!(Technique::VmwareDmesg,     50, vmware_dmesg),
194        #[cfg(target_os = "linux")]
195        entry!(Technique::QemuVirtualDmi,  40, qemu_virtual_dmi),
196        #[cfg(target_os = "linux")]
197        entry!(Technique::QemuUsb,         20, qemu_usb),
198        #[cfg(target_os = "linux")]
199        entry!(Technique::HypervisorDir,   40, hypervisor_dir),
200        #[cfg(target_os = "linux")]
201        entry!(Technique::UmlCpu,          80, uml_cpu),
202        #[cfg(target_os = "linux")]
203        entry!(Technique::VboxModule,      65, vbox_module),
204        #[cfg(target_os = "linux")]
205        entry!(Technique::SysinfoProc,     25, sysinfo_proc),
206        #[cfg(target_os = "linux")]
207        entry!(Technique::DmiScan,         55, dmi_scan),
208        #[cfg(target_os = "linux")]
209        entry!(Technique::PodmanFile,      95, podman_file),
210        #[cfg(target_os = "linux")]
211        entry!(Technique::WslProc,         95, wsl_proc),
212        #[cfg(target_os = "linux")]
213        entry!(Technique::FileAccessHistory, 15, file_access_history),
214        #[cfg(target_os = "linux")]
215        entry!(Technique::Mac,             30, mac),
216        #[cfg(target_os = "linux")]
217        entry!(Technique::NsjailPid,       95, nsjail_pid),
218        #[cfg(target_os = "linux")]
219        entry!(Technique::BluestacksFolders, 60, bluestacks_folders),
220        #[cfg(target_os = "linux")]
221        entry!(Technique::AmdSevMsr,       95, amd_sev_msr),
222        #[cfg(target_os = "linux")]
223        entry!(Technique::Temperature,     25, temperature),
224        #[cfg(target_os = "linux")]
225        entry!(Technique::Processes,       30, processes),
226        // ThreadCount: Linux + macOS
227        #[cfg(any(target_os = "linux", target_os = "macos"))]
228        entry!(Technique::ThreadCount,     35, techniques::cross::thread_count),
229    ]
230}
231
232// ── run_all / detect / percentage / brand ─────────────────────────────────────
233
234/// Run all enabled techniques (those set in `flags`).
235/// Returns the accumulated VM score.
236/// If `shortcut` is true, stops early once THRESHOLD_SCORE is reached.
237pub fn run_all(flags: Flagset, shortcut: bool) -> u32 {
238    let table = build_table();
239    let mut score: u32 = 0;
240
241    for entry in &table {
242        if !flags.is_empty() && !flags.is_set(entry.id) {
243            continue;
244        }
245
246        // Skip unsupported platform techniques
247        if util::is_unsupported(entry.id) {
248            continue;
249        }
250
251        let tech_id = entry.id as u8;
252
253        // Return cached result if available
254        if let Some(cached) = memo::cache_fetch(tech_id) {
255            if cached.result {
256                score += cached.points;
257            }
258            if shortcut && score >= THRESHOLD_SCORE {
259                return score;
260            }
261            continue;
262        }
263
264        // Run the technique
265        let result = (entry.func)();
266        let pts = if result { entry.points } else { 0 };
267        score += pts;
268
269        // Determine the brand from scoreboard (take highest)
270        let brand = best_brand();
271        memo::cache_store(tech_id, result, entry.points, brand);
272
273        if shortcut && score >= THRESHOLD_SCORE {
274            return score;
275        }
276    }
277
278    score
279}
280
281/// Returns true if the VM score meets or exceeds THRESHOLD_SCORE.
282pub fn detect(flags: Flagset) -> bool {
283    run_all(flags, true) >= THRESHOLD_SCORE
284}
285
286/// Returns the VM confidence percentage (0–100, clamped).
287pub fn get_percentage(flags: Flagset) -> u8 {
288    let score = run_all(flags, false);
289    let pct = (score * 100) / HIGH_THRESHOLD_SCORE;
290    pct.min(100) as u8
291}
292
293/// Returns the highest-scoring VM brand from the scoreboard.
294pub fn get_brand(flags: Flagset) -> VMBrand {
295    run_all(flags, false);
296    best_brand()
297}
298
299fn best_brand() -> VMBrand {
300    let board = BRAND_SCOREBOARD.lock().unwrap();
301    board
302        .iter()
303        .max_by_key(|e| e.score)
304        .map(|e| e.brand)
305        .unwrap_or(VMBrand::Invalid)
306}
307
308/// Returns the list of all brands that scored > 0.
309pub fn get_detected_brands(flags: Flagset) -> Vec<VMBrand> {
310    run_all(flags, false);
311    let board = BRAND_SCOREBOARD.lock().unwrap();
312    board
313        .iter()
314        .filter(|e| e.score > 0)
315        .map(|e| e.brand)
316        .collect()
317}
318
319/// Returns the number of techniques that returned true.
320pub fn detected_technique_count(flags: Flagset) -> usize {
321    run_all(flags, false);
322    // Count cached results that are true
323    let mut count = 0usize;
324    for id in 0..Technique::COUNT as u8 {
325        if let Some(r) = memo::cache_fetch(id) {
326            if r.result {
327                count += 1;
328            }
329        }
330    }
331    count
332}