Anti-virus Exploitation: Local Privilege Escalation in K7 Security (CVE-2019-16897)

Anti-virus Exploitation

Hey guys, long time no article! Over the past few months, I have been looking into exploitation of anti-viruses via logic bugs. I will briefly discuss the approach towards performing vulnerability research of these security products using the vulnerability I discovered in K7 Security as an example.

Disclaimer: I do not claim to know everything about vulnerability research nor exploitation so if there are errors in this article, please let me know.

Target Selection

Security products such as anti-viruses are an attractive target (at least for me) because they operate in a trusted and privileged context in both the kernel, as a driver, and userland, as a privileged service. This means that they have the ability to facilitate potential escalation of privilege or otherwise access privileged functionality. They have a presence in the low-privileged space of the operating system. For example, there may exist a UI component with which the user can interact, sometimes allowing options to be changed such as enabling/disabling anti-virus, adding directory or file exclusions, and scanning files for malware. Anti-viruses must also access and perform operations on operating system objects to detect malware, such as reading files, registry keys, memory, etc. as well as being able to do privileged actions to keep the system in a protected state no matter the situation. It is between this trusted, high privilege space and the untrusted, low privileged space where interesting things occur.

Attack Surface

As aforementioned, anti-viruses live in both sides of the privilege boundary as shown in the following diagram:


Whatever crosses the line between high and low privilege represents the attack surface.

Let’s look at how this diagram can be interpreted. The user interface shares common operations with the service process which is expected. If the user wants to carry out a privileged action, the service will do it on its behalf, assuming that security checks are passed. If the user wishes to change a setting, they open the user interface and click a button. This is communicated to the service process via some form of inter-process communication (IPC) which will perform the necessary actions, e.g. the anti-virus stores its configuration in the registry and therefore, the service will open the relevant registry key and modify some data. Keep in mind that the registry key is stored in the HKEY_LOCAL_MACHINE hive which is in high privilege space, thus requiring a high privilege process to modify its data. So the user, from low privilege, is able to indirectly modify a high privilege object.

One more example. A user can scan for malware through the user interface (of course, what good is an anti-virus if they disallow the user from scanning for malware?). A simple, benign operation, what could go wrong? Since it is the responsibility of the service process to perform the malware scan, the interface communicates the information to the service process to target a file. It must interact with the file in order to perform the scan, i.e. it must locate the file on disk and read its content. If, while the file data has been read and is being scanned for malware, and the anti-virus does not lock the file on disk, it is possible for the malware to be replaced with a symbolic link pointing to a file in a high privileged directory (yes, it is possible), let’s use notepad.exe. When the scan is completed and has been determined to be malware, the service process can delete the file. However, the malware has been replaced with a link to notepad.exe! If the anti-virus does not detect and reject the symbolic link, it will delete notepad.exe without question. This is an example of a Time of Check to Time of Use (TOCTOU) race condition bug. Again, the user, from low privilege, is able to indirectly modify a high privilege object because of the service process acting as a broker.


Exploitation

This vulnerability allows a low privilege user to modify (almost) arbitrary registry data through the anti-virus’s settings. However, a low privileged user (non administrator) cannot should not be able to change the anti-virus’s settings.

Admin check

Bypassing Administrative Checks

To narrow down how this administration check is performed, procmon can be used to identify operating system activity as the settings page is accessed again. This will trigger the anti-virus to recheck the administrative status of the current user while it interacts with the operating system as it is being logged. Of course, since we are low privilege and procmon requires high privilege, it is not practical in a real environment. However, because we control the testing environment, we can allow procmon to run as we have access to an administrator account. Setting promon to filter by K7TSMain as the process name will capture activity performed by the user interface process.

procmon%20filter
When procmon starts to log, attempting to access the settings page again in the UI will trigger procmon to instantly show results:


It can be seen that the anti-virus stores the administrative check in the registry in AdminNonAdminIsValid. Looking at the value in the Event Properties window shows that it returned 0, meaning that non administrator users are not allowed. But there is a slight problem here. Bonus points if you can spot it.

Now that we know where the check is being performed, the next step is bypassing it. procmon shows that the process is running in low privilege space as indicated by the user and the medium integrity meaning that we own the process. If it is not protected, we can simply hook the RegQueryValue function and modify the return value.


