Lucene search

K
talosblog[email protected] (Holger Unterbrink)TALOSBLOG:16BE18FDA87437413F6515551BE7AB5E
HistoryNov 01, 2018 - 8:00 a.m.

Talos Vulnerability Deep Dive - TALOS-2018-0636 / CVE-2018-3971 Sophos HitmanPro.Alert vulnerability

2018-11-0108:00:00
[email protected] (Holger Unterbrink)
feedproxy.google.com
72

0.0005 Low

EPSS

Percentile

18.1%

Marcin Noga of Cisco Talos discovered this vulnerability.

Introduction

Sophos patched two vulnerabilities in Sophos HitmanPro.Alert in version 3.7.9.759. We publicly disclosed these issues last week here, Cisco Talos will show you the process of developing an exploit for one of these bugs. We will take a deep dive into TALOS-2018-0636/CVE-2018-3971 to show you the exploitation process.

Sophos HitmanPro.Alert is a threat-protection solution based on heuristic algorithms that detect and block malicious activity. Some of these algorithms need kernel-level access to gather the appropriate information they need. The software’s core functionality has been implemented in the hmpalert.sys kernel driver by Sophos. This blog will show how an attacker could leverage TALOS-2018-0636 to build a stable exploit to gain SYSTEM rights on the local machine.

Vulnerability Overview

During our research, we found two vulnerabilities in the hmpalert.sys driver’s IO control handler. For the purposes of this post, we will focus only on TALOS-2018-0636/CVE-2018-3971, an escalation of privilege vulnerability in Sophos HitmanPro.Alert. First, we will turn it into a reliable write-what-where vulnerability and then later into a fully working exploit.

First, we use the OSR Device Tree tool (Figure 1) to analyse the hmpalert.sys driver’s access rights.

Figure 1. Device Tree application showing hmpalert device privilege settings

We can see that any user logged into the system can obtain a handler to the hmpalert device and send an I/O request to it. Keep in mind for building this exploit, as we mentioned in the original vulnerability blog post, the I/O handler related to this vulnerability is triggered by the IOCTL code 0x2222CC. The vulnerable code looks similar to the one below.

Figure 2. Body of a vulnerable function

The nice thing is that we fully control the first three parameters of this function, but we do not control the source data completely (e.g. the srcAddress needs to point to some memory area related to the lsass.exe process) (line 12).

Additionally, data read from the lsass.exe process (line 23) is copied to the destination address the dstAddress parameter is pointing to (line 33).

With this basic information, we can construct the first proof of concept exploit to trigger the vulnerability:

Figure 3. Minimal proof of concept to trigger the vulnerability

This looks like it could work, but it’s not enough to create a fully working exploit. We need to dig into the inLsassRegions function and see how exactly the srcAddress parameter is tested. We have to check if we will be able to predict this memory content and turn our limited arbitrary write access into a fully working write-what-where vulnerability.

Controlling the source

We need to dive into the inLsassRegions function to get more information about the srcAddress parameter:

Figure 4. The function responsible for checking if the srcAddress variable fits in one of the defined memory regions.
We can see that there is an iteration over the memoryRegionsList list elements, which are represented by the memRegion structure. The memRegion structure is quite simple — it contains a field pointing to the beginning of the region and a second field that’s the size of the region. The srcAddress value needs to fit into one of the memoryRegionsList elements boundaries. If this is the case, the function returns ‘true’ and the data is copied.

The function will return ‘true’ even if only the srcAddress value fits between the boundaries (line 21). If the srcSize value is larger than an available region space, the srcSize variable is updated with the available size line 26. The question is: What do these memory regions represent, exactly? The initMemoryRegionList function will give us an idea.

Figure 5. Initialization of memory regions list.
We can see that the context of a current thread is switched to the lsass.exe process address space and then the createLsaRegionList function is called:

Figure 6. Various memory elements of the lsass.exe processes are added to the memory regions list.

Now we can see that the memory regions list is filled with elements from the lsass.exe PEB structure. There are ImageBase addresses regarding loaded and mapped DLLs added to the list, including the SizeOfImage (line 31), along with other information. Unfortunately, the Lsass.exe process is running as a service. This means with normal user access rights, we won’t be able to read its PEB structure, but we can leverage the knowledge about the mapped DLLs in the exploit in the following way: System DLLs like ntdll.dll are mapped into each process under the same address, so we can copy bytes from the lsass.exe process memory region from these system DLLs into the memory location pointed to by the dstAddress parameter. With that in mind, we can start creating our exploit.

Exploitation

