CVE-2021-34462 is a logic-error vulnerability in the Windows AppXSvc service, a service generally used for providing an infrastructure support for deployment of store applications in Windows. This vulnerability was used to win the Windows EoP category in Pwn2Own 2021 by Tao Yan . The technical details was subsequently presented at BlackHat Europe 2021  and is a very good read. So, this blog post serves as a documentation for our analysis of the vulnerability and exploitation development effort for CVE-2021-34462.
First, we discuss the root-cause analysis of this vulnerability. Then we describe how it can be reliably exploited to escalate user privileges and obtain NT AUTHORITY\SYSTEM.
This exploit was developed on Windows x64 1909 (18363.418), although systems until Windows 20H2 (19042.906) are vulnerable.
The root cause of the vulnerability lies in how certain folders are handled when we reset the Solitaire Collection application. When we reset Solitaire, a few folders are created in the directory %USERPROFILE%\AppData\Local\Publishers\8wekyb3d8bbwe\. This directory is accessible for a low privileged user and any subdirectories created in this directory as well.
The folders on which we will focus on are SettingsContainer and mcg. Both these folders are deleted and recreated again in the 8wekyb3d8bbwe directory when Solitaire is reset. But during the recreation of these folders, something unusual happens; the AppXSvc service, which is running as SYSTEM, sets certain permissions on the directories which allows us to modify the Discretionary Access Control List (DACL) while impersonating as the NT AUTHORITY\SYSTEM user. In other words, although the folders are created by NT AUTHORITY\SYSTEM, low privileged users can access and have the permissions to modify the Access Control List (ACL) of that folder.
Basically, the application reset is implemented by deleting the application directory and then reinstalling it from the store. The two images below shows that the deletion and recreation of %USERPROFILE%\AppData\Local\Publishers\ directory and subdirectories during reset.
If we dig a little deeper into AppXDeploymentExtensions.onecore.dll, we can find the functions responsible for the deletion, creation and setting permissions on these folders. The function
StateCreation::CreateStateLocations() is responsible for creating the directories after they were being deleted. It is implemented by crafting the %USERPROFILE%\AppData\Local\Publisher\ path and creating it. Next, the StateCreation::CreatePublisherSubFolder() function creates the 8wekyb3d8bbwe, SettingsContainer, mcg and other subdirectories. The callee
CreateDirectoryW() will correspond to the
CreateDirectory() call in the image above.
Common::Deployment::AccessHelpers::AddPackageDataAccessHelper() and its callee
Common::ImpersonateSelf::Impersonate() are called to get the access token of current thread and then impersonates itself which is as SYSTEM.
DirectoryACLs::ApplySecurityDescriptor() is called to adjust the privileges of current thread token object. Then it calls
GetNamedSecurityInfoW() (below image) to get the security descriptor information of the SettingsContainer directory and add some Access Control Entries (ACE) into its ACL. Finally,
SetNamedSecurityInfoW() is called to set the security descriptor information of the folder as SYSTEM.
Upon application reset, the application package (including the SettingsContainer folder) is deleted and recreated during reinstallation. The main exploitation methodology for such cases is to create path redirections between the execution from folder deletion to the setting of its security information (ie:
SetNamedSecurityInfo()). This is usually achieved by creating a thread that can hijack the application installation thread execution, create the path redirections, and finally return execution back to thread to continue installation (but on the redirected path instead). In this case, we create three different path redirections for exploitation.
The first redirection happens between SettingsContainer\mcg folder deletion and its recreation (ie: CreateDirectoryW()), when we will redirect the SettingsContainer\mcg folder to any location accessible from local (low) user by creating a mountpoint to it.
%USERPROFILE%\AppData\Publishers\8wekyb3d8bbwe\ ⟶ C:\XYZ\
The second redirection happens between SettingsContainer\mcg folder recreation (ie:
CreateDirectoryW()) and setting of its permissions (ie:
GetNamedSecurityInfo()). There are a lot of code operations between them and this is advantageous to us as the possibility for CPU to switch from the installation thread to our own thread increases. In our thread, we can then redirect the SettingsContainer folder to \RPC CONTROL\ directory by creating a mountpoint to it. Then we will redirect this directory to a dummy file, dummyfile, by creating a symbolic link between them.
%USERPROFILE%\AppData\Publishers\8wekyb3d8bbwe\ ⟶ \RPC CONTROL\
\RPC CONTROL\SettingsContainer ⟶ \??\C:\XYZ\dummyfile
Prior to all these redirections taking place, we set an oplock on dummyfile. This is because when oplock is triggered in
CreateFile((…, FILE_READ_ATTRIBUTES, …), the execution is paused and this will buy us more time to create our third redirection.
The third redirection happens between
GetNamedSecurityInfoW() and its callee
CreateFile(…, FILE_READ_ATTRIBUTES, …). As we have already set the FSCTL_REQUEST_OPLOCK_LEVEL_1 oplock in step 2, when dummyfile is now opened with
FILE_READ_ATTRIBUTES access, a callback will be triggered to halt execution and force a thread context switch. At this point, we will "race" to delete the previous mountpoint-symlink pair and (re)create a new pair. Except that this time, the mountpoint will be symlink-ed to our target file PrintConfig.dll instead before installation thread execution continues to
\RPC CONTROL\SettingsContainer ⟶ \??\C:\XYZ\dummyfile
\RPC CONTROL\SettingsContainer ⟶ \??\C:\Windows\Sytem32\PrintConfig.dll
At this point, as we now have gained write privileges to C:\Windows\Sytem32\PrintConfig.dll, we can modify its ACL and overwrite it with our payload to start an interactive shell from the current user session. Subsequently, upon starting a new print job, the spoolsv service will load our fake C:\Windows\Sytem32\PrintConfig.dll to spawn a command shell in the context of spoolsv service; NT AUTHORITY\SYSTEM. It should be noted that, obviously, the target file is not limited to PrintConfig.dll only.
Stability is very important in this exploit as in step 3, we are racing threads to recreate a new symlink to target file. To manage every failed attempt during the race, we clean up any opened handles, mountpoints and symlinks to all objects and also delete any new folders created during this failed attempt.
Tao Yan (@ga1ois), Pwn2Own 2021 - Schedule and Live Results
Tao Yan (@ga1ois) and Bo Qu, From Logic to Memory: Winning the Solitaire in Reparse Points