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 and C:\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 0x32 . 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.

SampleSHA256
GuLoader Blueliv Article25018a8ff2a535ed05ebe8a1d2158a79dbeb53fc0be67d4e788bc936cb551b6d
GuLoader Checkpoint Shellcode295cb5b21bafcafa8d770d5ce325893340037c8efe545691b8289aff82315539
CloudEyE Checkpoint Shellcode3ca3f172d222d5f52be734079658e2a141d92e15c8edd4ea7515a72cf03a28a3

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

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.

detected

However, if the sandbox is not detected, a message appears on the screen indicating that it does not detect the sandbox.

not detected

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):

GuLoader

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.  

About the Author

Carlos Rubio Reverse Engineer, Outpost24