Privilege Escalation Via the Core Shell COM Registrar Object
December 20, 2019 | Simon ZuckerbraunThis final post in our series on interesting vulnerabilities from 2019 highlights an elegant local escalation of privilege (LPE) bug affecting Windows 10. It was submitted to us by an anonymous researcher and has the identifier CVE-2019-1184. Exploiting this vulnerability allows a sandboxed process running at low integrity to execute arbitrary code at medium integrity.
The COM Object and its Launch Permission
This vulnerability centers around a COM class named CoreShellCOMServerRegistrar
. An entry for this object appears in the Registry under HKCR\CLSID
, specifying an in-process server of %SystemRoot%\system32\CoreShellExtFramework.dll
. However, the Registry also associates this class with a DCOM AppID:
In practice, when a user logs on interactively, Windows launches a process named sihost.exe
, which is short for “Shell Infrastructure Host”. A significant portion of the code involved with displaying the Windows graphical shell environment has been refactored from explorer.exe
into the sihost.exe
process. Upon startup, sihost.exe
makes calls to CoRegisterClassObject
to register itself as a local server for several COM classes, including the CoreShellCOMServerRegistrar
class implemented in CoreShellExtFramework.dll
:
As an aside, you might notice that CoreShellCOMServerRegistrar
is being used to register itself.
Henceforth, any attempt to activate CoreShellComServerRegistrar
as a local (meaning out-of-process) server will bind to an instance hosted within the sihost.exe
process.
Now, consider the fact that sihost.exe
runs as the logged-in user at medium integrity. What happens if a low-integrity process attempts to activate an instance of this class? According to Microsoft documentation, a process running at a lower integrity level is blocked by default from activating a COM object served by a process running at a higher integrity level. This is a mandatory access control (MAC) policy known as “No Execute Up.” If a COM object wishes to override this default restriction, it can be done applying an integrity level label in the DCOM LaunchPermission
security descriptor. Figure 2 above shows a LaunchPermission
security descriptor in the registry for our AppID. Unfortunately, it’s not human-readable.
The DCOMCNFG
tool, also known as “Component Services,” can be used to view and administer permissions and other flags under the AppID
key. Due to a design shortcoming, the GUI does not show the presence or absence of integrity labels, nor can it be used to modify such labels. If we open Launch and Activation Permission for our class in Component Services, all we can see is this:
As far as the DACL goes, we see that access is granted to any INTERACTIVE user, as well as SYSTEM and two particular capability SIDs. The user interface gives us no way to see any label, though. We will have to do this programmatically. Reading the binary data from the LaunchPermission
registry value and feeding it to the API ConvertSecurityDescriptorToStringSecurityDescriptor
yields the following:
The final snatch, S:(ML;;NX;;;LW)
, is the SACL. It contains is a mandatory label (ML
) referring to the “No Execute Up” policy (NX
), granting access to low-integrity callers (LW
). Hence, this security descriptor allows low-integrity callers to activate CoreShellCOMServerRegistrar
. This is quite an interesting find and immediately raises the question of how much mischief a low-integrity (i.e., sandboxed) process can cause by invoking this object.
Side note: Dumping a Security Descriptor While Debugging
Before proceeding further, I’d like to share an item from my notes. While analyzing this case, it happened that I was debugging the RpcSs
(Remote Procedure Call) service, which is where the aforementioned security check takes place. And, by the way, if you’d ever like to debug the RpcSs
service yourself, the technique I recommend is to use dbgsrv.exe
and then connect from a remote WinDBG using File | Connect to Remote Stub.
At one point during debugging, I had in front of me a pointer to what seemed to be an in-memory copy of the LaunchPermission
security descriptor, and I wanted to get a dump to make sure it contained exactly what I thought it contained. To achieve that, I wrote a WinDBG one-liner that calls ConvertSecurityDescriptorToStringSecurityDescriptor
on the fly, then restores registers for smooth continuation of the target process:
This one-liner assumes that the pointer to the security descriptor is found in @r8
. It also assumes that a destination buffer will be available at 40000000, so before running it, you should allocate such a buffer using .dvalloc /b 40000000 1000
. Note that this one-liner is intended to be run at the start of a function. Otherwise it would be necessary to save and restore some additional registers as well.
In general, I highly recommend practicing this technique and keeping it at your fingertips. The ability to make ad-hoc native calls from within a debugging session can be enormously useful.
Exploiting CoreShellCOMServerRegistrar
Returning to the main exposition, we are now ready to explore the security impact of a low-integrity process accessing CoreShellCOMServerRegistrar
. A disassembly of CoreShellExtFramework.dll
helps greatly, especially since symbols are available. IDA shows us the methods that the object exposes:
One would expect CoreShellCOMServerRegistrar
to expose methods related to registering COM servers. However, in addition to methods RegisterCOMServer
and UnregisterCOMServer
, we can see a number of other interesting methods available here. In particular, there are the methods OpenProcess
and DuplicateHandle
. Disassembling these methods verifies that they do exactly what their name implies: they expose the functionality of the Windows OpenProcess
and DuplicateHandle
APIs to the caller. Since the caller can be a process running at low integrity, and the server runs at medium integrity, this provides an easy pathway to privilege escalation from low to medium.
The submitter provided a full exploit that works by calling OpenProcess
. The OpenProcess
method opens a Windows process specified by PID and duplicates a handle into the process of the caller’s choice. The caller should choose its own process as the destination, so it will be able to use the handle. The exploit works by brute forcing the PID of sihost.exe
itself, which is, after all, a process running at medium integrity. Once it has a handle to this medium integrity process, it uses the handle to inject shellcode, thereby achieving code execution at medium integrity.
Alternatively, it’s probably possible to write an exploit using the DuplicateHandle
method. In that case, the attacker would need to brute force a handle value, which is still very feasible.
The Patch
Microsoft fixed this in the August 2019 patch cycle. Surprisingly, they did not do it by tightening the security descriptor. Instead, they actually loosened the security descriptor by replacing the specific capability SIDs with ALL APPLICATION PACKAGES (and retaining the Low label), and, to compensate, they added code to the object factory for this COM class, performing manual checks for one of the required capabilities:
In CoreShellExtFramework!CoreShellComServerRegistrarFactory::HasRequiredPermissions
:
As an aside, by debugging into this code, I was able to resolve the numeric SIDs in the original ACL into their names. The two SIDs shown in Figure 4 are shellExperienceComposer
and coreShell
respectively. As you can see, in the August 2019 patch, they permitted a third capability as well, shellExperience
.
Just two months later, in the October 2019 patch, Microsoft reversed course. They backed out all code changes to CoreShellExtFramework.dll
made in the August patch, and instead just tightened the LaunchPermission
security descriptor, changing the Low label to Medium. I’m not aware of the reason they didn’t take that approach in the first place, but I can speculate that it may have to do with other changes that were being made to the integrity levels of some processes involved with rendering the graphical shell.
Thanks for joining us as we recapped some of the best bugs submitted to the ZDI program this year. You can find me on Twitter at @HexKitchen, and follow the team for the latest in exploit techniques and security patches.