Chapter 53

VM and Sandbox Detection

Automated malware analysis sandboxes run your sample in a controlled virtual machine, capture its behavior, and produce a report within 5 minutes. If your implant can detect it's running in a VM or sandbox, it can stay dormant until it reaches a real victim machine. Detection methods fall into four categories: hardware artifact checks (VM-specific CPU behavior, CPUID results, registry keys), behavioral fingerprints (mouse movement, running processes, disk artifacts), timing side-channels, and network environment checks. No single method is reliable — combine at least three from different categories.

CPU and Hypervisor Artifacts

CPUID instruction reveals hypervisor presence
  CPUID leaf 0x40000000 — "Hypervisor leaf":
  ─────────────────────────────────────────────────────────────────────────
  On real hardware:  CPUID(0x40000000) → EAX=0, EBX=ECX=EDX=0 (no response)
  On VMware:         EBX:ECX:EDX = "VMwaVMwaVMwa" (vendor string)
  On VirtualBox:     EBX:ECX:EDX = "VBoxVBoxVBox"
  On Hyper-V:        EBX:ECX:EDX = "Microsoft Hv" (shortened)
  On QEMU/KVM:       EBX:ECX:EDX = "TCGTCGTCGTCG" or "KVMKVMKVM\0\0\0"
  On Xen:            EBX:ECX:EDX = "XenVMMXenVMM"
  
  CPUID bit 31 of ECX after CPUID(1) — "Hypervisor Present" bit:
  ─────────────────────────────────────────────────────────────────────────
  Real hardware: ECX bit 31 = 0
  Any hypervisor: ECX bit 31 = 1 (all major hypervisors set this)
  
  Evasion by sandboxes:
  ─────────────────────────────────────────────────────────────────────────
  Modern sandboxes (Cuckoo, Joe Sandbox) clear the hypervisor bit and
  return zeros for leaf 0x40000000 to avoid detection.
  They mask their identity by spoofing CPUID results.
  
  Counter-counter-evasion:
  ─────────────────────────────────────────────────────────────────────────
  Even if CPUID is spoofed, other artifacts remain:
    - Timing: VM I/O is slower (disk read in VM >> disk read on metal)
    - Device names: VirtualBox installs "VBoxMiniRdrDN" network device
    - Registry: HKLM\SOFTWARE\VMware, Inc. or \Oracle\VirtualBox
    - Services: VBoxService.exe, vmtoolsd.exe running as processes
/* vm_detect.c — Multi-method VM and sandbox detection */
#include <windows.h>
#include <intrin.h>    /* __cpuid() intrinsic */
#include <stdio.h>
#include <string.h>

/* ── Method 1: CPUID hypervisor leaf ────────────────────────────────── */
static BOOL detect_cpuid_hypervisor(void) {
    int cpuid_result[4];
    __cpuid(cpuid_result, 0x40000000);
    /* EBX:ECX:EDX form a 12-char string for known hypervisors */
    char vendor[13] = {0};
    *(int*)(vendor + 0) = cpuid_result[1]; /* EBX */
    *(int*)(vendor + 4) = cpuid_result[2]; /* ECX */
    *(int*)(vendor + 8) = cpuid_result[3]; /* EDX */
    
    /* Check for known VM vendors */
    if (strstr(vendor, "VMwa") || strstr(vendor, "VBox") ||
        strstr(vendor, "KVMK") || strstr(vendor, "Xen") ||
        strstr(vendor, "Micr")) {
        printf("[VM] CPUID hypervisor vendor: %.12s\n", vendor);
        return TRUE;
    }
    return FALSE;
}

/* ── Method 2: CPUID hypervisor present bit ──────────────────────────── */
static BOOL detect_cpuid_hvbit(void) {
    int info[4];
    __cpuid(info, 1);
    return (info[2] >> 31) & 1;  /* ECX bit 31 */
}

