Playing with GuLoader Anti-VM techniques
GuLoader is one of the most widely used loaders to distribute malware throughout 2020. Among the malware families distributed by GuLoader, we can find FormBook, AgentTesla and other commodity malware. A recent research performed by Check Point suggests that GuLoader code is almost identical to a loader named as CloudEye and sold “legitimately” as a protection mechanism for binaries. At Blueliv we have been keeping track of it and have observed that some of the methods it uses to detect virtual machine execution and hook detection, although not new, are still quite effective, as we have seen that in several online sandboxes this loader does not run correctly. Our own sandbox was detected by GuLoader, but it is correctly handled now, executing this malware family without problems. In this blogpost, we will explain the techniques that this loader uses to thwart the analysts and their virtual machines/sandboxes, offering an application to check if a sandbox is detected by this technique or not.
Anti-Analysis & Anti-VM techniques
GuLoader uses the following techniques to make analysis tasks more difficult and to detect if it is running in a virtual machine:
- Using ZwQueryVirtualMemory to locate pages containing vm-related strings.
- Enumeration of windows (EnumWindows)
- Hooking ntdll_DbgBreakPoint and ntdll_DbgUiRemoteBreakin
- Checking breakpoints
- Hiding the thread (NtSetInformationThread with 0x11)
- Checking the existence of
C:\Program Files\Qemu-ga\qemu-ga.exe
andC:\Program Files\qga\qga.exe
RDTSC
Virtual machine detection- Hook detection
Due to the fact that many of these techniques have been documented, we will focus on the following ones throughout this article:
- Hook detection
- RDTSC Virtual machine detection
Sandbox hook detection
GuLoader uses the following code fragment to detect if it’s running in a sandbox with hooks installed.
_ANTI_HOOK
fnop
pop ebx
cld
cmp dword ptr [ebx], 0
jnz short _jmp_copied_NtAllocateVirtualMemory
clc
xor ecx, ecx
_copy:
clc
push dword ptr [eax+ecx]
pop dword ptr [ebx+ecx]
add ecx, 4
cmp ecx, 18h
jnz short _copy
cld
_jmp_copied_NtAllocateVirtualMemory:
fnop
jmp ebx
_ANTI_HOOK
With the push dword ptr [eax+ecx]
and pop dword ptr [ebx+ecx]
the NtAllocateVirtualMemory function is copied till retn 0x18
:
ntdll_NtAllocateVirtualMemory
arg_0= byte ptr 4
mov eax, 15h
xor ecx, ecx
lea edx, [esp+arg_0]
call large dword ptr fs:0C0h
add esp, 4
retn 18h
Then it jumps to the code where it has copied the function, using jmp ebx
and executes it. If it is running in an environment without hooks, the function will be executed correctly. But in the case that there is a hook in the function and this hook makes a relative jump, for example with opcode E9 (such as those performed by the cuckoo monitor), when the function that has been copied to another position is executed, it will jump to an unknown position and an exception will occur, causing an abrupt termination of the program.
RDTSC Virtual machine detection
Even though this technique is widely known, the interesting thing is the way that it has been implemented. GuLoader uses the following algorithm to detect if it is inside a virtual machine:
_VM_DETECT proc near
cld
_VM_DETECT_START:
fnop
xor edi, edi
nop
mov ecx, 186A0h
cld
nop
_VM_DETECT_CONTINUE:
push ecx
call _RDTSC_OPS
pop ecx
cmp edx, 32h
jl short _VM_DETECT_CONTINUE
add edi, edx
dec ecx
cmp ecx, 0
jnz short _VM_DETECT_CONTINUE
cmp edi, 0
jl short _VM_DETECT_START
cmp edi, 68E7780h
jge short _VM_DETECT_START
mov eax, edi
retn
_VM_DETECT endp
1. The value 0x186A0
is stored in ECX
. This value indicates the number of times EDI
will be incremented with the result of the _RDTSC_OPS
function as long as the result of the operation is greater than 0x
32
. 2. After that it will perform a call to the _RDTSC_OPS
function, explained later in detail. For now, it is only necessary to know that this function will return a value higher than 0. 3. Then it checks if the value is higher than 0x32. If that’s the case it adds the result in the EDI
register and decreases the ECX
value. Otherwise it returns to _VM_DETECT_CONTINUE
to call _RDTSC_OPS
again. As a note, if the value returned by _RDTSC_OPS
at this point is greater than 0 but less than 0x32 continuously, the program will remain in an infinite loop. 4. This will be done until ECX
is 0, so the result of the function will be added to the EDI
value with the add edi, edx
operation 0x186A0
times. 5. Finally, it will check if the result of those increments is greater or equal to 0x68E7780
. The result must be lower to pass the virtual machine check. If not, the execution will return to _VM_DETECT_START
. It is important to highlight that in a virtual machine without any modification or hook over RDTSC
this is the point where the program will remain running in an infinite loop. Basically, the malware developer estimated that the addition of values returned by the execution of the function _RDTSC_OPS
0x186A0
times within a virtualized environment will result in a value above 0x68E7780
due to the overhead generated by the _RDTSC_OPS
function. If the value of RDTSC
has been artificially lowered below 0x32
to attempt to bypass similar techniques, the analysis will be stuck in this Anti-VM check forever.
_RDTSC_OPS function
GuLoader uses the following algorithm to obtain the execution times between two RDTSC
calls, after a CPUID EAX=1
as shown below:
_RDTSC_OPS
lfence
rdtsc
lfence
shl edx, 20h
or edx, eax
mov esi, edx
pusha
mov eax, 1
cpuid
bt ecx, 1Fh
jb short $+2
popa
lfence
rdtsc
lfence
shl edx, 20h
or edx, eax
sub edx, esi
cmp edx, 0
jle short _RDTSC_OPS
retn
_RDTSC_OPS
The algorithm performs the following operations:
1. First obtains the elapsed time in EAX
(low-part) and EDX
(high-part) through RDTSC
.
2. Performs an OR
operation between the high and the low part, and saves the result in ESI
.
3. It makes the call to CPUID
with EAX=1
and then, thanks to the bt ecx, 1Fh
instruction, it checks if it is running in a virtual machine. However, the result of the operation is not relevant, because the call to CPUID
is made with the intention of generating a VM-Exit causing that the hypervisor passes the execution to the Virtual Machine Manager. This allows to differentiate if it is running in a virtual machine, because the call takes more time than in a physical machine.
4. Makes a call to RDTSC
and get again the time that has been spent in EAX
(low-part) and EDX
(high-part).
5. Performs an OR
operation between the high and low parts, and subtracts the previous result stored in ESI
.
6. If the result is greater than 0, as would be expected in normal execution, the function will return the result in EDX
, otherwise it will return to the start of the function. It is important to highlight that it is possible that in some sandboxes the sample will be locked in an infinite loop at this point., depending on the way the sandbox deals with the detection problem by RDTSC
.
Implementation changes in some GuLoader versions
We also found some differences between different GuLoader samples, particularly in the functions responsible for performing the virtual machine check.
Sample | SHA256 |
GuLoader Blueliv Article | 25018a8ff2a535ed05ebe8a1d2158a79dbeb53fc0be67d4e788bc936cb551b6d |
GuLoader Checkpoint Shellcode | 295cb5b21bafcafa8d770d5ce325893340037c8efe545691b8289aff82315539 |
CloudEyE Checkpoint Shellcode | 3ca3f172d222d5f52be734079658e2a141d92e15c8edd4ea7515a72cf03a28a3 |
Comparing the sample that we used to write the article with the different shellcodes that can be found in the following Check Point article, it is possible to see how the _VM_DETECT
function has gone through some modifications.
Comparison of the _VM_DETECT function in different samples
The function on the left corresponds to the sample that we have analyzed throughout the article and the function on the right corresponds to the shellcodes mentioned in Check Point’s article. We can see that the function on the left adds a check after calling _RDTSC_OPS
where it checks if the value is less than 0x32
. As mentioned previously, this new check may be due to the fact that there are sandboxes that after the call to the _RDTSC_OPS
function return values in EDX
below the normal. So GuLoader can avoid to be fully executed in those sandboxes where it will stay in an infinite loop at this point. We can also see that the value compared in EDI
in the function on the left is 0x68E7780
, which is greater than 0x66FF300
(function on the right). It is possible that the variation of this value is due to the fact that there are non-virtualized machines whose value after these operations is above 0x66FF300
and in some update they have had to increase the value up to 0x68E7780
, although this is just an assumption.
Blueliv Anti-VM detection tool
This technique was quite interesting to us because some of the existent sandboxes were not dealing with the issue. That’s why we thought it was a good idea to create a tool to help to detect the problem. This tool permits to know if a sandbox is detected by the technique described above (the most restrictive version). You can find this tool in our repository. If anyone is interested in discussing these technique or taking a look at the source code in order to fix the problem in a sandbox, be free to reach to us. The tool has been created from the code of the GuLoader sample without modifications, to avoid modifying its behavior. If the sandbox is detected, it displays by console the EDI
value after the 0x186A0
loop have been completed.
However, if the sandbox is not detected, a message appears on the screen indicating that it does not detect the sandbox.
If the tool does not display any value, there are two possibilities:
- The result of the operation between the
RDTSCs
is less than or equal to 0. - The result returned by the
_RDTSC_OPS
function is less than 0x32.
In both cases the execution would stay in an infinite loop. It is recommended to analyze the application step by step to find the problem. We have conducted some tests using the article’s sample in several well-known public sandboxes and have found that the sample does not run correctly in most of them. In those that allow access to the API log it is possible to see that the last logged API calls are:
- NtCreateFile of
C:\Program Files\Qemu-ga\qemu-ga.exe
- NtCreateFile of
C:\Program Files\qga\qga.exe
This is because the RDTSC
and anti-hook checks are performed after those calls, and may be avoiding the correct execution of the sample. We hope our tool can help to fix those problems.
Blueliv sandbox execution graph
We can see below the execution graph generated by the Blueliv sandbox, using our kernel monitor, during the execution of this sample (25018a8ff2a535ed05ebe8a1d2158a79dbeb53fc0be67d4e788bc936cb551b6d
):
As explained in this article, during the execution of the sample it performs several anti-virtual machine and anti-hook checks in order to thwart the analysis, but our sandbox was able to bypass them and continue with the execution flow. When executed it runs twice (liTK.exe
), then the GuLoader sample creates persistence and changes its name to host.exe and finally downloads from Microsoft OneDrive the final payload. The final payload could not be retrieved if the sandbox would not correctly bypass the mentioned anti-vm techniques.
Conclusions
Cybercriminals and malware authors are always evolving, modifying their tools and using techniques to evade virtual machines and difficult the analysis of the samples. GuLoader is a clear example of this, a really active loader which makes use of some modifications of well-known techniques like the RDTSC check to go further in the detection of sandboxes. The tool we provide will help to confirm if GuLoader is able to detect a sandbox and we hope that the affected online sandboxes can benefit from it and fix this problem. Keeping a sandbox in a good shape and with all the latest anti-vm techniques under control is crucial to be able to detonate samples and artifacts, and bypass packers and obfuscation layers. At Blueliv we know that and we investigate and fix all the problems that we detect so the number of extracted IOCs and our malware classification keeps having a good quality. This is especially important for our internal investigations, for our customers and for our Threat Exchange Network, where our sandbox can be used free of charge.