Saturday, December 11, 2021

Abusing COM, this time in C#

Some time ago, I've written up a technique for setting up object oriented, call based interprocess communication using an idiosyncratic subset of COM. A case for something similar came up for me again, but the project is in C# with .NET 6. So I've adapted the same technique to work in a managed world.

The ideas are the same - the client starts the server process with a cookie in the command line, the server lists its COM object in the Running Object Table (ROT) using a name that contains the cookie, the interfaces involved should marshal out of the box, the client pulls the server's object from the ROT using the cookie.

Anyway, here's a C# gist. Like all things managed, there's considerably less typing than the C++ equivalent. Originally compiled against .NET 6, it recompiles and works under .NET Framework 4.7.2 too.

The only point worth mentioning is that the interface is decorated with the GUID of IDispatch. Were it a dispinterface with a real IID, it won't be marshalable without registration, and the bit where the object reference from the ROT is being converted to IProtocol would fail (the C# as operator would return null). Internally, what happens at that point, the client calls QueryInterface() on the proxy object, the proxy forwards it to the server, the server dutifully returns an interface pointer that points at IDispatch. COM would like to pass it back to the client, but COM sees what IID was QueryInterface() called with, and it was not a known IID. COM has no way of knowing it's a dispinterface and not a custom interface, doesn't know how to marshal it over the process boundary, and at the end of it, QueryInterface() on the client fails.

Another workaround for that would involve registering the custom IID under HKEY_CLASSES_ROOT\Interfaces, and providing the GUID for PSDispatch ({00020420-0000-0000-C000-000000000046}) as the value of ProxyStubClsid32. But I decided to go with zero machine footprint instead.

The Protocol.cs file needs to be present in both the client and the server, as it contains things the client and the server should agree on. The RunningObjectTable.cs file needs to be in both, too; it's a helper class for ROT access.

No comments:

Post a Comment