Turla: APT Group Gives Their Kernel Exploit a Makeover

Turla: APT Group Gives Their Kernel Exploit a Makeover

Authored by: Arunpreet SinghClemens Kolbitsch

The Turla malware family is part of one of the most sophisticated malware families seen in the wild today. Given that the APT group behind this malware is suspected to be state-sponsored, the sophistication of the malicious code comes at no surprise – just like the fact that we are still encountering new and updated variants.

In this part of our series on diving into kernel exploitation, we want to have a look at one of the updated Turla variants: what is particularly noteworthy is that it includes a completely new method of injecting code into the Microsoft Windows operating system (OS) kernel. While the previous version had to disable driver-code signature verification, this variant completely bypasses this security mechanism, making the attack even more stealthy.

New and Stealthier Attack Mechanism, Still Caught

Before we dive into the details on what’s new, there is an interesting observation to be made: while the way the malicious code is injected into the kernel has been updated significantly, the actual behavior of the Turla kernel component has changed very little.

As we describe in the earlier post on Turla, the main functionality of the kernel component is to hide the user-mode components of the malware, and to monitor user-activity (such as network traffic) using filter drivers.

Turla behavior extracted by the Lastline analysis system

Given that the authors behind Turla have not extended the actual malicious behavior, it seems that they are focusing on providing more reliable, stealthy ways to inject code into the OS kernel. This, once again, highlights the importance of having a system that tackles evasive malware.

The Lastline analysis solution uses a full-system emulation approach that provides visibility into every aspect of malicious code. It can track execution of code in user-mode as well as in kernel-mode, and it does so without hooking API functions. As a result, our system is perfectly equipped to handle previously-unseen, zero-day attacks against our users.

Turla Kernel Code Injection Exploit

Just like the previously discussed variant of Turla, this updated variant also makes use of a VirtualBox vulnerability to manipulate the OS kernel. This vulnerability has already been well documented, so we only want to give a short recap here:

When VirtualBox is installed, arbitrary processes can communicate with its driver, and send it commands to load or create virtual machine (VM) instances from a configuration. As part of this VM configuration, the user can specify a VM image as well as a callback function used by the driver for handling certain IOCTLs.

Therefore, from an attacker’s perspective, the vulnerable version of the driver allows to load arbitrary data (including any shellcode) into the kernel, and then specify a function to be executed for IOCTL handling. Nothing prevents the attacker to point this handler into the VM data to execute user-provided shellcode.

An additional bug in the vulnerable driver also allows an attacker to write arbitrary values to any kernel memory address specified by the user. The only caveat is that the value to be written has to be returned by a function pointer passed to the driver – since this function is controlled by the user as well, it is not a significant restriction.

This second bug was used by a previous variant of Turla to write a value of 0 to kernel memory, using the following simple callback function:

As we explained in our previous post, this allows the malware to disable driver code-signature verification by overwriting the kernel variable g_CiEnabled with a value of 0, and, in turn, enabling the loading of unsigned and malicious code into the kernel.

Interestingly, this same vulnerability and exploitation technique is now being used by a publicly available tool to disable code signing. Clearly, this is an alarming trend from the perspective of the security industry!

Code Injection Wrapper

In the updated malware variant, the attackers equipped Turla with completely new shellcode: the shellcode allows loading unsigned code into the kernel directly – that is, without first having to disable driver signature verification. This way, the attack is more resilient to being detected, as no suspicious modifications of kernel variables are performed.

Most of the changes from previous versions took place in the main user-mode component of the Turla malware (usually referenced as pxinsi64.exe). The new version takes the name of a named section object as a command line argument. This section contains the PE image of an unsigned driver, which will later be loaded into the kernel through the exploit. pxinsi64.exe maps the named section, loads the driver code, combines it with additional shellcode (contained in one of its PE sections), and writes this into the VM image.

Next, it queries for SYSTEM_MODULE_INFORMATION via NtQuerySystemInformation and adds this information to the VM image. This data is needed later while executing in the kernel and saves the hassle of resolving the base-address of Ntoskrnl.exe while in the context of the operating system kernel. The pxinsi64.exe executable also adds a list of strings that correspond to API names to the VM image: these names are later used to resolve function pointers in kernel code, such as ExQueueWorkItem, ZwOpenSection, ZwMapViewOfSection, ExAllocatePoolWithTag, and ExFreePoolWithTag.