/* ── Method 3: Registry artifacts ────────────────────────────────────── */
static BOOL detect_registry_vm(void) {
    const char *vm_keys[] = {
        "SOFTWARE\\VMware, Inc.\\VMware Tools",
        "SOFTWARE\\Oracle\\VirtualBox Guest Additions",
        "SOFTWARE\\Microsoft\\Virtual Machine\\Guest\\Parameters",  /* Hyper-V */
        "SYSTEM\\CurrentControlSet\\Services\\VBoxGuest",
        "SYSTEM\\CurrentControlSet\\Services\\vmhgfs",
        NULL
    };
    for (int i = 0; vm_keys[i]; i++) {
        HKEY hkey;
        if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, vm_keys[i],
                          0, KEY_READ, &hkey) == ERROR_SUCCESS) {
            RegCloseKey(hkey);
            printf("[VM] Registry key found: HKLM\\%s\n", vm_keys[i]);
            return TRUE;
        }
    }
    return FALSE;
}

/* ── Method 4: Process artifact detection ────────────────────────────── */
static BOOL detect_processes_vm(void) {
    const wchar_t *vm_procs[] = {
        L"vmtoolsd.exe",   /* VMware Tools */
        L"vmwaretray.exe",
        L"VBoxService.exe",/* VirtualBox */
        L"VBoxTray.exe",
        L"vmsrvc.exe",     /* Virtual PC */
        L"vmusrvc.exe",
        L"prl_tools.exe",  /* Parallels */
        L"xenservice.exe", /* Xen */
        NULL
    };
    HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32W pe = { .dwSize = sizeof(pe) };
    BOOL found = FALSE;
    if (Process32FirstW(snap, &pe)) {
        do {
            for (int i = 0; vm_procs[i]; i++) {
                if (_wcsicmp(pe.szExeFile, vm_procs[i]) == 0) {
                    wprintf(L"[VM] Process found: %ls\n", pe.szExeFile);
                    found = TRUE;
                }
            }
        } while (Process32NextW(snap, &pe));
    }
    CloseHandle(snap);
    return found;
}

/* ── Method 5: Hardware device name artifacts ─────────────────────────── */
/*
 * Virtual network adapters have VM-specific MAC address prefixes.
 * VMware NIC MACs start with 00:0C:29 or 00:50:56
 * VirtualBox NIC MACs start with 08:00:27
 * Check adapter description strings for VM keywords.
 */
