Sandbox emulation usually lasts a short time because sandboxes are heavy loaded with thousands of samples. Emulation
time rarely exceeds 3-5 minutes. Therefore, malware can use this fact to avoid detection: it may perform
long delays before starting any malicious activity.
To counteract this, sandboxes may implement features which manipulate time and execution delays. For example, the Cuckoo
sandbox has a sleep skipping feature that replaces delays with a very short value. This should force the malware to start
its malicious activity before an analysis timeout.
However, this can also be used to detect a sandbox.
There are also some differences in the time of execution of some instructions and API functions that
can be used to detect a virtual environment.
Signature recommendations are not provided for this class of techniques as executing functions described in this
chapter does not imply their usage for evasion purposes. It is hard to differentiate between the code which aims to
perform an evasion code and the one which uses the same functions with non-evasion intentions.
While the use of most of these functions is obvious, we show examples of using the
timeSetEvent
function
from Multimedia API and the
select
function from the Windows sockets API.
Code sample (delay using the “select” function)
intiResult;DWORDtimeout=delay;// delay in millisecondsDWORDOK=TRUE;SOCKADDR_INsa={0};SOCKETsock=INVALID_SOCKET;// this code snippet should take around Timeout millisecondsdo{memset(&sa,0,sizeof(sa));sa.sin_family=AF_INET;sa.sin_addr.s_addr=inet_addr("8.8.8.8");// we should have a route to this IP addresssa.sin_port=htons(80);// we should not be able to connect to this portsock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);if(sock==INVALID_SOCKET){OK=FALSE;break;// setting socket timeoutunsignedlongiMode=1;iResult=ioctlsocket(sock,FIONBIO,&iMode);iResult=connect(sock,(SOCKADDR*)&sa,sizeof(sa));if(iResult==false){OK=FALSE;break;iMode=0;iResult=ioctlsocket(sock,FIONBIO,&iMode);if(iResult!=NO_ERROR){OK=FALSE;break;// fd set datafd_setWrite,Err;FD_ZERO(&Write);FD_ZERO(&Err);FD_SET(sock,&Write);FD_SET(sock,&Err);timevaltv={0};tv.tv_usec=timeout*1000;// check if the socket is ready, this call should take Timeout millisecondsselect(0,NULL,&Write,&Err,&tv);if(FD_ISSET(sock,&Err)){OK=FALSE;break;}while(false);if(sock!=INVALID_SOCKET)closesocket(sock);
Code sample (delay using the “timeSetEvent” function)
VOIDCALLBACKTimerFunction(UINTuTimerID,UINTuMsg,DWORD_PTRdwUser,DWORD_PTRdw1,DWORD_PTRdw2)bProcessed=TRUE;VOIDtiming_timeSetEvent(UINTdelayInSeconds)// Some varsUINTuResolution;TIMECAPStc;MMRESULTidEvent;// We can obtain this minimum value by callingtimeGetDevCaps(&tc,sizeof(TIMECAPS));uResolution=min(max(tc.wPeriodMin,0),tc.wPeriodMax);// Create the timeridEvent=timeSetEvent(delayInSeconds,uResolution,TimerFunction,TIME_ONESHOT);while(!bProcessed){// wait until our function finishesSleep(0);// destroy the timertimeKillEvent(idEvent);// reset the timertimeEndPeriod(uResolution);
The idea behind this technique is that a sandbox doesn’t reboot a virtual machine during the emulation of a
malicious sample. The malware may just set up persistence using any of available methods and silently exit.
Malicious actions are performed only after the system is rebooted.
Malware samples may check the current date and perform malicious actions only on certain dates. For example,
this technique was used in the Sazoora malware,
which checks the current date and verifies if the day is either the 16th, 17th or 18th
of a given month.
Example:
Countermeasures
Countermeasures for this class of evasion techniques should be comprehensive and include all described attack vectors.
The implementation cannot be simple and its description deserves a separate article. Therefore, we only provide general
recommendations here:
Implement sleep skipping.
System-wide dynamic time flow speed manipulation.
Run emulation multiple times on different dates.
Although sleep skipping is already implemented in the Cuckoo sandbox, it is very easy to deceive it.
Sleep skipping is disabled after a new thread or process is created to avoid sleep skipping detection.
However, it can still be easily detected as shown below.
Techniques of this type are generally aimed at the Cuckoo monitor sleep skipping feature and other time-manipulation
techniques that can be used in sandboxes to skip long delays performed by the malware.
In the code sample above, the delay timeout is set using the SetWaitableTimer() timer function.
The Sleep() function is called in a loop until the timer timeout. In the Cuckoo sandbox, delays that are performed
by the Sleep() function are skipped (replaced with a very short timeout) and the virtually elapsed
time will be much higher than the requested timeout:
We need to perform a delay that will be skipped in a sandbox and to measure elapsed time using different methods.
While the Cuckoo monitor hooks the GetTickCount(), GetLocalTime(), GetSystemTime() and
makes them return the skipped time, we still can find methods to measure time that are not handled by the Cuckoo
monitor.
We can also use our own implementation of GetTickCount to detect sleep skipping.
In the next code sample, we acquire the tick count directly from the KUSER_SHARED_DATA structure.
This way we can get the original tick count value even if the GetTickCount() function was hooked.
Code sample (getting the tick count from the KUSER_SHARED_DATA structure)
This method is similar to the previous one. Instead of measuring intervals we try to obtain the current system
time using different methods.
Code sample
SYSTEM_TIME_OF_DAY_INFORMATIONSysTimeInfo;ULONGLONGtime;LONGLONGdiff;Sleep(60000);// should trigger sleep skippingGetSystemTimeAsFileTime((LPFILETIME)&time);NtQuerySystemInformation(SystemTimeOfDayInformation,&SysTimeInfo,sizeof(SysTimeInfo),0);diff=time-SysTimeInfo.CurrentTime.QuadPart;if(abs(diff)>10000000)// differ in more than 1 secondprintf("Sleep-skipping DETECTED!\n);
Sleep-skipping is usually implemented as a replacement of the delay value with a smaller interval.
Let’s look at the NtDelayExecution function. The delay value is passed to this function using a pointer:
Therefore, we can check if the value of DelayInterval changes after the function execution.
If the value differs from the initial value, the delay was skipped.
Code sample
For Nt-functions that perform delays we can use either a relative delay interval or an absolute time for timeout.
A negative value for the delay interval means a relative timeout, and a positive value means an absolute timeout.
High-level API functions such as WaitForSingleObject() or Sleep() operate with relative intervals.
Therefore sandbox developers may not care about absolute timeouts and handle them incorrectly.
In the Cuckoo sandbox such delays are skipped, but skipped time and ticks are counted incorrectly. This can be used
to detect sleep skipping.
Code sample
Sleep skipping in the Cuckoo sandbox is not system-wide. Therefore, if there are performing delays, time moves
with different speeds in the different processes. After a delay we should synchronize the processes and compare
the current time in the two processes. A big difference in measured time values indicates sleep skipping was performed.
The current version of the Cuckoo monitor disables sleep skipping after creating new threads or processes.
Therefore, we should use a process creation method that is not tracked by the Cuckoo monitor, for example,
using a scheduled task.
A sandbox may set different dates to check how the behavior of analyzed samples is changed depending on the date.
The malware can use an external date and time source to prevent time manipulation attempts inside the VM.
This method can also be used to measure time intervals, perform delays, and detect sleep skipping attempts.
NTP servers, and the HTTP header “Date” can be used as an external source for the date and time.
For example, the malware may connect to google.com to check the current date and use it as a DGA seed.
Countermeasures
Implement fake web infrastructure or spoof NTP data and HTTP headers returned by real servers.
The returned/spoofed date and time should be synchronized with the date and time in a virtual machine.
The execution of some API functions and instructions may take different amounts of time in a VM and in the usual
host systems. These peculiarities can be used to detect a virtual environment.
BOOLrdtsc_diff_vmexit()ULONGLONGtsc1=0;ULONGLONGtsc2=0;ULONGLONGavg=0;INTcpuInfo[4]={};// Try this 10 times in case of small fluctuationsfor(INTi=0;i<10;i++)tsc1=__rdtsc();__cpuid(cpuInfo,0);tsc2=__rdtsc();// Get the delta of the two RDTSCavg+=(tsc2-tsc1);// We repeated the process 10 times so we make sure our check is as much reliable as we canavg=avg/10;return(avg<1000&&avg>0)?FALSE:TRUE;
#define LODWORD(_qw) ((DWORD)(_qw))
BOOLrdtsc_diff_locky()ULONGLONGtsc1;ULONGLONGtsc2;ULONGLONGtsc3;DWORDi=0;// Try this 10 times in case of small fluctuationsfor(i=0;i<10;i++)tsc1=__rdtsc();// Waste some cycles - should be faster than CloseHandle on bare metalGetProcessHeap();tsc2=__rdtsc();// Waste some cycles - slightly longer than GetProcessHeap() on bare metalCloseHandle(0);tsc3=__rdtsc();// Did it take at least 10 times more CPU cycles to perform CloseHandle than it took to perform GetProcessHeap()?if((LODWORD(tsc3)-LODWORD(tsc2))/(LODWORD(tsc2)-LODWORD(tsc1))>=10)returnFALSE;// We consistently saw a small ratio of difference between GetProcessHeap and CloseHandle execution times// so we're probably in a VM!returnTRUE;
Implement RDTSC instruction “hooking.” It is possible to make RDTSC a privileged instruction that
can be called in kernel-mode only. Calling the “hooked” RDTSC in user-mode leads to an execution of our handler
that can return any desired value.
This technique is a combination of techniques described in
Generic OS queries: Check if the system uptime is small
and WMI: Check the last boot time sections.
Depending on a method used for getting system last boot time, the measured sandbox OS uptime can be too
small (several minutes), or conversely, too big (months or even years), because the system is usually restored
from a snapshot after the analysis starts.
We can detect a sandbox by comparing the two values for the last boot time, acquired through WMI and through
NtQuerySystemInformation(SystemTimeOfDayInformation).
The second argument of the NtDelayExecution function is a pointer to the delay interval value. In the kernel-mode,
the NtDelayExecution function validates this pointer and can also return the following values:
STATUS_ACCESS_VIOLATION - If the value is not a valid user-mode address
STATUS_DATATYPE_MISALIGNMENT - If the address is not aligned (DelayInterval & 3 != 0)
In a sandbox, the input arguments for NtDelayExecution and similar functions might not be handled correctly.
If we call NtDelayExecution with an unaligned pointer for DelayInterval, normally it returns the
STATUS_DATATYPE_MISALIGNMENT. However, in a sandbox, the value for DelayInterval may be copied to a new variable
without the appropriate checks. In this case, a delay is performed and the returned value will be STATUS_SUCCESS.
This can be used to detect a sandbox.
Code sample
__declspec(align(4))BYTEaligned_bytes[sizeof(LARGE_INTEGER)*2];DWORDtick_start,time_elapsed_ms;DWORDTimeout=10000;//10 secondsPLARGE_INTEGERDelayInterval=(PLARGE_INTEGER)(aligned_bytes+1);//unalignedNTSTATUSstatus;DelayInterval->QuadPart=Timeout*(-10000LL);tick_start=GetTickCount();status=NtDelayExecution(FALSE,DelayInterval);time_elapsed_ms=GetTickCount()-tick_start;// If the pointer is not aligned the delay should not be performedif(time_elapsed_ms>500||status!=STATUS_DATATYPE_MISALIGNMENT)printf("Sandbox detected\n");
On the other hand, if an inaccessible address is set for DelayInterval, the return code should be
STATUS_ACCESS_VIOLATION. This can be used to detect a sandbox as well.
Code sample
If the DelayInterval argument is not verified before it is accessed, this may lead to an exception in the case of
using an invalid pointer. For example, the next code leads the Cuckoo monitor to crash.
Code sample