Exploit-DB #42021
Description: Microsoft Windows - Running Object Table Register ROTFLAGS_ALLOWANYCLIENT Privilege Escalation
Type: dos
Platform: windows
Date: 2017-05-17
Author: Google Security Research

Exploit:
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1112

Windows: Running Object Table Register ROTFLAGS_ALLOWANYCLIENT EoP
Platform: Windows 10 10586/14393 not tested 8.1 Update 2 or Windows 7
Class: Elevation of Privilege

Summary:
By setting an appropriate AppID it’s possible for a normal user process to set a global ROT entry. This can be abused to elevate privileges.

Description:

NOTE: I’m not sure which part of this chain to really report. As far as I can tell it’s pretty much all by design and fixing the initial vector seems difficult. Perhaps this is only a bug which can be fixed to prevent sandbox escapes?

When registering an object in the ROT the default is to only expose that registration to the same user identity on the same desktop/window station. This includes preventing the same user at different ILs (such as between sandbox and normal user) from seeing the same registration. However it could be imagined that you might want to register an entry for all users/contexts so IRunningObjectTable::Register takes a grfFlags parameter with the value  ROTFLAGS_ALLOWANYCLIENT which allows the ROT entry to be exposed to all users. 

The description of this flag indicates it can only be used if the COM process is a Local Service or a RunAs application. In fact there’s an explicit ROTFlags value for the AppID which would grant the privilege to a normal application. Quick testing proves this to be correct, a “normal” application cannot expose the ROT entry to any client as RPCSS does a check that the calling process is allowed to expose the entry. However there are two clear problems with the check. Creating a RunAs COM object in the current session would typically run at the same privilege level as the caller, therefore an application which wanted to abuse this feature could inject code into that process. Secondly while it’s not possible to register a per-user COM object which specifies a RunAs AppID it’s possible to explicitly set the AppID when calling CoInitializeSecurity (either via the GUID or by naming your program to match one which maps to the correct AppID).

Therefore in the current implementation effectively any process, including sandboxed ones should be able to register a global ROT entry. What can we do with this? The ROT is mainly used for OLE duties, for example Word and Visual Studio register entries for each document/project open. It would be nice not to rely on this, so instead I’ll abuse another OLE component, which we’ve seen before, the fact that LoadTypeLib will fall back to a moniker if it can’t find the type library file specified.

If the file loading fails then LoadTypeLib will effectively call MkParseDisplayName on the passed in string. One of the things MPDN does is try and create a file moniker with the string passed in as an argument. File Monikers have an interesting feature, the COM libraries will check if there’s a registered ROT entry for this file moniker already present, if it is instead of creating a new object it will call IRunningObjectTable::GetObject instead when binding. So as we can register a ROT entry for any user in any context we can provide our own implementation of ITypeLib running inside our process, by registering it against the path to the type library any other process which tries to open that library would instead get our spoofed one, assuming we can force the file open to fail.

This is the next key part, looking at the LoadTypeLib implementation the code calls FindTypeLib if this function fails the code will fall back to the moniker route. There’s two opportunities here, firstly CreateFile is called on the path, we could cause this to fail by opening the file with no sharing mode, in theory it should fail. However in practice it doesn’t most type libraries are in system location, if you don’t have the possibility of write permission on the file the OS automatically applies FILE_SHARE_READ which makes it impossible to lock the file in its entirety. Also some TLBs are stored inside a DLL which is then used so this route is out. Instead the other route is more promising, VerifyIsExeOrTlb is called once the file is open to check the type of file to parse. This function tries to load the first 64 bytes and checks for magic signatures. We can cause the read to fail by using the LockFile API to put an exclusive lock on that part of the file. This also has the advantage that it doesn’t affect file mappings so will also work with loaded DLLs. 

We now can cause any user of a type library to get redirected to our “fake” one without abusing impersonation/symbolic link tricks. How can we use this to our advantage? The final trick is to abuse again the auto-generation of Stubs/Proxies from automation compatible interfaces. If we can get a more privileged process to use our type library when creating a COM stub we can cause a number of memory safety issues such as type confusion, arbitrary memory read/writes and extending the vtable to call arbitrary functions. This is an extremely powerful primitive, as long as you can find a more privileged process which uses a dual automation interface. For example the FlashBroker which is installed on every Win8+ machine is intentionally allowed to be created by sandboxed IE/Edge and uses dual interfaces with auto-generated Stubs. We could abuse for example the BrokerPrefSetExceptionDialogSize and BrokerPrefGetExceptionDialogSize to do arbitrary memory writes. This all works because the stub creation has no was of ensuring that the actual server implementation matches the generated stub (at least without full symbols) so it will blindly marshal pointers or call outside of the object's vtable.

Proof of Concept:

I’ve provided a PoC as a C# project. You need to compile it first. It fakes out the Windows Search Service’s type library to modify the IGatherManagerAdmin2::GetBackoffReason method so that instead of marshaling a pointer to an integer for returning the c