static BOOL detect_network_adapter_vm(void) {
    HKEY hKey;
    if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,
            "SYSTEM\\CurrentControlSet\\Control\\Class\\"
            "{4D36E972-E325-11CE-BFC1-08002bE10318}",
            0, KEY_READ, &hKey) != ERROR_SUCCESS)
        return FALSE;

    BOOL found = FALSE;
    for (DWORD idx = 0; ; idx++) {
        char subkey[64], desc[256];
        DWORD subkey_len = sizeof(subkey);
        if (RegEnumKeyExA(hKey, idx, subkey, &subkey_len,
                          NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
            break;
        HKEY hSub;
        if (RegOpenKeyExA(hKey, subkey, 0, KEY_READ, &hSub) == ERROR_SUCCESS) {
            DWORD desc_len = sizeof(desc);
            if (RegQueryValueExA(hSub, "DriverDesc", NULL, NULL,
                                  (LPBYTE)desc, &desc_len) == ERROR_SUCCESS) {
                if (strstr(desc, "VMware") || strstr(desc, "VirtualBox") ||
                    strstr(desc, "VBOX") || strstr(desc, "Xen") ||
                    strstr(desc, "Virtio")) {
                    printf("[VM] Network adapter: %s\n", desc);
                    found = TRUE;
                }
            }
            RegCloseKey(hSub);
        }
    }
    RegCloseKey(hKey);
    return found;
}

/* ── Method 6: User activity check (sandbox has no real user) ────────── */
/*
 * Automated sandboxes typically have:
 *   - Very short uptime (just booted for this sample)
 *   - No foreground window changes (no one's using the machine)
 *   - Mouse cursor at a fixed position (no human moving it)
 *   - No recently opened files (empty recent files list)
 * 
 * Check uptime and mouse movement:
 */
static BOOL detect_no_user_activity(void) {
    /* Short uptime: sandbox reboots before each sample */
    DWORD uptime_ms = GetTickCount();
    if (uptime_ms < 5 * 60 * 1000) { /* less than 5 minutes uptime */
        printf("[Sandbox] System uptime: %lu ms (very short — likely sandbox)\n", uptime_ms);
        return TRUE;
    }

    /* Mouse hasn't moved: get position twice with a delay and compare */
    POINT p1, p2;
    GetCursorPos(&p1);
    Sleep(2000);
    GetCursorPos(&p2);
    if (p1.x == p2.x && p1.y == p2.y) {
        /* No mouse movement in 2 seconds — possibly automated */
        /* Note: This is weak alone — real machines also have idle periods */
        printf("[Sandbox] No mouse movement detected in 2s\n");
        return TRUE;
    }
    return FALSE;
}

/* ── Combined verdict ─────────────────────────────────────────────────── */
BOOL is_vm_or_sandbox(void) {
    int score = 0;
    score += detect_cpuid_hypervisor()      ? 2 : 0; /* Strong signal */
    score += detect_cpuid_hvbit()           ? 1 : 0;
    score += detect_registry_vm()           ? 2 : 0; /* Strong signal */
    score += detect_processes_vm()          ? 2 : 0; /* Strong signal */
    score += detect_network_adapter_vm()    ? 2 : 0;
    score += detect_no_user_activity()      ? 1 : 0; /* Weak alone */

    printf("[*] VM/Sandbox score: %d\n", score);
    return score >= 3; /* Require 3+ points to avoid false positives */
}

Questions & Answers

How do modern sandboxes try to hide their VM nature, and which detection methods survive this?

Modern sandboxes like Cuckoo (with dodger module), Joe Sandbox, and Any.run deploy extensive VM cloaking: they clear the hypervisor CPUID bit, spoof VMware/VBox registry keys by deleting or renaming them, kill VM tool processes before the sample starts, and simulate mouse movement by auto-clicking at random intervals. Against these defenses: timing-based detection survives because VM disk I/O and CPU timing anomalies are structural (the hypervisor itself can't fully hide), MAC address checking survives if the sandbox doesn't randomize MAC prefixes, and uptime-based detection survives sandboxes that don't reboot long enough before the sample. The most robust anti-sandbox approach targets things the sandbox can't easily fake: the number of CPU cores (sandboxes often use 1-2 cores while real workstations have 8+), total RAM (sandboxes often have 2-4 GB), and the number of recently accessed files in the user's profile (zero for a fresh sandbox).

What's the risk of false-positives from VM detection? Some legitimate users run in VMs.

Very real and important. Enterprise users increasingly work in virtual desktops (VDI environments, Azure Virtual Desktop, Citrix), and developers run Windows VMs for testing. Triggering on any VM would cause the implant to fail on a large percentage of legitimate enterprise targets. Mitigation: raise the score threshold, require multiple independent confirmations, and don't gate on VM detection alone. The real target of VM detection is sandboxes that run fresh with no user history, not production VMs used daily by employees. Combining VM detection with: "has the user logged in before" (profile has files older than 7 days), "is this a domain-joined machine" (LDAP/AD membership), and "is this machine name matching known target patterns" gives a much higher confidence verdict than raw VM detection alone.

Can you use the screen resolution or display as a sandbox detection signal?

Yes — and it's surprisingly effective. Sandbox VMs are typically configured with minimal resources: common sandbox screen resolutions are 1024x768 or 800x600, which are almost never seen on real user workstations in 2026 (where 1920x1080 or higher is standard). GetSystemMetrics(SM_CXSCREEN) and GetSystemMetrics(SM_CYSCREEN) give you the primary display dimensions. A system with SM_CXSCREEN < 1200 is likely a sandbox or very old hardware. More reliable: check the number of monitors (GetSystemMetrics(SM_CMONITORS)) — real enterprise workstations often have 2+ monitors; sandboxes almost always have exactly 1. Combine with pixel depth: 32-bit color is standard on real machines, but some sandboxes use 16-bit to save resources.