Last, the malware program creates and maps a named section (of 16 bytes), and adds this section’s name into the VM data. This section is later used to pass status information from the code running in kernel-mode back to the user-mode counterpart.

The disassembly above shows how pxinsi64.exe combines the individual parts to prepare a VM image that exploits the vulnerable VirtualBox driver.

Updated Turla Shellcode

With all pieces put in place by pxinsi64.exe described above, the shellcode becomes somewhat straightforward, and interestingly lacks any form of encryption at this point.

First, it finds the base-address at which Ntoskrnl.exe is loaded in kernel memory, which is provided by the SYSTEM_MODULE_INFORMATION struct loaded with the shellcode. From there, the shellcode parses the export table of Ntoskrnl.exe to resolve all addresses of API functions that are part of the list described above.

Next, it calls ExQueueWorkItem, passing a second-phase shellcode. From the function description on MSDN:

        ExQueueWorkItem inserts a given work item into a queue from which a system worker thread removes the item and gives control to the routine that the caller supplied to ExInitializeWorkItem.

This means that the kernel will find the work item pointing to the next-phase shellcode and execute it. The code disassembly below shows how the function names are resolved to memory addresses and subsequently are called:

As part of this last phase, the shellcode opens and maps the shared section provided in the VM data. Then, it uses ExAllocatePoolWithTag to allocate memory which the user-provided driver is copied to, relocating the driver and fixing its imports – this last step is actually very easy, as the driver has already been loaded in user space and only needs to be relocated to its new base-address.

The shellcode finds the entry point to the payload code by parsing the PE header of the driver and calls it. Finally, it writes the return value of the DriverEntry() function as status-flag to the shared memory section.

Evasive Kernel Code Injection

While we can only speculate on the exact reasons why the attackers behind Turla have updated their code injection mechanism, it is clear that this variant is an improvement in a number of ways.

First, Turla is now compatible with newer versions of the Microsoft Windows kernel: in Windows 8 (and later), PatchGuard protects the kernel variable controlling driver signature verification (g_CiOptions, or g_CiEnabled in earlier versions of the OS). Thus, any modification to this flag, without disabling PatchGuard beforehand, results in the notorious Blue Screen of Death (BSOD). The new code-injection method works without modifying this variable, and, as a result, works despite PatchGuard.

A second benefit of the updated injection mechanism is that the malicious driver is never written to disk. This means that security products that scan the file-system for suspicious objects will not be able to see the driver, and be evaded.

Last, the new variant does not have to call the LoadDriver function, which offers security products a reliable way to intercept new code that is loaded into the OS kernel. Without this call, security products must have other means to identify new code injected into the kernel. The Lastline sandbox, for example, is based on full-system emulation and can see every instruction that is executed within the analysis environment. Thus, we immediately recognize when new code is executed in the context of the kernel, and, hence, we are not vulnerable to this type of evasion.

An interesting point to note: Whenever the Lastline solution encounters new, untrusted code executing inside the sandbox, it generates a snapshot containing this code (stripped of other, benign and unrelated code regions). We have covered this functionality in a previous blog-post, but it is worthwhile mentioning that the same also applies to code executing in the kernel. This way, analysts can get further insights by analyzing malicious drivers with off-the-shelf tools such as IDA Pro.


APT groups are constantly on the move, attacking new targets, and improving their weapons. Looking at the evolution of sophisticated malware, such as Turla, we constantly see new tricks used by attackers to compromise OS kernels and to hide from security solutions.

In addition to this increasing sophistication, malware is also becoming more difficult to analyze for security solutions: new attack vectors have to be covered, and this is only reasonably possible if the sandbox can track the entire execution of the malware sample, regardless if it executes in the context of a user-mode application or as part of a kernel driver; whether invoked directly or executed as part of a kernel callback.

Only full visibility allows detection, everything else will inevitably fail.