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.
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.
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.
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.
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.
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
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 (
.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
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.
system("cmd") as the payload will prompt the user with an interactive session in the context of the
NT AUTHORITY\SYSTEM account via the
Selecting to view the message brings up the following:
We have root!