from Microsoft Malware Protection Center
Out of the multiple components used in the sophisticated Duqu 2.0 cyberespionage attack, we had a chance to look into one of the kernel exploits used for its elevation-of-privilege attack.
Early this year, Kaspersky Lab discovered Duqu 2.0 and named it as such due to its close similarity to the original Duqu malware. Microsoft patched the vulnerability and published Security bulletin MS15-061 on June 9, 2015.
This blog takes a closer look at the exploitation technique used and not the vulnerability itself.
The road to corruption and control
The nature of the vulnerability itself is very straightforward. After the userland process registers its own ClientCopyImage callback, and when the callback is called from kernel, it destroys the Windows object. It also unregisters the associated class that triggered the callback, which leads to use-after-free condition. When the vulnerability is easy to understand, we found that the exploitation technique used was very complicated.
By indirectly allocating a structure just after the use-after-free condition, the attacker can control what happens next. The exploit calls the NtUserThunkedMenuItemInfo function. The call allocates various objects in place of the freed memory location. The exploit then allocates objects to guarantee alignment precision such that the attacker-controlled address is used as a pointer to an object that is passed to HMUnlockObject, which is called by the kernel after the ClientCopyImage callback finishes.
The pointer falls just to the right location inside the tagCLS object to overwrite the cbclsExtra field when the instruction inside HMUnlockObject decreases the object reference counter. The tagCLS object address is calculated using the _MapDesktopObjectWin32k function.
Figure 1: How the use-after-free condition works
From the code below, rcx points inside of one of the tagCLS objects that the fake object points to. The instruction highlighted in yellow decreases the DWORD value of that memory location.
Figure 2: HMUnlockOBject to corrupt a memory location
The corruption target, rcx+8, points to cbclsExtra field of the tagCLS object. The tagCLS object is pre-allocated by calling a series of Windows APIs. The field indicates the size of extra class memory. Usually, APIs like GetClassLong and SetClassLong, are used to access extra class memory.
Figure 3: Original tagCLS object
The field is initialized to 0 in this case, which means there is no extra memory for this class. But with the HMUnlockObject instruction’s corruption of the memory, it becomes -1 or 0xffffffff in unsigned DWORD form.
Figure 4: Corrupt tagCLS object
With the corrupt cbclsExtra field, the exploit can freely access extra memory address space using GetClassLong and SetClassLong API sets.
Because the code used ja instruction to check the maximum value for the APIs’ index parameter, there is an unsigned comparison between 0xffffffff and the index value. It then allows the exploit to access a wide range of kernel memory with read and write privilege.
Figure 5: Out of bounds index
Opening up 64-bit memory address for read and write access
The tactic the attacker chose after the first corruption stage is also very interesting. It looks for a specific structure inside the tagWND class. The location of tagWND and its member object is calculated using the _MapDesktopObjectWin32k function.
By carefully calculating the tagWND objects’ location inside the kernel based on the object returned from the call, it locates the strName member variable inside the tagWND object by adding 0x0d8 value to the base of object.
Figure 6: Locating tagWND.strName
It is very interesting to know the reason why the exploit is using this field. Even when you have a wide range of read/write access to the system memory, you don’t cover the whole 64-bit memory space with GetClassLong and SetClassLong APIs. This is because they are bound by 32-bit index value even if the exploit runs on a 64-bit system. It is also not easy to know what address you are actually reading or writing. The exploit’s tactic is to corrupt the strName.Buffer member variable from tagWND and use it as leverage for further memory access. This time, it has full memory access with 64-bit memory range and with arbitrary length of data.
For example, from the following API log, NtUserSetClassLongPtr API was used to set the tagWND.strName.Buffer value to fffff6fb7dbedf90, which is arbitrary kernel memory location. If the InternalGetWindowText function is called, it retrieves bytes from the designated tagWND.strName.Buffer location.
Therefore, if you know the address of the memory location, you can read any data from the kernel.
Figure 7: Using InternalGetWindowText API to read from kernel memory
Another way is also possible, which is writing arbitrary memory location with any data. Use NtUserSetClassLongPtr API to set the tagWND.strName.Buffer value and call NtUserDefSetText function. It then writes any designated bytes to the target kernel memory location.
Figure 8: Using NtUserDefSetText API to write to kernel memory
In this way, malware perpetrators use the simple use-after-free vulnerability as a very powerful exploit that has full kernel memory access. Even when the exploit acquires full memory read and write privilege on the kernel, it is still tough to achieve code execution.
In the next blog, we will discuss the stage after this step, which is also quite interesting because it is about Windows kernel mitigations.
Acknowledgement: Thanks to Elia Florio for his advice on kernel land vulnerability analysis.
MMPC
Jeong Wook Oh