In the two previous parts, we have examined the different structures created by the dropper and the anti-reverse engineering checks it performs. In this part, we are going to take an interest in the numerous ways Avatar uses to try and gain kernel mode code execution.

Right after Avtr_anti_VM function returns, – keep in mind that no checks were performed on the return value at Mem+0x18 yet – a function named Avtr_setup_list is called. This function creates a doubly linked list and appends elements to it depending on the certain condition that we will be examining shortly. For now, the structure is defined in the following manner:

typedef
struct _avtr_listelem

{


struct _avtr_listelem* next;


struct _avtr_listelem* prev;

DWORD technique;
/*0x08*/

DWORD privilege_value;
/*0x0C*/

DWORD n_tries;
/*0x10*/

DWORD milliseconds;
/*0x14*/

} avtr_listelem,
*pavtr_listelem;

  • technique: this field indicates the technique used to gain kernel code execution.
    • 0x80000000: Kernel code execution from the same process (through loading a driver or exploiting a kernel vulnerability)
    • 0x80000001: DLL injection into explorer.exe
    • 0x80000002: UAC bypass
  • privilege_value: this fields contains a value indicating the privilege needed for the technique.
  • n_tries: a number indicating how many times the same technique is tried (this is always 1 in our case)
  • milliseconds: The number of milliseconds the main thread waits on events.

I have manually decompiled the different functions and code blocks involved in using these fields. You can find them, for better understanding, here:

Avtr_setup_list routine: https://goo.gl/Sg9nfX

Avtr_dllmain loop: https://goo.gl/PBJS1z

Avtr_ThreadProc routine: https://goo.gl/wPGXRe

Now, let’s look at how the linked list is created and examine each element closely.

Setting up the list:

  1. The first check performed by Avtr_setup_list is on the global variable IsAdmin which we mentioned in previous part. If the user is indeed an administrator an element is inserted in the list with the following field values:

Elem->technique =
0x80000000;// SCM or ZwLoadDriver

Elem->privilege_value =
2;
//Administrator

Elem->n_tries =
1;

Elem->milliseconds =
30000;

  1. When this first check is done, the routine issues a call to Avtr_isVulnerable in the following fashion:

Figure 1

The function does this check on afd.sys:

/*

arg_0: Filename (“afd.sys”)

arg_4: Year (2011)

arg_8: Month (10)

*/

BOOLEAN isVulnerable =
0;

/*[…]*/

if
(


( Year > LastWriteTimeFields.Year ||


( Year == LastWriteTimeFields.Year


&& Month >= LastWriteTimeFields.Month


)


)


&&


( Year > CreationTimeFields.Year ||


( Year == CreationTimeFields.Year


&& Month >= CreationTimeFieldsds.Month


)


))

{

isVulnerable =
1;

}

NtClose(FileHandle);

return isVulnerable;

This is a simple creation/modification date check to see if the driver was updated to patch a certain vulnerability. Apparently, the vulnerability exploited in afd.sys was patched sometime in October 2011.

If this turns out to be the case and the machine contains the unpatched binary, an entry is added to the linked list with these field values:

Elem->technique =
0x80000000;//afd.sys vulnerability

Elem->privilege_value =
1;//unprivileged user

Elem->n_tries =
1;

Elem->milliseconds =
30000;

  1. Finally, the last entry is appended to the list if the user is an administrator or if the machine runs Windows Vista or later. If one of these conditions is satisfied, the new element’s fields values are set to:

Elem->technique =
0x80000001;//explorer.exe DLL injection

/*0x2 if admin, 0x80000002 if Vista or later*/

Elem->privilege_value = privilege_value_val

Elem->n_tries =
1;

Elem->milliseconds =
30000;

Creating the “injection” thread:

To implement each one of the techniques, a loop is entered that creates one thread at a time for each element of the linked list. Once the created thread signals the events that the main thread is waiting on, the latter understands that kernel code execution was acquired successfully.