Attempting to attach to the K7TSMain.exe process using x32dbg is allowed! The breakpoint on RegQueryValueExA has been set for when we try to access the settings page again.


x32dbg catches the breakpoint when the settings page is clicked. The value name being queried is ProductType but we want AdminNonAdminIsValid, so continuing on will trigger the next breakpoint:


Now we can see AdminNonAdminIsValid. To modify the return value, we can allow the function to run until return. However, the calling function looks like a wrapper for RegQueryValueExA:

RegQueryValueExA%20wrapper%20function
So continuing again until return reveals the culprit function that performs the check:


There is an obvious check there for the value 1 however, the current returned value for the registry data is 0. This decides the return value of this function so we can either change [esp+4] or change the return value to bypass the check:

Intercepting Inter-process Communication

Multiple inter-process communication methods are available on Windows such as mailslots, file mapping, COM, and named pipes. We must figure out which is implemented in the product to be able to analyse the protocol. An easy way to do this is by using API Monitor to log select function calls made by the process. When we do this and then apply a changed setting, we can see references to named pipe functions:


Note that the calling module is K7AVOptn.dll instead of K7TSMain.exe. If we have a look at the data being communicated through TransactNamedPipe, we can see some interesting information:


The first thing that pops out is that it looks like a list of extension names (.ocx, .exe, .com) separated with | where some have wildcard matching. This could be a list of extensions to scan for malware. If we have a look at the registry where the anti-virus stores its configuration, we can see something similar under the value ScanExtensions in the RTFileScanner key:


Continuing down the list of calls, one of them contains some very intriguing data:


It looks as though the anti-virus is applying values by specifying (privileged) registry keys and their values by their full key path. The next obvious step is to see if changing one of the keys and their values will work. This can be done by breakpointing on the TransactNamedPipe function in x32dbg:


Once here, locate the input buffer in the second argument and alter the data to add or change a key in the HKEY_LOCAL_MACHINE hive like so:


If it is possible to change this registry key’s values, high privileged processes will be forced to load the DLLs listed in AppInit_DLLs, i.e. one that we control. The LoadAppInit_DLLs value must also be set to 1 (it is 0 by default) to enable this functionality. The result:

Triggering the Payload

You may have noticed that the registry key resides within Wow6432Node which is the 32-bit counterpart of the registry. This is because the product is 32-bit and so Windows will automatically redirect registry changes. In 64-bit Windows, processes are usually 64-bit and so the chances of loading the payload DLL through AppInit_DLLs is unlikely. A reliable way is to make use of the anti-virus because it is 32-bit assuming a privileged component can be launched. The easiest way to do this is to restart the machine because it will reload all of the anti-virus’s processes however, it is not always practical nor is it clean. Clicking around the UI reveals that the update function runs K7TSHlpr.exe under the NT AUTHORITY\SYSTEM user:

As it is a 32-bit application, Windows will load our AppInit_DLLs DLL into the process space.


Using system("cmd") as the payload will prompt the user with an interactive session in the context of the NT AUTHORITY\SYSTEM account via the UI0Detect service:

UI0Detect
Selecting to view the message brings up the following:


We have root! :sunglasses:

Automated Exploit


Link to my GitHub for the advisory and an automated exploit.

11 Likes

Very good write-up!
Can you please me explain how you automated the exploit or link me some references? Thank you very much

Hey, thanks for reading!

Basically the automation process involved some (hardcoded) fancy UI clicking emulation (because I was too lazy to reverse engineer any further) and hooking the RegQueryValueExA and TransactNamedPipe functions. It goes a little something like this:

  1. Inject hooks into the two functions,
  2. Click into the anti-virus settings,
  3. Match the AdminNonAdminIsValid string parameter and replace the return value in the RegQueryValueExA function hook,
  4. Click to modify a setting,
  5. Match the registry keys’ strings and replace the values in the TransactNamedPipe function hook,
  6. Spawn the K7TSHlpr.exe process by launching an instance of the K7 update program to activate the payload DLL injection.
2 Likes

the github post on this only has the release binary. any chance you would release the source code to help me understand writing exploit code for this?