Marcin Noga of Cisco Talos discovered this vulnerability.
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.
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.
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:
Removing entire codes related to pool feng-shui.
Writing a class for memory operations using the found primitives in the hmpalert.sys driver.
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.
Overwrite the function pointer inside NtGdiDdDDIGetContextSchedulingPriority
with the address of nt!ExAllocatePoolWithTag.
Call the NtGdiDdDDIGetContextSchedulingPriority
(=ExAllocatePoolWithTag
) with the NonPagedPool
parameter to allocate writable/executable memory.
Write the ring-0 shellcode to the allocated memory buffer.
Overwrite the function pointer inside NtGdiDdDDIGetContextSchedulingPriority
with the address of the shellcode.
Call the NtGdiDdDDIGetContextSchedulingPriority
(= shellcode
).
The shellcode will escalate our privileges to SYSTEM access rights after copying a security TOKEN from the system process to our process.
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
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.
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 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.
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.