Avatar Rootkit: Dropper Analysis
Avatar was first detected by ESET circulating in the wild in early 2013 [1]. However, no samples were collected for analysis until May of the same year. ESET published two analysis reports on the botnet and its main modules and described it as "mysterious" [1] [2].
If you happen to be interested in understanding how Avatar works in a general and concise way, you may find their analysis very helpful. However, I am going to take a different approach by walking you step by step through the analysis considering the educational purpose of the material. Still, as the modules are lengthy, some areas or functionalities might be hinted on or completely left out for the reader to explore on his own.
We are going to start first by taking a look at the Rootkit dropper. In other words, it is the module responsible for loading (dropping) the rootkit. In the case of Avatar, all the "loading" is done in memory. Hence, extra work is needed on the part of the dropper to load DLL modules and the rootkit. Before we start, I would like to add that I named the functions appropriately to what actions I thought they perform. The names are preceded by "Avtr_."
The sample, as found, used in this analysis is unpacked, and the file hash was collected from ESET's blog posts [1][2]. A quick Google search of this hash (93473126a9aa13834413c494ae5f62eec1016fde) will provide you with a download link.
Let's start by looking at the first instructions of the start routine (Figure 1).
Figure 1
We see a function "Avtr_PatchKiUserExceptionDispatched" being called. I named it exactly after what it does, which is installing a hook on KiUserExceptionDispatcher. Briefly, the execution is switched to KiUserExceptionDispatcher whenever an exception occurs. This leads to invoking another routine "RtlDispatchException" responsible for calling the registered exception handlers and so on. What Avatar does is patch the call to RtlDispatchException to divert the execution to its own routine (Avtr_RtlDispatchException). Below are the relevant parts of this process:
Figure 2
Another thing worth mentioning is that Avatar performs a check right after the hooking in which it checks if it is running on a 64-bit machine. When that is the case, it just bails out and deletes itself.
In this stage, the dropper calls Avtr_RaiseException to trigger a null pointer dereference exception. However, before doing that, it registers an EH3_EXCEPTION_REGISTRATION (Figure 3).
Figure 3
Avtr_handler_1CC0 is responsible for executing the exception filter and handler inside the scope table entry provided. The handler at loc_1231D04 reads the well-known BeingDebugged flag into a local variable after setting ESP to its old value before the exception (Figure 4).
Figure 4
If a debugger is detected Avatar will delete itself and terminate its execution. Afterward, the relative offset to a resource (in the resources section) containing several LZMA compressed PE files is fetched (Figure 5).
Figure 5
A trick used to evade the detection of the compressed data using tools like binwalk is implemented here. It consists of stripping the compressed data of its header only to add it later on before the decompression (Figure 6). Also, notice the allocation of the memory region where the decompressed data will reside.
Figure 6
Subsequently, Avtr_DecompressSourceToDest is invoked which results in the decompressed data stored in the location pointed by Avtr_Decompressed_PEs. A memory chunk of size 4448 bytes is allocated and zeroed afterward; we will call it Mem. (Mem+0x18) is then incremented for reasons we will discuss later. Right after that, certain values are pushed onto the stack in the following fashion:
Figure 7
As mentioned in the figure above, the values pushed onto the stack are parameters for NtAllocateVirtualMemory. The function is not called right away, and the task is left to the exception handler to divert execution to it. Also, notice that the interrupt time is read from KUSER_SHARED_DATA. The exception handler that will be used in the one registered in Figure 1. Let's take a look at it (Figure 8):
Figure 8
We can see that a similar technique to GetTickCount is used here as an anti-debugging technique. Next, the processor context of the frame that threw the exception is used to get EAX and EDX where the InterruptTime is stored. Moreover, the context's EBP is used to get stack pointers to variables (ntdll_address, NtAllocateVirtualMemoryChecksum, and Mem).
We see in the last chunk of code that EIP is moved right after the instruction that caused the exception (EIP + 2 => jmp edx). EDX is then set to contain the address of NtAllocateVirtualMemory. Moreover, since it is a jump, a return address must be present at the top of the stack (loc_D726D5). Furthermore, the value at (Mem+0x18) that was incremented earlier is now back to zero. This field always receives one before testing for a debugger or a VM, and it is zeroed back if the tests succeed.
On its return from NtAllocateVirtualMemory, the sample gets yet another resource. It is another compressed PE file; it decompresses it as we have seen before. I named the variable holding its location Decompressed_DLL_Location. All the PE files are now decrypted in memory. Let's dump them.
We saw that the sample uses decompression twice. The first yields multiple PE files and the second only one. Let's start with the first decompressed data.
The function Avtr_DecompressSourceToDest returns the size of the decompressed data in an out variable. So, we'll need to break right after this function, take the start address of the decompressed data and its size then supply them to the following python script that will dump the PE files.
from idautils import
*
def
get_next_pe(buffer,index,size)
:
i = index
while i < size :
if buffer[i:i+4]
==
'MZx90x00'
:
return i
i+=1
return size
start =
0x1300020
size =
0x30200
buffer = GetManyBytes(start,size,True)
for c in range(size)
:
if buffer[c:c+4]
==
'MZx90x00'
:
i = get_next_pe(buffer,c+4,size)
dump = open("dumppe"+str(c)+".bin","wb")
out = buffer[c:i]
dump.write(out)
dump.close()
After the execution of this script we get three files (I named them accordingly):
pe0.bin => Avtr_Rootkit.sys
pe108176.bin => Avtr_sock.dll
pe180368.bin => Avtr_unk.dll
We do the same to dump the other PE file.
from idautils import
*
start =
0x4AF590
size =
0xA400
data = GetManyBytes(start,size,True)
f = open("dumpload_driver.dll","wb")
f.write(data)
f.close()
Next, Avatar will fetch an encrypted key from a resource, decrypt it and then use it to decrypt data stored in a separate resource of size 0x319.
The decrypted key:
E623J5XKJ9NF4bseM5J2nkwhs1K2766DUOMUDSee3c7xu06Q9QayV61U4fm5H89ppuNgLt9M5D2XTCLcd0aS3m9CO1aZg9h9o2zb2EIC437IU3X1P3ec07481E0j2Tdr
Interesting bits of the decrypted data:
svchost.exe[*netsvcs]=avcmd.dll
http://www.googleudatedb4.com http://www.microsoftonilinedbserv.com http://www.googleanalytscs2873.com http://www.yahoodataserverdono376.com http://www.microsogaretgias.com
In the next part, we'll see how the dropper loads the DLL responsible for performing some well-known VM checks and of course dropping the rootkit.
References: