Wednesday 21 May 2014

Impersonation and MS14-027

The recent MS14-027 patch intrigued me, a local EoP using ShellExecute. It seems it also intrigued others so I pointed out how it probably worked on Twitter but I hadn't confirmed it. This post is just a quick write up of what the patch does and doesn't fix. It turned out to be more complex than it first seemed and I'm not even sure it's correctly patched. Anyway, first a few caveats, I am fairly confident that what I'm presenting here is already known to some anyway. Also I'm not providing direct exploitation details, you'd need to find the actual mechanism to get the EoP working (at least to LocalSystem).

I theorized that the issue was due to mishandling of the registry when querying for file associations. Specifically the handling of HKEY_CLASSES_ROOT (HKCR) registry hive when under an impersonation token. When the ShellExecute function is passed a file to execute it first looks up the extension in the HKCR key. For example if you try to open a text file, it will try and open HKCR\.txt. If you know anything about the registry and how COM registration works you might know that HKCR isn't a real registry hive at all. Instead it's a merging of the keys HKEY_CURRENT_USER\Software\Classes and HKEY_LOCAL_MACHINE\Software\Classes. In most scenarios HKCU is taken to override HKLM registration as we can see in the following screenshot from Process Monitor (note PM records all access to HKLM classes as HKCR confusing the issue somewhat). 





When ShellExecute has read this registry key it tries to read a few values out it, most importantly the default value. The value of the default value represents a ProgID which determines how the shell handles the file extension. So for example the .txt extension is mapped to the ProgID 'txtfile'.

The ProgID just points ShellExecute to go reading HKCR/txtfile and we finally find what we're looking for, the shell verb registrations. The ShellExecute function also takes a verb parameter, this is the Action to perform on the file, so it could be print or edit but by far the most common one is open. There are many possible things to do here but one common action is to run another process passing the file path as an argument. As you can see below a text file defaults to being passed to NOTEPAD.

Now the crucial thing to understand here is that HKCU can be written to by a normal, unprivileged user. But HKCU is again a fake hive and is in fact just the key HKEY_USERS\SID where SID is replaced with the string SID of the current user (see pretty obvious, I guess). And even this isn't strictly 100% true when it comes to HKCR but it's close enough. Anyway, so what you might be asking? Well this is wonderful until user Impersonation gets involved. If a system or administrator process impersonates another user it's also suddenly finds when it accesses HKCU it really accesses the impersonated user's keys instead of its own. Perhaps this could lead to a system service that is  impersonating a user and then calls ShellExecute to start up the wrong handler for a file type leading to arbitrary execution at a higher privilege. 

With all this in mind lets take a look at patch in a bit more depth. The first step it to diff the patched binary with the original, sometimes easier said than done. I ran an unpatched and patched copies of shell32.dll through Patchdiff2 in IDA Pro which lead to a few interesting changes. In the function CAssocProgidElement::_InitFileAssociation a call was added to a new function CAssocProgidElement::SetPerMachineRootIfNeeded.


Digging into that revealed what the function was doing. If the current thread was impersonating another user and the current Session ID is 0 and the file extension being looked up is one a set of specific types the lookup is switched from HKCR to only HKLM. This seemed to confirm by suspicions that the patch targeted local system elevation (the only user on session 0 is likely to be LocalSystem or one of the service accounts) and it was related to impersonation.




Looking at the list of extension they all seemed to be executables (so .exe, .cmd etc.) so I knocked up a quick example program to test this out.


Running this program from a system account (using good old psexec -s) passing it the path to an executable file and the process ID of one of my user processes (say explorer.exe) I could see in process monitor it's reading the corresponding HKCU registry settings.








Okay so a last bit of exploitation is necessary I guess :) If you now register your own handler for the executable ProgID (in this case cmdfile), then no matter what the process executes it will instead run code of the attacker's choosing at what ever privilege the caller has. This is because Impersonation doesn't automatically cross to new processes, you need to call something special like CreateProcessFromUser to do that.


So how's it being exploited in the real world? I can't say for certain without knowing the original attack vector (and I don't really have the time to go wading through all the system services looking for the bad guy, assuming it's even Microsoft's code and not a third party service). Presumably there's something which calls ShellExecute on an executable file type (which you don't control the path to) during impersonating another user.

Still is it fixed? One thing I'm fairly clear on is there seems to still be a few potential attack vectors. This doesn't seem to do anything against an elevated admin user's processes being subverted. If you register a file extension as the unprivileged user it will get used by an admin process for the same user. This is ultimately by design, otherwise you would get inconsistent behaviour in elevated processes. The fix is only enabled if the current thread is impersonating and it's in session 0 (i.e. system services), and it's only enabled for certain executable file types.

This last requirement might seem odd, surely this applies to any file type? Well it does in a sense, however the way ShellExecute works is if the handling of the file type might block it runs the processing asynchronously in a new thread. Just like processes, threads don't inherit impersonation levels so the issue goes away. Turns out about the only thing it treats synchronously are executables. Well unless anyone instead uses things like FindExecutable or AssocQueryString but I digress ;-) And in my investigation I found some other stuff which perhaps I should send MS's way, let's hope I'm not too lazy to do so.