Avatar Rootkit: Deeper Dropper Analysis
In this second article on the dropper, we will resume our analysis right where we left off: the decryption of the key and data. After the decryption, two structures are initialized. The equivalent pseudo-code is presented below. Also, notice that the previously allocated "Mem" memory chunk is used here.
Avtr_Structure.Unk01 = 0;
Avtr_Structure.DllBase = Decompressed_DLL_Location;
Avtr_Structure.Dllsize = 0xA400;
Avtr_Structure.NtdllBase = ntdll_address;
Avtr_Structure.Unk02 = 1;
Avtr_Structure.Mem = Mem;
Avtr_Structure.Memsize = 0x1160;
Avtr_Structure.Unk03 = 0;
*(Mem+0x8) = Avtr_Decompressed_PEs; //Where the 3 decrypted PE files reside
*(Mem+0xC) = Avtr_Decompressed_PEs_size;
*(Mem+0x10) = decrypted_data;
*(Mem+0x14) = decrypted_data_size; // == 0x319
*(Mem+0x1C) = decrypted_anti_vm; //Allocated but still empty
*(Mem+0x4) = 0x80000000;
After the dropper is done with structure initialization, it starts generating random strings that are names for the malware's mutexes, a kernel object, a random 6-bytes string and also two random 4-bytes values. This randomization keeps the malware from being detected as it would normally when using hardcoded global mutex and event names.
The name placeholders are already present in the .rdata section. Figure 1 shows their format before the randomization.
The routine responsible for the randomization is Avtr_GenerateRandomNames. This routine stores pointers to the generated names and the two random double-words values in a global structure I named rndm_v. Avtr_SetMutexNames is invoked subsequently (Figure 2) to first replace the strings present in the .rdata sections of the decompressed executables and then replace all occurrences of 0xDA1EDA20 and 0xDA1EDA1E by the two random double-words respectively in those same PEs.
Figure 3 shows an occurrence of 0xDA1EDA20 that will be replaced by the first random double-word in the rootkit driver.
Next, Avtr_GetObjectName is invoked to access the name from the rndm_v array with parameter 0x14. The routine will calculate from this value where the desired object is and return a pointer to it. The name is then used to create an event (Figure 4).
Figure 4 also shows a call to Avtr_RestorKiUserExceptionDispatcher which restores the original call offset of RtlDispatchException, and a call to Avtr_DecryptAndSwitchToDLL. What this routine does is load the DLL which was decompressed separately and then invoke its DllEntryPoint (Figure 5).
This DLL is the second level dropper, and it is the one responsible for loading the Rootkit. DllMain will end up calling the DLL's Avtr_main_func which starts by checking if the current user is an administrator (Figure
6); this step is important because it determines how the rootkit will be loaded afterward.
The technique used in the function IsAdministrator is identical to the code in the CheckTokenMembership MSDN example .
After that, a function is invoked that determines which version of Windows the user is running. Then, the module path that was stored earlier to Mem+0x120 is duplicated to Mem+0x328. The next block of code copies the 1st and 2nd mutex names, both preceded by BaseNamedObjects, to Mem+0x20 and Mem+0xA0 respectively (Figure
7). These fields are going to be used by the kernel rootkit later for synchronization.
The code executed subsequently will patch the call to RtlDispatchException in the KiUserExceptionDispatcher routine as was done in the 1st level dropper. Then, Avtr_anti_VM will check if the malware is running in a virtual machine or not (Figure 8).
The code that performs anti-VM checks is encrypted. Before being decrypted, it is copied the previously allocated memory region at *(Mem+0x1C) (refer to the pseudocode at the beginning of the article). Then, it is decrypted using a simple cyclic XOR algorithm with the string "explorer" (Figure 9).
Before issuing a call to the decrypted position independent code, Mem+0x18 is set to 1. In the previous part, we have found that Mem+0x18 is always set to 1 before performing an anti-debugging check and it is not decremented back to zero until all the checks fail at finding a debugger. The same value is used here before testing for a virtual machine.
To get a close look at the code, a similar script to the ones in the previous part was used to dump it. There is a total of 5 anti-VM checks performed if one of them fails the value of Mem+0x18 is not decremented. There's actually nothing new about the techniques used here, and they are well known. However, that does not mean we should not look at some of them:
This first technique (Figure 10) compares the segment selector of that points to the TSS (Task State Segment) of the current task to 0x4000. This is an anti-VMware technique since this value is 0x4000 under VMWare .
Another anti-VMware technique used is the famous IN instruction trick. Supposing the sample is running on VMware, the execution of the IN instruction will make EAX and EBX hold VMware's version and 'VMXh' respectively. In normal cases, an exception will be raised since the IN instruction is privileged. That is why, before executing any of this code an exception handler is set up. This handler will immediately recover the previous stack pointer and return execution to the caller with a success "return value" (CF = 1).
To bypass all these checks, one can simply wait for the decrypted code to return to Avtr_anti_VM and then decrement Mem+0x18.
This was all for this part; Next time we will examine the different code paths Avatar takes — depending on the circumstances — to load the rootkit driver into the kernel.
Become a certified reverse engineer!