Marcin Noga of Cisco Talos discovered this vulnerability.
Sophos patched two vulnerabilities in Sophos HitmanPro.Alert in version 18.104.22.1689. 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.
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
We need to dive into the
inLsassRegions function to get more information about the
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:
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:
Leak addresses of certain kernel modules using the NtQuerySystemInformation API — We assume that our user operates at the
Medium IL level.
NtGdiDdDDIGetContextSchedulingPrioritywith the address of
=ExAllocatePoolWithTag) with the
NonPagedPoolparameter to allocate writable/executable memory.
NtGdiDdDDIGetContextSchedulingPrioritywith the address of the shellcode.
Tested on Windows: Build 17134.rs4_release.180410-1804 x64 Windows 10
Vulnerable product: Sophos HitmanAlert.Pro 3.7.8 build 750
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
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.
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.
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
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.
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.