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.
This blog post details information about the Windows registry and how improper permissions assignment on registry keys can lead to vulnerabilities in applications or services that utilize this functionality.
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.
Permissions are a core component of operating systems and implemented in many ways.
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
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.
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.
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.
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.
This vulnerability was discovered during runtime analysis using the tool
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
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
Path 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:\Windows\System32\ 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
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
Reviewing the results of the permissions for this given resource reveals that only
NT AUTHORITY\SYSTEM 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
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
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.
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
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.
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.
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.
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
TTD does not support active tracing of
Session 0 processes through default usage. Using
GFlags, this limitation can be bypassed because,
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
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
General 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
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:
_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
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
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 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:
Learn useful techniques to identify vulnerable WCF services, discover what to look for when analyzing decomposed .NET assemblies, including those that have been obfuscated, and watch a demonstration of attacks against real software.
Learn More →
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. 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 →