Published on Thu 06 July 2023 by @clavoillotte
Product: Windows 10 1909, Windows Server 2019 (2004 and older versions also affected but not tested)
Type: Local Privilege Escalation
Summary: The Windows Installer accesses the MSI files in C:\Windows\Installer
while impersonating the user (and using the impersonated user's device map), and trusts these files to perform elevated/privileged operations such as registry key creation. This can be abused by an unprivileged user to obtain SYSTEM
privileges.
This vulnerability was patched with Windows September 2020 security updates.
Introduction
A few months ago, an interesting vulnerability in Windows Installer (from Adrian Denkiewicz) reminded Jonas Lykkegård of a vulnerability he discovered in 2020, for the exploitation of which I lent a hand. While a bit old (patched in September 2020), this vulnerability is still interesting, in part because instead of a somewhat common "sensitive resource is user-writable" or "privileged process accesses user resource without impersonation", it lies in the operations msiexec performs while impersonating the user. Its exploitation was also a bit tricky - and fun.
So, better late than never, here is the writeup for CVE-2020-0911.
Note: this article assumes some knowledge of filesystem redirection attacks, most required notions are introduced in this article.
Description
The Windows Installer (msiexec.exe
) creates MSI files for managed packages in C:\Windows\Installer
at install times. These packages are not user-writable, and are somewhat trusted by Installer when maintenance operations need to be performed on the associated product.
However, when an unprivileged user triggers a maintenance operation from an MSI file in C:\Windows\Installer
, the Windows Installer accesses the MSI file while impersonating the user and using the impersonated user's device map.
A detailed explanation of what a device map is can be found in James Forshaw's article on a TrueCrypt/ driver vulnerability (it's also mentioned in several places like his article on symlinks and his research on DefineDosDevice), and another one in this LSA PPL bypass article from itm4n.
The (over)simplified summary is: a device map is a set of symbolic links (in the object manager, not on the filesystem) that map MS-DOS device names (such as C:
or D:
) to the corresponding device object (e.g. \Device\HarddiskVolume1
), so that when a path like C:\Dir\File.ext
is used, the C:
is resolved to the appropriate device. While there is a "global" device map for the system, there is also a per-session device map that allows a user to do things like mapping a network drive to Z:
, that mapping will only exists in the user's session. Turns out, an unprivileged user process can create a C:
object symbolic link in the device directory of its session, thus redirecting subsequent file accesses from other processes in the session - as well as processes impersonating it.
Coming back to the Windows Installer behavior in CVE-2020-0911, we see the privileged msiexec
process accessing the .msi
file while impersonating the unprivileged user:
However, later operations performed by the privileged process (without impersonation) are presumably based on data it has read in the .msi
file, such as the creation/overwrite of registry keys:
So the reading/parsing of the MSI file is performed while impersonating the unprivileged user, but later operations based on data read from the MSI file are performed with full privileges.
This can be abused by an unprivileged user to make the Windows Installer perform arbitrary maintenance operations - such as registry key creation - with SYSTEM
privileges, by changing the user device map and using object directories and object symlinks to redirect the MSI file accesses to an arbitrary file.
Exploitation
Exploiting this bug is not as straightforward as just creating a symlink however, as there are some hurdles to overcome:
- changing the
C:
mapping for the current session has side effects on other processes in that session that can make things misbehave (and ultimately make the session unusable) - same thing with large file redirections in directories that are often accessed such as
C:\
andC:\Windows
: this may break the target process before it reaches the interesting point, as well as other processes (if they are subject to the same device map) - the modified MSI file cannot be too different from the original one (still need to match product ID etc.) or the Installer will just error out and stop
To address the first two limitations, Jonas came up with a clever way of performing the device map redirection:
- create a directory structure in the object manager that replicates the target file structure:
- use object directories and symlinks to point to original files
- use an object symlink to redirect the target file to the desired file
- use shadow object directories so that accesses to other files in the structure transparently fall back to original files/folders
- apply the global device map from
\GLOBAL??
to all current (non-target) processes, so that they are not affected by the redirection - redirect the session's device map to the fake directory structure
- all new processes will have the "fake" device map
- trigger the target process
The redirection of the target MSI file C:\Windows\installer\c63eb.msi
has multiple parts, so it's easier to see step by step. Initially, the DosDevice
object directory for the session only contains a link to the Global??
object directory:
By creating a C:
object link in this directory, accesses (in this session) to C:\whatever
are redirected to a temporary folder on the filesystem:
This temporary folder contains junctions named after all folder in the original C:\
root directory:
Each of these junctions points to the corresponding original folder (using the Global
device map) except one: the one that is part of the path we want to redirect (here Windows
, for C:\Windows
):
This junction points to an object directory in \RPC Control
. This "primary" object directory only contains a single entry: a link corresponding to the next item in the target path (here installer
, for C:\Windows\installer
):
This "primary" object directory has a shadow object directory (called here the "secondary" directory) that is used as a fallback if a resource is not found in the "primary" directory. The "secondary" directory has links for all the items of the original C:\Windows
filesystem folder, each link pointing to the original item on the filesystem:
This combination of object directory and shadow object directory allows redirecting specific file(s) or folder(s) in the primary directory, while redirecting all other entries to their respective original filesystem counterparts in the shadow/secondary directory: when an item is not found in the primary directory, the secondary one will be used. While not strictly necessary (we could just use a single object directory and change the target symlinks inside), it makes adding and changing links a bit easier in some scenarios.
The installer
symlink in the primary directory points to another pair of object directories (primary and secondary), this time reflecting the original C:\Windows\installer
filesystem folder:
In the primary directory of this pair, the c63eb.msi
link points to the malicious file we want the Windows Installer process to load (while thinking it's loading the one from the trusted system directory), here C:\Temp\c63eb.msi
. (Note: inprogressinstallinfo.ipi
was initially used for oplocks during the tests, but ended up not being necessary.)
The following schema sums up the redirection setup:
Before this redirection is put into place, existing processes in the current session are made to use the \Global??
directory as their device map using an undocumented call to NtSetInformationProcess
that allows setting the device map for the process:
bool SetProcessDeviceMap(HANDLE hDir, HANDLE hProc = GetCurrentProcess()) {
PROCESS_DEVICEMAP_INFORMATION DeviceMap = { hDir };
NTSTATUS status = NtSetInformationProcess(hProc,
ProcessDeviceMap,
&DeviceMap,
sizeof(DeviceMap));
return status == 0;
}
This allows others processes to continue operating normally using the global device map (as long as they do not need access to a sessions-specific device) but a new process (in the session or impersonating it) will use the fake directory structure. Also, for this process, accesses to existing files and folders will work somewhat normally, except for the specific target file C:\Windows\installer\c63eb.msi
that gets redirected to C:\Temp\c63eb.msi
.
As for the MSI file, in the PoC we chose to just "binary patch" the existing MSI file and replace a registry key that gets created, to create an Image File Execution Options
registry key for WerFault.exe
, and then trigger a crash to execute the payload as SYSTEM
(WerFault.exe
is run as SYSTEM
and a second time as the user whose process crashed).
The only requirement for this (which is always almost fulfilled) is to have at least one installed product / managed package on the machine. For our PoC we targeted the Minimum Runtime package from the Visual C++ 2019 redistributable x64, as it is very commonly installed. Note however, that the vulnerability is obviously not specific to this package, and the same approach can be used with almost any MSI package.
Because the target registry path is longer than the one being replaced, a registry key symlink is used to keep the registry path short:
$RegSymlink = [NtApiDotNet.NtKey]::CreateSymbolicLink("\Registry\Machine\SOFTWARE\Microsoft\Tracing\a\abcdefghijklmnop", $null, "\REGISTRY\Machine\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options")
This creates an alternative path HKLM:\SOFTWARE\Microsoft\Tracing\a\abcdefghijklmnop\WerFault.exe
to the desired registry path, with the alternative path being the same length than the existing path HKLM:\SOFTWARE\Microsoft\DevDiv\VC\Servicing\14.0\RuntimeMinimum
used in the MSI file. It simplifies the binary patching needed, as simple string match/replace can now be used.
Summing up, the chosen exploitation method is the following:
- Find and copy the existing MSI file
- Create a registry key symlink to the target registry key and value (the
Debugger
property of theImage File Execution Options
registry key forWerFault.exe
) - Patch the binary MSI file to replace the registry key/entry path, name and value
- Change the user device map to redirect
C:
,C:\Windows
andC:\Windows\Installer
through object directories and object symlinks, and change the device map of existing processes so they continue running undisturbed - Run
msiexec
on the MSI file fromC:\Windows\Installer
(but the modified file will be accessed instead because of the redirection) - The elevated
msiexec
will the use the fake MSI file (believing it's the trusted one) and create the desired registry key - Trigger a crash to execute
WerFault.exe
- and its "debugger" a.k.a. the payload - asSYSTEM
Procmon can be used to confirm the process accesses the redirected path:
And creates the desired registry key:
Proof of Concept
The PoC exploits this vulnerability to create the desired registry key, then triggers a crash to execute the debugger associated with WerFault.exe
(C:\x\z.exe
which is a copy of payload.exe
) as SYSTEM
.
The relevant parts of the source code can be found here.
Note: source code provided in this repository is the original one sent to Microsoft; it may need to be adapted, as the C++ code requires Jonas' exploit toolkit which was not public at the time and may have slight differences with the published version.
The PoC targets the VC++ 2019 Minimum Runtime x64 package. As explained above, the vulnerability has nothing to do with this particular package - but this one was chosen for the demonstration because it is very commonly installed.
Here is a video of that PoC in action on an unpatched Windows 10 1909:
Fix
Microsoft has released a patch and an advisory. Users should apply updates through usual channels.
References
https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2020-0911
Timeline
2020-05-29: Initial report sent to vendor
2020-05-29: Vendor acknowledges reception of report
2020-10-09: Vendor publishes fix and advisory
2023-07-06: Publication of this article