Sandbox and VM Detection
Sandboxes and dynamic analysis environments run malware in an instrumented VM to observe behavior. Evasion means detecting this environment and changing behavior — either doing nothing (appearing benign) or exiting cleanly. From a detection engineering perspective, sandbox evasion is a high-confidence indicator: a sample that checks for VM artifacts, timing anomalies, or analyst-specific system properties is almost certainly malicious. The check itself is the signal.
A threat actor submits a dropper to VirusTotal. The sample executes in Cuckoo, ANY.RUN, and Joe Sandbox — and appears to do nothing. IR receives the sample and manually executes it on a domain-joined workstation with a real user profile: it immediately beacons out to a C2. The analyst is confused. Understanding why tells you exactly what to look for in sandbox-evasion telemetry to catch this class of malware.
Sandbox Landscape
| Sandbox | Type | Key artifacts left | Evasion difficulty |
|---|---|---|---|
| VirusTotal/Cuckoo | Open-source, VirtualBox/QEMU | VBox drivers, specific hostnames, small disk size | Low — well-documented artifacts |
| ANY.RUN | Interactive, cloud-based | Analyst interaction simulated; specific UA, network range | Medium |
| Joe Sandbox | Commercial, enterprise-grade | Bare-metal option available; advanced anti-evasion | High |
| VMware Workstation | Corp analyst VM | VMware tools DLLs, vmtoolsd.exe, SCSI disk string | Low |
| Hyper-V (Azure sandbox) | Microsoft Cloud | Hyper-V BIOS string, vmbusres.dll, synthetic NIC | Medium |
| Bare metal | Physical hardware analysis | No VM artifacts; physical CPU timing | Very high |
VM Artifact Detection
// Check multiple VM indicators. Exit or sleep if any detect sandbox/VM.
// Returns TRUE if running in a virtualized/sandboxed environment.
#include <windows.h>
#include <intrin.h>
BOOL CheckVmwareArtifacts() {
// Check for VMware registry keys
HKEY hKey;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\VMware, Inc.\\VMware Tools",
0, KEY_READ, &hKey) == ERROR_SUCCESS) {
RegCloseKey(hKey); return TRUE;
}
// Check for VMware SCSI disk adapter string
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"SYSTEM\\CurrentControlSet\\Enum\\SCSI"
L"\\Disk&Ven_VMware_&Prod_VMware_Virtual_S",
0, KEY_READ, &hKey) == ERROR_SUCCESS) {
RegCloseKey(hKey); return TRUE;
}
// Check for vmtoolsd.exe running process
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32W pe = { sizeof(pe) };
if (Process32FirstW(hSnap, &pe)) {
do {
if (_wcsicmp(pe.szExeFile, L"vmtoolsd.exe") == 0 ||
_wcsicmp(pe.szExeFile, L"vboxservice.exe") == 0) {
CloseHandle(hSnap); return TRUE;
}
} while (Process32NextW(hSnap, &pe));
}
CloseHandle(hSnap);
return FALSE;
}
BOOL CheckCpuidHypervisor() {
// CPUID leaf 1: ECX bit 31 = hypervisor present bit
// Hypervisors set this; bare metal does not.
int cpuInfo[4];
__cpuid(cpuInfo, 1);
return (cpuInfo[2] >> 31) & 1; // ECX[31]
}
BOOL CheckVboxArtifacts() {
// VirtualBox guest additions DLL in system32
return GetFileAttributesW(L"C:\\Windows\\System32\\VBoxHook.dll")
!= INVALID_FILE_ATTRIBUTES;
}
Timing and CPU-Based Checks
// Sandbox accelerates execution — NtDelayExecution (Sleep) returns early.
// Sandboxes patch the sleep call to return immediately.
// Check: call Sleep(N), measure actual elapsed time with QPC.
BOOL CheckSleepAcceleration() {
LARGE_INTEGER freq, before, after;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&before);
Sleep(1500); // sleep 1.5 seconds
QueryPerformanceCounter(&after);
double elapsed = (double)(after.QuadPart - before.QuadPart)
/ freq.QuadPart;
// If less than 1.2s elapsed, sleep was patched — sandbox
return (elapsed < 1.2);
}
BOOL CheckRdtscDelta() {
// RDTSC: reads CPU timestamp counter.
// In a VM under heavy instrumentation, RDTSC delta for a tight loop
// is anomalously large (VM exits are expensive).
// Compare tight loop vs expected cycle count.
UINT64 t1 = __rdtsc();
for (volatile int i = 0; i < 100; i++);
UINT64 t2 = __rdtsc();
// On real hardware: ~200-500 cycles for 100 iterations.
// In VMware/QEMU with full instrumentation: can be 5-50x higher.
return (t2 - t1) > 10000;
}
BOOL CheckCpuCoreCount() {
// Sandboxes often run with 1-2 virtual CPUs.
// Most legitimate enterprise workstations have 4+ logical cores.
SYSTEM_INFO si;
GetSystemInfo(&si);
return si.dwNumberOfProcessors < 4;
}
User Interaction Checks
// Sandboxes simulate but do not truly replicate a live user session.
// Check for mouse movement, foreground window changes, screen resolution,
// recent user activity, and clipboard content.
BOOL CheckMouseMovement() {
// Measure mouse position twice; if no movement after delay → likely sandbox
POINT p1, p2;
GetCursorPos(&p1);
Sleep(3000);
GetCursorPos(&p2);
return (p1.x == p2.x && p1.y == p2.y); // no movement = sandbox indicator
}
BOOL CheckScreenResolution() {
// Sandboxes often use 800x600 or 1024x768.
// Real workstations: 1920x1080 or higher.
int w = GetSystemMetrics(SM_CXSCREEN);
int h = GetSystemMetrics(SM_CYSCREEN);
return (w < 1024 || h < 768);
}
BOOL CheckRecentFileAccess() {
// Sandboxes have fresh user profiles with no recent document history.
// A real workstation has files in Recent Items / recent Documents.
WCHAR recentPath[MAX_PATH];
SHGetSpecialFolderPathW(NULL, recentPath, CSIDL_RECENT, FALSE);
HANDLE hFind;
WIN32_FIND_DATAW fd;
WCHAR pattern[MAX_PATH];
swprintf_s(pattern, L"%s\\*", recentPath);
hFind = FindFirstFileW(pattern, &fd);
int count = 0;
if (hFind != INVALID_HANDLE_VALUE) {
do { count++; } while (FindNextFileW(hFind, &fd) && count < 5);
FindClose(hFind);
}
return (count < 3); // fewer than 3 recent items = suspicious
}
BOOL CheckDiskSize() {
// Sandboxes often have small disk images (20-60GB total).
// Real enterprise machines: 250GB-1TB+.
ULARGE_INTEGER freeBytes, totalBytes, totalFreeBytes;
GetDiskFreeSpaceExW(L"C:\\", &freeBytes, &totalBytes, &totalFreeBytes);
// Less than 60GB total → likely a sandbox image
return totalBytes.QuadPart < ((UINT64)60 * 1024 * 1024 * 1024);
}
Domain and Environment Checks
// Advanced targeted malware checks whether it's running in the intended
// target environment — wrong domain or organization = don't detonate.
BOOL CheckDomainJoined() {
DSROLE_PRIMARY_DOMAIN_INFO_BASIC* info;
DWORD r = DsRoleGetPrimaryDomainInformation(NULL,
DsRolePrimaryDomainInfoBasic, (PBYTE*)&info);
if (r != ERROR_SUCCESS) return FALSE;
BOOL joined = (info->MachineRole != DsRole_RoleStandaloneWorkstation &&
info->MachineRole != DsRole_RoleStandaloneServer);
DsRoleFreeMemory(info);
return !joined; // not domain-joined = sandbox or test env
}
BOOL CheckUsernameNotAnalyst() {
// Known sandbox/analyst usernames:
static const WCHAR* sandboxUsers[] = {
L"malware", L"virus", L"analyst", L"sandbox",
L"cuckoo", L"test", L"admin", L"user", NULL
};
WCHAR username[256]; DWORD sz = 256;
GetUserNameW(username, &sz);
for (int i = 0; sandboxUsers[i]; i++)
if (_wcsicmp(username, sandboxUsers[i]) == 0) return TRUE;
return FALSE;
}
// Combined evasion check:
BOOL IsSandbox(void) {
return CheckVmwareArtifacts()
|| CheckVboxArtifacts()
|| CheckCpuidHypervisor()
|| CheckSleepAcceleration()
|| CheckCpuCoreCount()
|| CheckScreenResolution()
|| CheckRecentFileAccess()
|| CheckDomainJoined()
|| CheckUsernameNotAnalyst();
}
// In main():
if (IsSandbox()) { ExitProcess(0); } // silent exit — appears benign to sandbox
Detection Engineering
title: Sandbox Evasion — Hypervisor and VM Artifact Checks at Runtime
logsource:
product: windows
category: process_access
detection:
selection:
EventID: 10
TargetImage|endswith:
- '\vmtoolsd.exe'
- '\vboxservice.exe'
filter_legit:
SourceImage|contains: '\VirtualBox'
condition: selection AND NOT filter_legit
level: medium
title: CPUID Hypervisor Check (via Unusual Process)
logsource:
product: windows
service: sysmon
detection:
selection:
EventID: 25 # Process tampering
condition: selection
falsepositives: [system utilities, hardware diagnostics]
level: low
-- Practical: most sandbox evasion is detected in sandbox itself via behavioral
-- heuristics. For production EDR, focus on: samples that call registry queries for
-- VMware/VBox artifacts and then exit cleanly — that exit is the indicator.
-- MDE KQL: process that queries sandbox-relevant registry keys then exits quickly
DeviceRegistryEvents
| where RegistryKey has_any ("VMware", "VirtualBox", "VBOX")
| join kind=inner (
DeviceProcessEvents
| where ActionType == "ProcessCreated"
| project DeviceName, ProcessId, InitTime = Timestamp
) on DeviceName
| join kind=inner (
DeviceProcessEvents
| where ActionType == "ProcessStopped"
| project DeviceName, ProcessId, StopTime = Timestamp
) on DeviceName, ProcessId
| extend lifespan_sec = datetime_diff("second", StopTime, InitTime)
| where lifespan_sec < 10 // very short process life after VM check = evasion
| project DeviceName, InitiatingProcessFileName, lifespan_sec, RegistryKey
Q&A
Modern sandbox environments counter-evade by simulating mouse movement and faking system uptime — how does a sophisticated sample defeat these counter-measures, and where does the cat-and-mouse end?
Modern sandboxes like Joe Sandbox and ANY.RUN deploy counter-evasion by simulating convincing user activity: synthetic mouse movements at human-plausible intervals, browser history pre-populated with realistic sites, fake recently-accessed documents, inflated uptime values, and synthetic user-agent strings that match real enterprise browsers. Against these, naive sandbox checks (mouse movement delta, uptime, recent files count) are increasingly unreliable.
A sophisticated sample escalates to checks that are harder to fake at scale. Timing checks against the wall clock rather than system APIs are more reliable: if a sample knows it was built on a specific date and checks that the Windows event log has entries spanning multiple weeks before its first execution, a sandbox that was provisioned fresh for this sample cannot fake years of event log history without prohibitive storage overhead per sample. Similarly, checking the installed software list for realistic enterprise tooling — common in real environments (Office, Adobe Reader, VPN clients, corporate EDR agents) but rarely present in sandbox images — distinguishes real environments from provisioned sandboxes.
The most sophisticated modern approach is environmental fingerprinting: the sample doesn't look for VM artifacts directly but instead contacts a C2 endpoint that knows the target organization's public IP range, domain configuration, or external hostname patterns. If the sample is running in a network that doesn't match the expected target's AS number or public IP geolocation, it exits. No sandbox can replicate the target's exact network egress profile. This is exactly what APT implants (SUNBURST, FinFisher) have done: check a specific external resource before activating.
The cat-and-mouse ends in the defender's favor only with behavioral analysis that doesn't depend on the sample running its malicious code at all. Sandboxes that analyze API call sequences at import resolution time, that capture memory layouts before any instruction executes, or that use CPU-level hardware tracing (Intel PT) to observe every branch can see the conditional checks themselves and flag the "check for sandbox then exit" pattern as malicious regardless of whether the payload stage runs. From a detection engineering standpoint, the presence of these sandbox-check API call patterns in the import table or call graph is itself a high-fidelity malware indicator, independent of what the payload does.