When performing vulnerability research, it is essential to make sure that all attack vectors concerning exploitation are exhausted. One avenue of exploitation comes from the Windows registry. For an attacker, the Windows registry holds critical data that applications may rely on during runtime. Not only do applications rely on this data, but also services and even the Windows OS itself relies on the information inside of the registry to function correctly.
The Windows registry is a database that holds configuration data for the Windows OS, along with different applications, and services that choose to utilize this functionality. These applications and services can read, write, and modify the registry as well if given the proper permissions.
From the perspective of an attacker, the Windows registry is an attractive target. This is because many different use cases exist for why an application or service may utilize this extended functionality. The use cases recommended by Microsoft is for configuration and runtime functionality.
When dealing with the Windows registry, it is imperative to assign proper permissions to each key created. The reason for this is to prevent unauthorized access to potentially critical data within a given registry key. The next topic of interest is understanding how to enumerate registry key permissions.
When it comes to the Windows operating system, permissions are assigned using Access Control Lists (ACL). ACL’s are composed of Access Control Entries (ACE’s) which hold two separate permission structures.
The first is a Discretionary Access Control List (DACL) and the second is a SYSTEM Access Control List (SACL). The DACL is the one we are most interested in since it defines who can access a specified resource to modify, write, read, or execute respectively. The SACL, on the other hand, is for logging purposes for the security event log.
To view the DACL’s of a given resource, we can use the tool AccessEnum from the SysInternals Suite. AccessEnum is a versatile tool that offers functionality to quickly enumerate the permissions of resources. Beyond just enumerating files and folders within the Windows operating system, it can also enumerate the permissions of keys within the Windows registry Hive. When using AccessEnum for permission enumeration, the column Write is what holds the most valuable information.
This column provides information on what users or groups are allowed permissions synonymous with the ability of write permissions for each resource, or in this case, each registry key scanned. AccessEnum offers the ability to navigate the registry hive by default with an option available to end users. This is performed through the Registry option provided by AccessEnum its self, and Figure 1 depicts this option.
Figure 1: AccessEnumRegistry scan example
After selecting this option, a new window pop-up will show, offering the ability to select any registry key available. For this example, the registry key HKEY_LOCAL_MACHINE was selected. This scan enumerates every Subkey within this primary Key and displays permissions respectively for each Subkey. In Figure 2, this example of permission enumeration can be reviewed.
Figure 2: AcccessEnum Registry scan results
When it comes to reviewing registry key permissions, it is important to note the use of specific permissions. These different permissions are related to different functionality that is exposed through the Windows registry. One such example is the permission Set Value. This permission allows for the ability to modify values within a registry key.
A complete list of the permissions including the specific ones are provided in the list below:
Now that we have covered general information and permissions concerning the Windows registry, the next section details a Local Privilege Escalation (LPE) vulnerability affecting a System level Service through the improper usage of Windows registry key permissions.
Abusing Registry Key Entries with a Real-World Vulnerability
Intro – Waves Maxx Audio Registry Key Abuse LPE to System
Waves Maxx Audio (Waves) is a software suite that offers advanced audio settings and manipulation for consumer and professional products. This software comes preinstalled on a few different Dell laptop’s. Waves installs a service named WavesSysSvc that is configured to start automatically and starts as LocalSystem at runtime.
This service establishes a handle to a registry key called General and queries an entry with a name value of ExternalModule that holds a semi-colon separated string as its data value. This string contains Dynamic Link Library (DLL) names that are loaded into the process memory when it is started. This registry key has improper permissions that allow any user in group Users to modify the entries within the key. An attacker may abuse the permissions assigned in order to conduct a DLL side-loading attack and achieve local privilege escalation.
Discovery via Dynamic Analysis
This vulnerability was discovered during runtime analysis using the tool Procmon from SysInternals Suite. Procmon is a versatile tool that is used to view different types of operations performed by an application. The specific operations that lead to this discovery were Load Image and RegQueryValue. In Figure 3 this specific Filter can be seen within Procmon.
Figure 3: Procmon filters
These filters provide useful information regarding any potential registry key entry where the data value holds references to either a DLL or a binary application executable. The RegQueryValue operation relates to the Windows API function RegQueryValue respectively and returns the results of certain data values within a registry key entry in the Detail column. The second operation, Load Image, is used to capture functionality that is related to the loading of a library or image into a process’ memory region. When analyzing the output of the resulting filter, we find that a DLL referenced in the Detail column of a RegQueryValue operation is also present in the Load ImagePath column. This is seen in the Procmon CSV output below.
After seeing this, the first thing we checked is the permissions associated with the File MaxxAudioIntelKabylake64.dll. Given that this file is located within the C:WindowsSystem32 path a user may not have the proper permissions to abuse this local resource. Checking the permissions associated with this resource can be done using the tool AccessEnum from SysInternals Suite. To do this, we copied the absolute path present within the Path column of the Load Image operation to the search section of AccessEnum and then hit the Scan button. The results of the permissions associated with this file can be viewed in the lower pane window of AccessEnum.
Figure 4: AccessEnum Scan Results for MaxxAudioIntelKabylake64.dll
Reviewing the results of the permissions for this given resource reveals that only NT AUTHORITYSYSTEM has the permissions to write to this file. These permissions are not suitable since normal users cannot modify or alter this file.
The next thing we checked is the registry key that is referenced before this Load Image operation is executed. This is because shortly after the RegQueryValue operation is performed, this DLL is loaded into the WavesSysSvc64.exe process memory region. We used the tool Regedit from Microsoft to view the permissions associated with this given registry key. Using Regedit, we navigated to the same registry key path location that was recorded from Procmon.
Figure 5: Regedit MaxxAudioGeneral Key View
Next, right click on the key named General and select the option called Permissions. After selecting this option, locate the Users group and review the permissions associated. Given that the group Users has complete control over this General registry key, manipulation of entries within this key can be performed.
Now that we’ve verified that the Users group is assigned the Full Control permission over the registry key named General, the next step is verifying this information by performing both static and dynamic analysis of the Waves service binaries in the next section.
Root Cause Analysis
Further analysis is required so that a deeper understanding of why this vulnerability exists and how it can be abused. To perform this task, two main tools will be utilized. These tools are IDA Pro from Hex-Rays and WinDbg Preview from Microsoft. Now, when dealing with IDA Pro analysis, I will only cover the essential sections concerning the vulnerability. For WinDbg Preview, we used the independent binary version of Time Travel Debugging (TTD). TTD is used because of its ability to review a Trace file to gain insight into memory, instructions being executed, and values of arguments being passed to functions at runtime.
The first task we performed is that of identifying the service binary responsible for functionality associated with the Waves service. We used the command line tool sc, which is used to gather information and issue commands to services. The information we want to gather from the output of the sc tool is the BINARY_PATH_NAME field for the Waves service. We used the qc flag, which queries a specific service for configuration information.
Figure 6: SC service BINARY_PATH_NAME results
From the image above, it can be seen that the BINARY_PATH_NAME holds an absolute path to the Waves service binary. This binary is what is executed once this service is started. With this information, we then used WinDbg’s Time Travel Debugger, to record the initial runtime functionality. To perform this action, we used the tool GFlags from Microsoft. GFlags can be used in both command line and a Graphical User Interface (GUI) mode with the same functionality. This tool will be used to set the WinDbg tool TTD as the debugger option to create a Trace file once this service is started.
The tool GFlags is included as part of WinDbg and may be downloaded here. After this is properly installed, start Gflags.exe, and a window like Figure 7 shall be present.
Figure 7: Gflags default view
Next, select the Image File tab near the top. In this new view, two spots will be modified. The first is the Image section. For this section, type the name of the service binary WavesSysSvc64.exe then hit the keyboard key Tab. After this, everything else should become available to modify. The second option to change is the debugger option. For this option, pass the full path to the Time Travel Debugging TTD binary along with the arguments seen in the snippet below:
The full path where TTD is located may be different, so make sure to change that to the current path for each environment. The -maxfile option is for setting a max size limit for the trace file to 2GB. The -out command specifies the location of where to store the trace file and finally the -launch command is used. The reason the -launch command has no following binary to launch is because after setting this value, GFlags automatically assigns the service binary as the last argument being passed. Since the service binary is the last argument being passed, make sure to include a trailing white space after the -launch command. Finally, select apply, and these new settings will be instantly applied.
The reason we set the TTD binary as the debugger option inside of Gflags is because WinDbg Preview’sTTD does not support active tracing of Session 0 processes through default usage. Using GFlags, this limitation can be bypassed because, GFlags starts TTD in the same session as the Waves service binary allowing for the ability of Tracing to be performed.
After setting these options within GFlags, we then started the Service and verified that everything was working correctly. To perform this task, the tool Procexp from SysInternals Suite will be utilized. Procexp is a tool that is used to review detailed information about running processes. Once Procexp is open, locate the TTD.exe binary executable and then terminate the process. By terminating the TTD process, the trace will complete and be appropriately recorded and can be provided to WinDbg preview for analysis.
As mentioned before in earlier sections, this vulnerability is centered around DLL side-loading. Now, that attack is quite vague and can be accomplished in many ways. However, the main thing is that a DLL is loaded into a processes memory region. Armed with this knowledge, we can infer that the LoadLibrary functions exposed via the Kernel32 library are utilized. These LoadLibrary functions can be called many times so by using a simple WinDbg command one-liner, we can iterate over all the calls to LoadLibraryA, print out the contents of the argument being passed via the register RCX, and dump the call stack. This one-liner can be seen below:
After setting this command and running the debugger, the output of the command is present. We are looking for the specific location where calls that hold an argument value equal to our first DLL referenced in the entry name ExternalModuleGeneral registry key. While reviewing this output the first DLL referenced is found, and the output from the one-liner can be seen below:
As we can see from the command output, as we enter the LoadLibraryA function, the argument passed holds our specific DLL name. After that, a call stack was generated. Following the call stack information, a ModLoad is present. Modload signals that the DLL referenced has been loaded into the process memory region. So, given this information, we can infer that the instruction before the address offset at MaxxAudioAPOShell64!WavesFX_Preset_SetSoundMode+0x54895 is in fact the LoadLibraryA function. The next step is to figure out how this value is acquired from the ExternalModule registry entry. At this point, we will use the tool IDA Pro and disassemble the MaxxAudioAPOShell64.dll. First, we searched for usage of the function RegQueryValue, as this function is used to return the data value associated with a specific entry name in a registry key. However, for this function to operate successfully a handle must first be created for the registry key General. This is performed through the usage of the function RegCreateKey. So, after a bit of cross-referencing and some more debugging with WinDbg, the specific RegCreateKey function was found. This information can be seen in the snippet below:
According to the code snippet above, the registry key and its path should be passed via the argument lpSubKey (RDX) to RegCreateKeyExA function. To verify this, we set a breakpoint at address offset 0x2C67B inside of the MaxxAudioAPOSHell64.dll using WinDbg and displayed the contents of the RDX register. The contents of this register along with the WinDbg output can be seen in the snippet below:
After the handle is created, we then enter the renamed function AAA_regqueryvalue where the data values from the above registry entry ExternalModule is enumerated. So, if we step into this function, we will find the usage of the RegQueryValueExA function. When reviewing this function call, we notice that the ExternalModule registry key entry name is being passed. This can be seen in the related code snippet below:
Now, according to Microsoft’s documentation for the function RegQueryValueExA, if we want to see the data values that are recovered from the entry named ExternalModule, we will have to display the contents of [rsp+218h+lpData] after this function call. The following WinDbg snippet below shows the output that we expected to see.
This semi-colon separated string that was enumerated from the data value of the ExternalModule key entry, is then later passed as an argument to an _mbschr function call. The code responsible for this can be seen in the IDA Pro snippet below:
The function _mbschr, will return a pointer that is shifted to the first occurrence of the token specified. In our case, the returned pointer is pointing to the ; token referenced from the data value. The following code after this function call, essentially copies the first string recovered before our token ; and passes it to our final function that holds the call to LoadLibraryA via the RDX register.
After entering the final function where the vulnerable call to LoadLibraryA exists, a strcpy_s function is also called shortly before. This strcpy_s function copies the reference of our first DLL name into the memory allocation that RCX currently points to. At the start of this function, a reference of RCX is stored into the register RBX and when this strcpy_s function is called; all references are updated with the first DLL name from the ExternalModule data value.
After the referenced memory location is updated via the call to strcpy_s, the final function LoadLibraryA is called where the first DLL referenced is loaded. The code responsible for this can be seen below in the IDA Pro code snippet.
Finally, we will set a breakpoint at the address offset 0x5AC1F inside of the DLL MaxxAudioAPOShell64.dll and dump the contents of the Resource name argument passed via RCX to LoadLibraryA function. If the output from WinDbg backs up our assumptions, then we know our analysis was accurate. The results of WinDbg can be seen in the output snippet below:
Reviewing the above output, we can verify that our analysis was correct. The main points covered during this phase of analysis were the creation of the registry key, the enumeration of registry key entry name ExternalModule data values, the token string search to identify each DLL to load, and finally, the vulnerable function call its self. Now that we have covered the analysis section of this vulnerability, the last stage is exploitation!
We will exploit this vulnerability by replacing the first DLL referenced before the first token ; with the absolute path to our malicious DLL. Inside of our malicious DLL, we will place the start of our payload inside of the function DLLMain. The DLLMain function in reference to DLL’s is unique because whenever a DLL is loaded into a process memory region via LoadLibrary or similar functions, the DLLMain function is automatically executed. Given that the Waves service is configured to runs as the account LocalSystem, any child processes that are spawned will inherit the permissions of system.
Now that we have covered the high-level theory behind this attack, the next thing to cover is the actual exploitation of this vulnerability. The first thing we did was build a simple DLL where the DLLMain function calls another function called bind_shell, that spawns a bind system level shell on the local port 4444.
This Proof of Concept (PoC) code is used to showcase that the ability to gain a system level interactive command prompt session is possible. The code responsible for this can be seen in the code snippet below:
We used Python to automate the overwriting of data within the entry name ExternalModule registry key General. The Python based PoC can be seen below:
This PoC Python script accepts a single argument. This argument is the absolute path of the malicious DLL. This script takes that argument and appends it to the variable reg_value_data that is passed to the function set_registry. The function set_registry performs the action of modifying the entry named ExternalModule data value accordingly. This Python script can run successfully from any user account in the Users group. The output below displays the output from the Python script once executed.
After running this PoC script and manually restarting the Waves service, we use ncat to establish a connection to localhost on port 4444 to gain a SYSTEM level shell. This can be viewed in the command window below:
At VerSprite, we have noticed that the trend of improper permission assignment on critical resources is quite common. The impact of improper permissions assignment ranges from information leakage to code execution. To counter this, when handling the creation and or usage of registry keys and accompanying value entries, it is essential to apply proper permissions. By applying proper permissions, the attack presented in this blog would have been prevented.
If you are interested in further reading of vulnerabilities in Windows software, below is a list of recent vulnerabilities and exploitation analysis:
VerSprite’s Research and Development division (a.k.a VS-Labs) is comprised of individuals who are passionate about diving into the internals of various technologies. Our clients rely on VerSprite’s unique offerings of zero-day vulnerability research and exploit development to protect their assets from various threat actors. From advanced technical security training to our research for hire B.O.S.S offering, we help organizations solve their most complex technical challenges. Learn more →