Sunday 18 November 2018

Finding Windows RPC Client Implementations Through Brute Force

Recently, @SandboxEscaper wrote a detailed blog post (link seems she's locked down the blog, here's a link to an archive) about reverse engineering local RPC servers for the purposes of discovering sandbox escapes and privilege escalation vulnerabilities. After reading I thought I should put together a sort-of companion piece on RPC client implementation for PoC writing, specifically not implementing one unless you really need to.

If you go and read the blog post it goes through finding an RPC service to investigate using RpcView, then using the tool to decompile the RPC interface to an IDL file which can be added to a C++ project. This has a few problems when you're dealing with an unknown RPC interface:

  • Even if the decompiler was perfect (and RpcView or my own in my NtObjectManager PowerShell module are definitely not) the original IDL to NDR compilation process is lossy. Reversing this process with a decompiler doesn't always produce a 100% correct IDL file and thus the regenerated NDR might not be 100% compatible.
  • The NDR engine is terrible at giving useful diagnostic information for why the IDL is incorrect, usually just returning error code 1783 "The stub received bad data". This is made even more painful when dealing with complex structures or unions which must be exactly correct otherwise it all goes to hell.
  • It's hard to use the IDL from any language but C/C++, as that's really the only supported output format for RPC interfaces.
While all three of these problems are annoying when trying to produce a working PoC, the last one annoys me especially. I have a thing about writing my PoCs in C#, about the only exception to using C# is when I need to interact with an RPC server. There's plenty of ways around this, for example I could build the client into a native DLL and export methods to call from C#, but this feel unsatisfactory. 

At least in some cases, Microsoft have already done most of the work for me. If there's a native RPC server on a default installation of Windows there must be some sort of client component. In some cases this client might be embedded completely inside a binary and not directly callable, COM is a good example. However in other cases the developers also provide a general purpose library to interact with the server. If you can find the client library, it'll bring a number of advantages:
  • If it's a truly general purpose the library will export methods which can be easily interacted with from C# using P/Invoke (or any other language which can invoke native exports).
  • The majority of these libraries will deal with setting up the RPC client connection, dealing with asynchronous calls and custom serialization requirements.
  • The NDR client code is going to be 100% compatible with the server, which should eliminate error code 1783 as well as dealing with changes to parameters, method layout and interface IDs which can happen between major versions of the OS. 
  • You only have to deal with calling a C style method (or sometimes a COM interface, but that's still a C calling convention) which gives a bit more flexibility which it comes to getting structure definitions correct.
  • As it's a library there's a chance that useful type information might be disclosed in the client code, or it will allow to to track down callers of these APIs in other binaries that you can RE to get a better idea of how to call the methods correctly.
There's sadly some disadvantages to this approach:
  • Not all clients will actually be in a general purpose library with easy entry points, or at least the entry points don't cleanly map to the underlying RPC methods. That's not to say it's useless as you could load the DLL then use a relative pointer to the RPC client structures and manually reconstruct the call but that removes many of the advantages.
  • The library might be general purpose but the developers added a significant amount of client side parameter verification or don't expose some parameters at all. Some bugs are only going to present themselves by calling the RPC method with parameters the developers didn't expect to receive, perhaps because they verify in the client.
To prevent this blog post getting even longer let's look how I could identify the client library for the Data Sharing Service which SandboxEscaper dropped a bug in that was recently fixed as CVE-2018-8584. The bug SandboxEscaper discovered was in the method PolicyChecker::CheckFilePermission implemented in dssvc.dll. By calling one of the RPC methods, such as RpcDSSMoveFromSharedFile an arbitrary file can be deleted by the SYSTEM user. Looking at dssvc.dll it doesn't contain any client code, so we have to go hunting for the client. For this we'll use my NtObjectManager PowerShell module as it contains code to do just this. Any lines which start with PS> are to be executed in PowerShell.

Step 1: Install the NtObjectManager module from the PowerShell gallery.

PS> Install-Module NtObjectManager -Scope CurrentUser
PS> Import-Module NtObjectManager

You might need to also disable the script execution policy for this to work successfully.

Step 2: Parse RPC interfaces in all system DLLs using Get-RpcServer cmdlet.

PS> $rpc = ls c:\windows\system32\*.dll | Get-RpcServer -ParseClients

This call passes the list of all DLLs in system32 to the Get-RpcServer command and specifies that it should also parse all clients. This command does a heuristic search in a DLL's data sections for RPC servers and clients and parses the NDR structures. You can use this to generate RPC server definitions similar to RpcView (but in my own weird C# pseudo-code syntax) but for this scenario we only care about the clients. My code does have some advantages, for example the parsed NDR data is stored as a .NET object so you can do better analysis of the interface, but that's something for another day.

Step 3: Filter out the client based on IID and Client status.

PS> $rpc | ? {$_.Client -and $_.InterfaceId -eq 'bf4dc912-e52f-4904-8ebe-9317c1bdd497'} | Select FilePath

The server's IID is bf4dc912-e52f-4904-8ebe-9317c1bdd497 which you can easily get from the IDL server definition in the uuid attribute. We also need to filter only client implementations using the Client property. 

If you've followed these procedures you'll find that the client implementation is in the DLL dsclient.dll. Admittedly we might have been able to guess this based on the similarity of names, but it's not always so simple. 

Step 4: Disassemble/RE the library to find out how to call the methods.

It doesn't mean the DLL contains a general purpose library, we'll still need to open it in a disassembler and take a look. In this case we're lucky, if we look at the exports for the dsclient.dll library we find the names match up with the server. For example there's a DSMoveFromSharedFile which would presumably match up with RpcDSSMoveFromSharedFile.

Decompilation of DSMoveFromSharedFile

If you follow this code you'll find it's just a simple wrapper around a call to the method DSCMoveFromSharedFile which binds to the RPC endpoint and calls the server. There's no parameter verification taking place so we can just determine how we can call this method from C# using the server IDL we generated earlier. 

And that's it, I was able to implement a PoC for CVE-2018-8584 by defining the following C# P/Invoke method:

[DllImport("dsclient.dll", CharSet = CharSet.Unicode)]
public static extern int DSMoveFromSharedFile(string token, string source_file);

Of course your mileage may vary depending on your RPC server. But what I've described here is a quick and easy way to determine if there's a quick and easy way to avoid writing C++ code :-)