These events on which the main thread waits depend on the technique used. If the technique value of the current list element is 0x80000000, the thread only waits on one event I called Global_02 (check decompiled source). This is the one which was concatenated with \BaseNameObject as we have seen in the previous part. This event is signaled from kernel-mode and entails that Avatar is now executing in the kernel. However, if the UAC bypass — which is not used unless the 1st level dropper is compiled as a DLL and that is not the case here — or the DLL injection into explorer.exe is used, the main thread waits on an event I named Global_03. This event is always set to signal in the “injection” thread when one of the mentioned techniques is used; However, the main thread needs to also wait on the Global_02 event to ensure that kernel-mode code execution was successful.

Now that we have a general idea about how the dropper sets up the linked list and how it knows whether a technique worked or not let’s delve into the specifics and examine each technique.

We will first start with the techniques defined with 0x80000000.

Note :

CreateThread () responsible for creating the “exploitation” threads is supplied the anti-reversing flag (Mem+0x18) as a parameter.

CreateThread

(

NULL,

0,

Avtr_ThreadProc,

Elem,

*(Mem+0x18)
*
4,//dwCreationFlags

NULL

);

Meaning that if a debugger or a VM were detected, the thread would start in a suspended state. Thus, the waits performed on the events will then successively timeout causing the malware to exit.

Loading an infected system driver:

If we are an administrator, this technique is the first one tried. The function called by the “code injection” thread is Avtr_AdminInfectDriver which starts off by adjusting the required privileges (Figure 2).

Figure 2

This function subsequently invokes Avtr_FindAndInfectDriver. The latter gets the system directory, appends “\drivers\*.sys” to it and calls FindFirstFileA. The FindFirstFileA, FindNextFileA couple is used to iterate through system drivers until a suitable one for injection is found.

Nonetheless, the loop does not start applying selection criteria to the file until the loop’s iterator reaches a randomly generated index between 0 and 40. Let’s see what criteria is applied:

  • Filesize > 30720 && Filesize <= 307200
  • The .sys file is read into a buffer, and we continue dealing with the same file if:
    • Windows version is below Windows Vista.
    • Alternatively, if the .sys file is not signed (checks for IMAGE_DIRECTORY_ENTRY_SECURITY) when Windows version >= Vista.

    If these conditions are met, Avtr_InjectCodeIntoImage is called; this routine seems if the code injection is possible by parsing the PE file. The checks performed, as well as the injection process is done in the following fashion:

  • Get the section within the image file where the driver’s entry point is located (usually in .INIT)
  • Check whether there’s enough room in the section to inject the necessary functions. For example, if the entry point and import table are in the same section, the injected code must not overwrite the import table.
  • Generate two “random” different offsets within the section, considering the size of the data to be stored.
  • Overwrite the original entry point by sub_10001070.
  • Copy sub_10001000 in the 1st “random” location within the section.
  • Copy sub_10001060 in the 2nd “random” location within the section.

    If you take a look at the injected functions, you will see that they contain preinserted placeholders that must be replaced or relocated accordingly to the code’s position inside the section.

    Ethical Hacking Training – Resources (InfoSec)

    Figure 3

    For example, for sub_10001000 depicted in Figure 3 above:

    • 0x21212121 is replaced by the address of PsLookupProcessByProcessId calculated dynamically in Avtr_getKernelRoutines (refer to
      Avtr_ThreadProc
      decompilation).
    • The value 12121212h is replaced by a random number between 0x58 and 0x108 (stack-aligned of course).
    • 0x01010101 is replaced by a random value + 0x18 (within the local variable space of course). The 0x18 is added to leave space for the KAPC_STATE that will be stored later.
    • 0x12345678 is modified to contain the process id of the dropper process.
    • 0x22222222 is replaced with the dynamically calculated kernel address of KeStackAttachProcess.
    • 0x20202020 is replaced by the same random value generated for 0x01010101. [ebp-20202020h] will be pointing to the KAPC_STATE to be returned by KeStackAttachProcess.
    • Avtr_kernel_routine that is subsequently called is a global variable initialized in Avtr_InjectCodeIntoImage to sub_100034E0 (Avtr_main_kernel_routine).
    • KeUnstackDetachProcess global variable has the kernel pointer to the routine indicated by its name.

    The other routine that gets modified is the injected entry point: sub_10001070. The call and jump relative offsets are calculated dynamically — sub_10001000 and sub_10001060 offsets are randomized within the section — and then they’re replaced (Figure 4).

    Figure 4

    If everything went well, a random name between 4 and 8 characters is generated and the infected driver is written to the temporary directory under that name plus the “.sys” file extension. At this stage, the file is sitting in the temporary directory waiting to be loaded and Avatar makes use of two classical approaches to load it:

  1. SCM:

