Privilege Escalation Via the Core Shell COM Registrar Object

December 20, 2019 | Simon Zuckerbraun

This 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:

Figure 1

Figure 2

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:

Figure 3

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:

Figure 4

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:

Figure 5

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:

Figure 6

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:

Figure 7

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:

Figure 8

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.