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

Marcin Noga of Cisco Talos discovered this vulnerability.


After disclosing two vulnerabilities in Sophos HitmanPro.Alert on Thursday, 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.


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:
  1. Leak addresses of certain kernel modules using the NtQuerySystemInformation API — We assume that our user operates at the `Medium IL` level.
  2. Overwrite the function pointer inside `NtGdiDdDDIGetContextSchedulingPriority` with the address of `nt!ExAllocatePoolWithTag.`
  3. Call the `NtGdiDdDDIGetContextSchedulingPriority`(`=ExAllocatePoolWithTag`) with the `NonPagedPool` parameter to allocate writable/executable memory.
  4. Write the ring-0 shellcode to the allocated memory buffer.
  5. Overwrite the function pointer inside `NtGdiDdDDIGetContextSchedulingPriority` with the address of the shellcode.
  6. Call the `NtGdiDdDDIGetContextSchedulingPriority`(`= shellcode`).
  1. 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



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.

Article Link: http://feedproxy.google.com/~r/feedburner/Talos/~3/-aaW_K4WTu0/TALOS-2018-0636.html