This is not a typical write-what-where vulnerability like you see in the common exploitation training class, but nevertheless, we don’t need to be too creative to exploit it. The presented exploitation process is based on the research presented by Morten Schenk during his presentation at the BlackHat USA 2017 conference. It also includes modifications from Mateusz “j00ru” Jurczyk, which he included in his paper “Exploiting a Windows 10 PagedPool off-by-one overflow (WCTF 2018).” With a few changes, we can use j00ru`s code, WCTF_2018_searchme_exploit.cpp, as a template for our exploit. These changes include:

  1. Removing entire codes related to pool feng-shui.

  2. Writing a class for memory operations using the found primitives in the hmpalert.sys driver.

  3. Updating the important exploit offsets based on the ntoskrnl.exe and the win32kbase.sys versions.
    Then, we will be able to use the mentioned strategy from Morten and Mateusz:

  4. Leak addresses of certain kernel modules using the NtQuerySystemInformation API — We assume that our user operates at the Medium IL level.

  5. Overwrite the function pointer inside NtGdiDdDDIGetContextSchedulingPriority with the address of nt!ExAllocatePoolWithTag.

  6. Call the NtGdiDdDDIGetContextSchedulingPriority(=ExAllocatePoolWithTag) with the NonPagedPool parameter to allocate writable/executable memory.

  7. Write the ring-0 shellcode to the allocated memory buffer.

  8. Overwrite the function pointer inside NtGdiDdDDIGetContextSchedulingPriority with the address of the shellcode.

  9. Call the NtGdiDdDDIGetContextSchedulingPriority(= shellcode).

  10. The shellcode will escalate our privileges to SYSTEM access rights after copying a security TOKEN from the system process to our process.

Test environment

Tested on Windows: Build 17134.rs4_release.180410-1804 x64 Windows 10

Vulnerable product: Sophos HitmanAlert.Pro 3.7.8 build 750

Memory operation primitives

To simplify memory operations, we wrote a class using the found memory operation primitives in the hmpalert.sys driver.

Figure 7. The memory class implementation

The core copy_mem method is implemented like this:

Figure 8. The Memory::copy_mem method implementation

We initialize a couple of important elements inside the class constructor:

Figure 9. The memory class constructor implementation

We can use the write_mem method to write a certain value to a specific address:

Figure 10. The memory class write_mem method implementation
We can not directly copy bytes defined in the data argument. Therefore, we need to search for each byte from the data argument in the ntdll.dll mapped image and then pass the address of the byte to the hmpalert driver via the srcAddress parameter. That way, byte by byte, will overwrite the data at the destination address dstAddress with bytes defined in the data argument. We can easily overwrite necessary kernel pointers and copy our shellcode to the allocated page by using this class:

Figure 11. Shellcode copy operation to an allocated page.

The rest of the exploit is straightforward, so we can leave the implementation as a task for the interested reader.

Fail — Zero-day protection really works!

Armed with a fully working exploit, we are ready to test it. If it works, we should get SYSTEM level privileges.

Figure 12. The elevated console is detected and terminated by the HitmanPro.Alert.
It looks like our exploit has been detected by the HitmanAlert.Pro's anti-zero-day detection engine. Looking at the exploit log, it seems that its entire code was executed, but the spawned elevated console has been terminated.

Figure 13. At the end of the exploit, the console with elevated rights is executed.

We can see in the system event log that HitmanAlert.Pro logged an exploitation attempt and classified it as a local privilege escalation:

Figure 13. Event log showing that it was logged by HitmanAlert.Pro as an attempted privilege escalation.

Using a zero-day to bypass anti-zero-day detection

We know that our exploit works correctly, but the problem is that it’s terminated by the anti-exploitation engine during an attempt to spawn the elevated shell.

We can look at HitmanAlert.Pro’s engine to find out where this function is implemented. The Microsoft Windows API provides the PsSetCreateProcessNotifyRoutine, which can be used to monitor process creation in the OS. Searching for this API call in the hmpalert.sys driver, IDA shows a couple of calls.

Figure 14. Registration of ProcessNotifyRoutine via PsSetCreateProcessNotifyRoutine API.
We do see some places where it registers the callback routine. Let’s look into the implementation of the ProcessNotifyRoutine. While stepping through it, we found the following code:

Figure 15. An implementation of ProcessesKiller function, responsible for the termination of potentially malicious processes.
At line 44, you can see a call to the routine that’s responsible for killing “dangerous/malicious” processes. As we can see at line 5, there is a condition checking whether a global variable dword_FFFFF807A4FA0FA4 is set. If it is not set, the rest of the function code will not be executed. All we need to do is to overwrite the value of this global variable with a value of zero to avoid termination of our elevated console. The final portion of the exploit looks like this:

Figure 16. Overwriting a global variable in the hmpalert.sys driver to trick the ProcessesKiller function, allowing our spawned elevated console to execute.

Time to test our exploit in action.

Final exploit - LPE Windows 10 x64 / SMEP bypass

Summary

Due to the many anti-exploitation features in today’s operating systems, weaponizing vulnerabilities can often be arduous, but this particular vulnerability shows that we can still use some Windows kernel-level flaws to easily exploit bugs in modern Windows systems. This deep dive showed how an attacker could take a vulnerability and weaponize it into a stable, usable exploit. Talos will continue to discover and responsibly disclose vulnerabilities on a regular basis and provide additional deep-dive analysis when necessary. Check out or original disclosure here to find out how you can keep your system protected from this vulnerability.

0.0005 Low

EPSS

Percentile

18.1%

Related for TALOSBLOG:16BE18FDA87437413F6515551BE7AB5E