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.
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 RegQueryValueExA
:
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:
Selecting to view the message brings up the following:
We have root!
Automated Exploit
Link to my GitHub for the advisory and an automated exploit.