The service is created with the following arguments:

CreateServiceA(

hSCM,

random_service_name,
/*lpServiceName*/

random_service_name,
/*lpDisplayName*/

SERVICE_ALL_ACCESS,
/*dwDesiredAccess*/

SERVICE_KERNEL_DRIVER,
/*dwServiceType*/

SERVICE_DEMAND_START,
/*dwStartType*/

SERVICE_ERROR_NORMAL,
/*dwErrorControl*/

infected_driver_temp_path,
/*lpBinaryPathName*/

NULL,
/*lpLoadOrderGroup*/

NULL,
/*lpdwTagId*/

NULL,
/*lpDependencies*/

NULL,
/*lpServiceStartName*/

NULL,
/*lpPassword*/

);

After that, StartService is called to load the driver and consequently invoke the injected entry point. Finally, DeleteService is invoked.

Note that deleting the service requires the service to be stopped before deleting it. In our case, the service is still running. Thus, DeleteService will just mark it for deletion, and nothing will take effect until the next restart.

  1. ZwLoadDriver:

If loading the driver through the SCM APIs fails somehow, the sample adds the service key and its subkeys to the registry then it invokes ZwLoadDriver.

After ZwLoadDriver returns, the program sleeps for 3 seconds before calling ZwUnloadDriver and ZwDeleteKey.

AFD.SYS vulnerability:

The other way of gaining code execution in kernel mode is by exploiting a privilege escalation vulnerability in AFD.sys driver. If we examine Microsoft’s advisory for the vulnerability, we see that the date of the patch matches exactly the one Avatar checks against. [1]

Moreover, the shellcode executed by the kernel turns out to issue a call to the same function “Avtr_main_kernel_routine” invoked by the injected entry point when using the previous technique.

Injecting the DLL into explorer.exe:

The 2nd level dropper DLL, which is the topic of this analysis, injects its image into explorer.exe.

We have seen in the previous part that Dllmain receives the Avtr_Structure in the lpReserved argument. Since the DLL loaded into explorer.exe will not have access to this structure and even if it did its fields containing pointers would be invalid, the structure fields need to be changed accordingly by allocating and copying the necessary fields and allocations into explorer’s virtual address space.

This includes the decrypted links and data, the decompressed PE files, the Mem structure, and of course the Avtr_Structure which contains/refers to the whole.

The routine Avtr_LoadDll (sub_10001090) is also copied to explorer’s virtual address space, and it is the starting address of the remote thread’s execution. Obviously, it will load the DLL and invoke its main function. Finally, RtlCreateUserThread is called to create the remote thread.

Conclusion:

In this part, we have completed our analysis of the 1st and 2nd level droppers. In the upcoming articles, we will explore the kernel-mode part starting our analysis with the Avtr_main_kernel_routine function.

References:

[1]: https://support.microsoft.com/en-us/help/2592799/ms11-080-vulnerability-in-ancillary-function-driver-could-allow-elevation-of-privilege-october-11,-2011