#Introduction
AV/EDR evasion can broadly be categorized into three classes, each solving a different problem and carrying different trade-offs in reliability, noise, and operational risk. Understanding which class you are operating in is more important than memorizing individual tricks, because the “best” evasion technique is entirely dependent on context, not cleverness.
The three categories are:
1 Static Evasion
Techniques designed to avoid detection before execution. This includes obfuscation, packing, encryption, signature mutation, and build-time transformations. Static evasion is evaluated against scanners and pre-execution inspection (disk, email, web gateways), but once execution begins, its value rapidly diminishes.
2 Behavioral / Runtime Evasion
Techniques that alter or suppress observable behavior during execution. This includes delayed execution, staged loading, API indirection, parent/child process shaping, unhooking, and sandbox awareness. These methods target EDR telemetry and behavioral engines and are typically more durable than static techniques, but also more complex and environment-sensitive.
3 Environmental / Contextual Evasion
Techniques that determine whether execution should occur at all. This includes sandbox, VM, debugger, and analyst environment detection, as well as user-presence and system-profile checks. These techniques don’t make payloads stealthy — they make them selective, trading coverage for survivability.
When evaluating which evasion techniques to use, the goal is not maximum stealth, but maximum reliability for the given engagement. A technique that works perfectly in a lab but fails on a real endpoint is operationally worse than a noisier method that executes consistently.
Effective evaluation should consider:
- Where detection is expected to occur (pre-execution vs runtime)
- How deterministic the target environment is
- What failure looks like (blocked, alerted, or silent)
- Whether evasion failure is acceptable or mission-ending
In practice, most successful operations combine techniques from multiple categories, using static evasion to reach execution, behavioral evasion to reduce visibility, and environmental evasion to avoid wasting capability where it won’t succeed.
#Sandbox Detection Through Folders
Many hypervisors and automated analysis environments leave behind predictable filesystem artifacts on guest systems. These folders are typically created to support guest tools, instrumentation, logging, or analysis workflows and are rarely present on genuine end-user workstations outside of lab or testing environments.
From an offensive perspective, searching for these folders can be a dynamic method to determine if you are running in a sandboxed or virtual environment. This should not be used as a standalone verdict. When combined with additional telemetry, it can be a powerful tool to know when and where to execute.
BOOL DirectoryExists(TCHAR* szPath)
{
DWORD dwAttrib = GetFileAttributes(szPath);
return (dwAttrib != INVALID_FILE_ATTRIBUTES) && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY);
}BOOL check_for_qemu()
{
TCHAR szProgramFile[MAX_PATH];
TCHAR szPath[MAX_PATH] = _T("");
TCHAR szTarget[MAX_PATH] = _T("C:\\Program Files\\QEMU\\");
if (IsWoW64())
ExpandEnvironmentStrings(_T("%ProgramW6432%"), szProgramFile, ARRAYSIZE(szProgramFile));
else
SHGetSpecialFolderPath(NULL, szProgramFile, CSIDL_PROGRAM_FILES, FALSE);
PathCombine(szPath, szProgramFile, szTarget);
return is_DirectoryExists(szPath);
}SeeAl-khaser
| Product | Type | OS | Folder Path | Notes |
|---|---|---|---|---|
| CWSandbox | Sandbox | Windows | C:\analysis | Default working directory for CW Sandbox detonations |
| Cuckoo Sandbox | Sandbox | Windows | C:\cuckoo | Common Cuckoo analysis root |
| Cuckoo Sandbox | Sandbox | Windows | C:\cuckoo\agent | Cuckoo agent working directory |
| Any.Run | Sandbox | Windows | C:\Program Files\ANY.RUN | Hosted sandbox artifacts |
| Joe Sandbox | Sandbox | Windows | C:\JoeSandbox | Default Joe Sandbox directory |
| Joe Sandbox | Sandbox | Windows | C:\ProgramData\JoeSandbox | Runtime and analysis data |
| Hybrid Analysis | Sandbox | Windows | C:\analysis | Shared path across multiple sandboxes |
| VMware | Hypervisor | Windows | C:\Program Files\VMware | VMware Tools and services |
| VMware | Hypervisor | Windows | C:\Program Files (x86)\VMware | 32-bit VMware components |
| VMware | Hypervisor | Windows | C:\ProgramData\VMware | VMware runtime data |
| VirtualBox | Hypervisor | Windows | C:\Program Files\Oracle\VirtualBox Guest Additions | Guest Additions install path |
| VirtualBox | Hypervisor | Windows | C:\Program Files\Oracle\VirtualBox | Host-side VirtualBox installs |
| VirtualBox | Hypervisor | Windows | C:\ProgramData\VirtualBox | VM metadata and state |
| Hyper-V | Hypervisor | Windows | C:\ProgramData\Microsoft\Windows\Hyper-V | Hyper-V configuration |
| Hyper-V | Hypervisor | Windows | C:\Users\Public\Documents\Hyper-V\Virtual Hard Disks | Default VHD storage |
| QEMU | Hypervisor | Windows | C:\Program Files\qemu | QEMU user-mode installs |
| QEMU | Hypervisor | Windows | C:\Program Files\QEMU | Alternate casing seen in labs |
| Sandboxie | Sandbox | Windows | C:\Sandbox | Default Sandboxie container path |
| Sandboxie | Sandbox | Windows | C:\Program Files\Sandboxie-Plus | Modern Sandboxie installs |
| ThreatExpert | Sandbox | Windows | C:\ThreatExpert | Legacy sandbox still fingerprinted |
| Anubis | Sandbox | Windows | C:\anubis | Academic sandbox artifacts |
| FireEye | Sandbox | Windows | C:\Program Files\FireEye | FireEye endpoint/sandbox tooling |
| FireEye | Sandbox | Windows | C:\ProgramData\FireEye | FireEye runtime data |
| Windows Sandbox | Sandbox | Windows | C:\Users\WDAGUtilityAccount | Windows Defender Application Guard |
| Windows Sandbox | Sandbox | Windows | C:\ProgramData\Microsoft\Windows Defender | Defender/Sandbox artifacts |
#Sandbox Detection Through Files
Similar to Folders many hypervisors and automated analysis environments leave behind predictable filesystem artifacts on guest systems. These files are typically created to support guest tools, instrumentation, logging, or analysis workflows and are rarely present on genuine end-user workstations outside of lab or testing environments.
From an offensive perspective, searching for these files can be a dynamic method to determine if you are running in a sandboxed or virtual environment. This should not be used as a standalone verdict. When combined with additional telemetry, it can be a powerful tool to know when and where to execute.
# This function is exactly the same as DirectoryExists.
BOOL FileExists(TCHAR* szPath)
{
DWORD dwAttrib = GetFileAttributes(szPath);
return (dwAttrib != INVALID_FILE_ATTRIBUTES) && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY);
}VOID Check_For_Files()
{
// List of file paths (relative to %WINDIR%) to check for VM / sandbox artifacts
TCHAR* szPaths[] = {
_T("system32\\drivers\\prleth.sys"),
_T("system32\\drivers\\vmhgfs.sys"),
_T("system32\\drivers\\csagent.sys")
};
// Calculate number of paths in szPaths
WORD dwlength = sizeof(szPaths) / sizeof(szPaths[0]);
// Resolve the Windows directory (e.g., C:\Windows)
TCHAR szWinDir[MAX_PATH] = _T("");
TCHAR szPath[MAX_PATH] = _T("");
GetWindowsDirectory(szWinDir, MAX_PATH);
// Iterate over each relative path and check for file existence
for (int i = 0; i < dwlength; i++)
{
// Build full path: %WINDIR%\relative_path
PathCombine(szPath, szWinDir, szPaths[i]);
std::cout << "Checking file " << szPath << ": ";
//Conditions if result is true or not
if (FileExists(szPath))
std::cout << "TRUE\n";
else
std::cout << "FALSE\n";
}
}| Platform | Component Type | File Path | Purpose / Notes |
|---|---|---|---|
| Parallels | Driver | C:\Windows\System32\drivers\prleth.sys | Parallels virtual network adapter |
| Parallels | Driver | C:\Windows\System32\drivers\prlfs.sys | Shared filesystem driver |
| Parallels | Driver | C:\Windows\System32\drivers\prlmouse.sys | Mouse synchronization |
| Parallels | Driver | C:\Windows\System32\drivers\prlvideo.sys | Virtual video adapter |
| Parallels | Driver | C:\Windows\System32\drivers\prltime.sys | Time synchronization |
| Parallels | Driver | C:\Windows\System32\drivers\prl_pv32.sys | Paravirtualization driver |
| Parallels | Driver | C:\Windows\System32\drivers\prl_paravirt_32.sys | Legacy paravirtualization |
| VirtualBox | Driver | C:\Windows\System32\drivers\VBoxMouse.sys | Virtual mouse integration |
| VirtualBox | Driver | C:\Windows\System32\drivers\VBoxGuest.sys | Core guest additions driver |
| VirtualBox | Driver | C:\Windows\System32\drivers\VBoxSF.sys | Shared folders driver |
| VirtualBox | Driver | C:\Windows\System32\drivers\VBoxVideo.sys | Virtual display adapter |
| VirtualBox | DLL | C:\Windows\System32\vboxdisp.dll | Display driver interface |
| VirtualBox | DLL | C:\Windows\System32\vboxhook.dll | API hook support |
| VirtualBox | DLL | C:\Windows\System32\vboxmrxnp.dll | Network provider |
| VirtualBox | DLL | C:\Windows\System32\vboxogl.dll | OpenGL support |
| VirtualBox | DLL | C:\Windows\System32\vboxoglarrayspu.dll | OpenGL acceleration |
| VirtualBox | DLL | C:\Windows\System32\vboxoglcrutil.dll | OpenGL utility |
| VirtualBox | DLL | C:\Windows\System32\vboxoglerrorspu.dll | OpenGL error handling |
| VirtualBox | DLL | C:\Windows\System32\vboxoglfeedbackspu.dll | OpenGL feedback |
| VirtualBox | DLL | C:\Windows\System32\vboxoglpackspu.dll | OpenGL packing |
| VirtualBox | DLL | C:\Windows\System32\vboxoglpassthroughspu.dll | OpenGL passthrough |
| VirtualBox | Executable | C:\Windows\System32\vboxservice.exe | Guest services daemon |
| VirtualBox | Executable | C:\Windows\System32\vboxtray.exe | System tray integration |
| VirtualBox | Executable | C:\Windows\System32\VBoxControl.exe | Guest control utility |
| Virtual PC | Driver | C:\Windows\System32\drivers\vmsrvc.sys | Virtual Machine Service |
| Virtual PC | Driver | C:\Windows\System32\drivers\vpc-s3.sys | Power management (S3) |
| VMware | Driver | C:\Windows\System32\drivers\vmmouse.sys | VMware PS/2 mouse |
| VMware | Driver | C:\Windows\System32\drivers\vmnet.sys | Virtual networking |
| VMware | Driver | C:\Windows\System32\drivers\vmxnet.sys | Paravirtualized NIC |
| VMware | Driver | C:\Windows\System32\drivers\vmhgfs.sys | Host-guest file sharing |
| VMware | Driver | C:\Windows\System32\drivers\vmx86.sys | Core virtualization driver |
| VMware | Driver | C:\Windows\System32\drivers\hgfs.sys | Legacy shared folders |
| CrowdStrike Falcon Sandbox | Driver | C:\Windows\System32\drivers\csagent.sys | Falcon sensor / sandbox agent |
| CrowdStrike Falcon Sandbox | Driver | C:\Windows\System32\drivers\csdevicecontrol.sys | Device control in sandbox |
| FireEye / Mandiant | Driver | C:\Windows\System32\drivers\fefilt.sys | File system filter |
| FireEye / Mandiant | Driver | C:\Windows\System32\drivers\fe_kmd.sys | Kernel monitoring driver |
| FireEye / Mandiant | Executable | C:\Program Files\FireEye\xagt.exe | Endpoint/sandbox agent |
| FireEye / Mandiant | DLL | C:\Windows\System32\feclient.dll | Analysis client interface |
| Palo Alto WildFire | Driver | C:\Windows\System32\drivers\wf_kmd.sys | WildFire kernel monitor |
| Palo Alto WildFire | Executable | C:\Program Files\Palo Alto Networks\WildFire\wfagent.exe | WildFire analysis agent |
| Sophos Sandbox | Driver | C:\Windows\System32\drivers\sophosflt.sys | Sophos file filter |
| Sophos Sandbox | Driver | C:\Windows\System32\drivers\sophoskmd.sys | Kernel monitoring |
| Kaspersky Sandbox | Driver | C:\Windows\System32\drivers\klif.sys | Kaspersky AV/sandbox filter |
| Kaspersky Sandbox | Driver | C:\Windows\System32\drivers\klhk.sys | Heuristic kernel hooks |
| Kaspersky Sandbox | Executable | C:\Program Files\Kaspersky Lab\* | Analysis tooling |
| Microsoft Defender Sandbox | Driver | C:\Windows\System32\drivers\WdFilter.sys | Defender minifilter |
| Microsoft Defender Sandbox | Driver | C:\Windows\System32\drivers\WdBoot.sys | Boot-time protection |
| Microsoft Defender Sandbox | Executable | C:\Program Files\Windows Defender\MsMpEng.exe | Defender engine |
| Joe Sandbox | Driver | C:\Windows\System32\drivers\joedriver.sys | Joe Sandbox instrumentation |
| Joe Sandbox | DLL | C:\Windows\System32\joeclient.dll | Analysis hooks |
| Any.Run | Driver | C:\Windows\System32\drivers\anyrun.sys | Behavioral instrumentation |
| Any.Run | Executable | C:\Program Files\ANY.RUN\agent.exe | Sandbox agent |
| Cuckoo Sandbox | Driver | C:\Windows\System32\drivers\cuckoomon.sys | Cuckoo kernel monitor |
| Cuckoo Sandbox | DLL | C:\Windows\System32\cuckoomon.dll | Userland monitoring hooks |
| Hybrid Analysis | Driver | C:\Windows\System32\drivers\falconmon.sys | Falcon/HA kernel monitor |
#Time-Based Evasion
Time-based evasion techniques attempt to exploit the fact that automated analysis environments operate under constrained execution windows. Early implementations relied on long sleep calls to delay malicious behavior until the sandbox timed out and returned a benign verdict. Modern sandboxes have largely mitigated this approach through sleep-skipping, time acceleration, and forced execution advances.
As a result, time-based evasion today is less about waiting out analysis and more about detecting time manipulation. By comparing multiple timing sources, measuring execution drift, or correlating expected versus observed delays, an implant can infer whether its execution timeline is being artificially altered.
From an offensive perspective, time-based checks are best used as environmental signal, not as a gating mechanism on their own. When combined with other indicators—such as filesystem artifacts, process anomalies, or lack of user interaction, they can help determine whether continued execution is appropriate without relying on brittle, single-point delays.
This method only uses a straight sleep call, checks the clock before and after the sleep, and determines the resulting time lapse. If the value is less than the expected time then sleep-skipping was done.
//Get current System Time
auto start = high_resolution_clock::now();
//Sleep 2 Seconds
std:Sleep(2000);
// Get Current System Time
auto stop = high_resolution_clock::now();
//Check the difference
auto duration = duration_cast<milliseconds>(stop - start);
//translate into a number
unsigned int dwDuration = duration.count();
//check to see if the duration was actually less than our expected 2 seconds
cout << dwDuration;
if (dwDuration < 2000)
return 0;### Tick-Count Sleep-Skip Detection
This Sleep skip detection works through the use of checking CPU "ticks" and then checking the count between two separate tick counts.
int TickSleepCheck() {
DWORD tick1;
tick1 = GetTickCount();
Sleep(1000);
if ((GetTickCount() - tick1) > 980)
return FALSE;
else
return TRUE;
}### Web Confirmation Sleep-Skip Detection {.col-span-3}
This method uses a remote server to establish what the time is instead of relying on internal local systems. Then we compare the delta between the two to detect for sleep skipping
static bool GetServerTime(ULONGLONG& outUnixSeconds)
{
// User-Agent is visible in telemetry update with a common UA
LPCWSTR ua = L"Mozilla/5.0 (Windows NT 10.0; Win64; x64)";
//open the session
HINTERNET hSession = WinHttpOpen(ua, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (!hSession) return false;
//connect to Google
HINTERNET hConnect = WinHttpConnect(hSession, L"google.com", INTERNET_DEFAULT_HTTPS_PORT, 0);
if (!hConnect)
{
WinHttpCloseHandle(hSession);
return false;
}
//Make the Request
HINTERNET hReq = WinHttpOpenRequest(hConnect, L"HEAD", L"/", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
if (!hReq)
{
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return false;
}
bool ok = false;
//Send it
if (WinHttpSendRequest(hReq, 0, 0, 0, 0, 0, 0) && WinHttpReceiveResponse(hReq, NULL))
{
WCHAR dateHdr[128] = {0};
DWORD size = sizeof(dateHdr);
//Make sure we got a response
if (WinHttpQueryHeaders(hReq, WINHTTP_QUERY_DATE, WINHTTP_HEADER_NAME_BY_INDEX, dateHdr, &size, WINHTTP_NO_HEADER_INDEX))
{
SYSTEMTIME st = {0};
//translate the time header into a system time structure
if (InternetTimeToSystemTimeW(dateHdr, &st, 0))
{
FILETIME ft = {0};
//translate system time to file time which is a 64bit value
SystemTimeToFileTime(&st, &ft);
// Convert FILETIME (100-ns since 1601) -> Unix seconds (since 1970)
ULONGLONG t = ((ULONGLONG)ft.dwHighDateTime << 32) | ft.dwLowDateTime;
// seconds between 1601 and 1970
const ULONGLONG EPOCH_DIFF = 11644473600ULL;
outUnixSeconds = (t / 10000000ULL) - EPOCH_DIFF;
ok = true;
}
}
}
WinHttpCloseHandle(hReq);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return ok;
}bool SleepSkipCheck(DWORD sleepMs, DWORD toleranceMs)
{
ULONGLONG t1 = 0, t2 = 0;
if (!GetServerDateSeconds(t1)) return false;
Sleep(sleepMs);
if (!GetServerDateSeconds(t2)) return false;
ULONGLONG deltaMs = (t2 - t1) * 1000ULL;
// Expect delta ~= sleepMs, but allow for HTTP Date granularity + latency jitter
// Example: sleep 5000ms, tolerance 1500ms
if (deltaMs + toleranceMs < sleepMs) {
// “too small” -> time rushing / stubbed response / accelerated env signal
return true;
}
return false;
}### Sleep-Skip Detection w/ Invalid Args on Hooked Function {.col-span-3}
This technique relies on the sandbox environment not properly detecting when invalid arguments on hooked sleep or delay functions are passed. In turn continuing if the code executes normally as if the function did not actually have malformed arguments then the actual sleep function was skipped. As malware developers we can utilize this to detect when sleep skipping is done on delay or sleep functions through hooked processes.
For example using NtDelayExecution This has two failure conditions
- 1
STATUS_ACCESS_VIOLATIONwhich means that the value is not a valid user-mode address - 2
STATUS_DATATYPE_MISALIGNMENTwhich means that the address supplied is not properly alligned.
In practice that could look like
// Just checking for status returned
if (NtDelayExecution(FALSE, (PLARGE_INTEGER)0) != STATUS_ACCESS_VIOLATION)
{
cout << "hooked process";
return 0;
}// Check to see if hooked process continue with sleep or provided false data on sleep
// First create the unaligned value
__declspec(align(4)) BYTE aligned_bytes[sizeof(LARGE_INTEGER) * 2];
DWORD tick_start, time_elapsed_ms;
DWORD Timeout = 10000;
PLARGE_INTEGER DelayInterval = (PLARGE_INTEGER)(aligned_bytes + 1);
//Create a variable to store the error
NTSTATUS status;
// conduct the sleep
DelayInterval->QuadPart = Timeout * (-10000LL);
//getting tick count
tick_start = GetTickCount();
//status SHOULD fail due to unlaignment
status = NtDelayExecution(FALSE, DelayInterval);
//Do the math on the ticks
time_elapsed_ms = GetTickCount() - tick_start;
// Since the sleep was dependant on a malformed delay, we should expect an error status and no delay
// If that is not the case then we know that the function was hooked and skipped and we are in a sandbox
if (time_elapsed_ms > 500 || status != STATUS_DATATYPE_MISALIGNMENT )
{
cout << "hooked process";
return 0;
}### Non-Traditional Sleep - Math
Sometimes calling sleep is not the most effective or efficient manner of sleeping within the application. Especially when malware detection and sandboxing tools constantly hook, or circumnavigate entirely, the functions for doing so. This is where creativity can become a method for sleeping.
In this example we use a very inefficient prime calculator to determine if the abnormally high number is a prime number or not.
bool IsItPrime(ULONGLONG n) {
if (n <= 1)
return false;
if (n <= 3)
return true;
if (n % 2 == 0 || n % 3 == 0)
return false;
for (ULONGLONG i = 5; i * i <= n; i = i + 6)
if (n % i == 0 || n % (i + 2) == 0)
return false;
return true;
}//calling the IsItPrime Function
IsItPrime(9223372036854775807);## Human-Like Based Evasion
### Introduction
Human-like based evasion techniques leverage the absence of real user interaction in automated analysis environments. Some sandboxes execute samples without meaningful keyboard input, mouse movement, application usage, or long-lived user state, creating behavioral gaps that rarely exist on genuine end-user systems.
From an offensive perspective, these techniques focus on observing expected indicators of human presence, such as input activity, application history, or interaction timing. It should be noted that automated analysis platforms increasingly simulate interaction, and false positives are common on idle or freshly provisioned systems. These techniques are most effective when correlated with additional telemetry such as system age, filesystem artifacts, or timing anomalies.
### Mouse Movement Detection {.col-span-2}
This technique is a bit outdated but can be updated to check to erratic mouse movement as well, but the general idea is to check for mouse movement in a given period of time
This technique was seen witin the LummaC2 Stealer
int mouse_active() {
POINT position1, position2;
GetCursorPos(&position1);
std:Sleep(5000);
GetCursorPos(&position2);
if ((position1.x == position2.x) && (position1.y == position2.y))
// No mouse activity during the sleep.
return TRUE;
else
return FALSE;
}### Impossible Install Window {.col-span-2}
Many times when a GUI is involved within an application an automated sandbox will attempt to click through the process to determine if the installer or files behind the GUI interaction are malicious. We can abuse this action by creating a window that would be impossibly small for a user to interact with.
#define BTN_INSTALL 1001
HWND hWnd = CreateWindow(
TEXT("Button"), // class "button"
TEXT("Install"), // caption is “Install”
NULL, // style flags are not required, the control is invisible
1, 1, 1, 1, // the control is created of 1x1 pixel size
hParentWnd, // parent window
NULL, // no menu
NULL, // a handle to the instance of the module to be associated with the window
NULL); // pointer to custom value is not requiredbool g_ButtonClicked = false;
//Window procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_COMMAND:
if (LOWORD(wParam) == BTN_INSTALL)
{
g_ButtonClicked = true;
// we know for sure no one could have clicked this button so we just gonna quit
return 0;
}
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}int main()
{
// window creation / button creation here
// Message loop with timeout
DWORD timeoutMs = 5000;
DWORD start = GetTickCount();
MSG msg;
while ((GetTickCount() - start) < timeoutMs)
{
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
if (g_ButtonClicked)
return 0;
}
Sleep(10);
}
printf("No interaction detected, continuing...\n");
return 0;
}