Time-based sandbox evasion techniques
1. Delayed execution
1.1. Simple delaying operation
1.2. Deferred execution using Task Scheduler
1.3. No suspicious actions until reboot
1.4. Running only on certain dates
2. Sleep skipping detection
2.1. Parallel delays using different methods
2.2. Measure time intervals using different methods
2.3. Get system time using different methods
2.4. Check if the delay value changes after calling a delay function
2.5. Use absolute timeout
2.6. Get time from another process
3. Get the current date and time from an external source (NTP, HTTP)
4. Difference in time measurement in VM and hosts
4.1. RDTSC (with CPUID to force a VM Exit)
4.2. RDTSC (Locky version with GetProcessHeap and CloseHandle)
5. Check the system last boot time using different methods
6. Call a potentially hooked delay function with invalid arguments
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.
Execution delays are used to avoid detection of malicious activity during the emulation time.
- Sleep, SleepEx, NtDelayExecution
- WaitForSingleObject, WaitForSingleObjectEx, NtWaitForSingleObject
- WaitForMultipleObjects, WaitForMultipleObjectsEx, NtWaitForMultipleObjects
- SetTimer, SetWaitableTimer, CreateTimerQueueTimer
- timeSetEvent (multimedia timers)
- select (Windows sockets)
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)
Code sample (delay using the “timeSetEvent” function)
Credits for this code sample: al-khaser project
This method can be used both for delaying execution and evading sandbox tracking.
Code sample (PowerShell)
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.
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.
The idea behind the techniques is to perform different types of delays in parallel and to measure the elapsed time.
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.
- QueryPerformanceFrequency, QueryPerformanceCounter
Code sample (using “QueryPerformanceCounter” to measure elapsed time)
Code sample (using “GetTickCount64” to measure elapsed time)
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.
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.
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.
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.
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.
Credits for this code sample: al-khaser project
Credits for this code sample: al-khaser project
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).
- Adjust the KeBootTime value
- Reset the WMI repository or restart the "winmgmt" service after the KeBootTime adjustment
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.
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.
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.
As stated earlier, normally this call should return STATUS_ACCESS_VIOLATION without causing an exception.
Hooked functions should check arguments and return appropriate error codes if arguments are invalid.
Countermeasures are present in the appropriate sub-sections above.
Credits go to open-source projects from where code samples were taken:
- al-khaser project on GitHub