I hate you COM – Pitfalls of COM object activation

Background

This is not a Windows COM 101, readers are expected to have a decent understanding of COM and CLR hosting internals

Dotnet unamanged-api is not a stranger to Offssec tool developers, it enables developers to tinker with managed processes and the CLR itself from within a native process(say c/c++). You can see the whole list of supported interfaces listed categorically here. Frequently I go through each category in the list, in the hope of finding any interesting methods exposed by COM interfaces which can be abused, especially those Hosting and debugging interfaces.

long story short, this post is about struggle i faced to solve a specific problem, it turned out to be a rabbit hole. I spend hours and hours googling and reading very old COM headers just to hit another brick wall. I hope this short post helps someone struggling to make COM work 🙂

In the middle of cooking up a POC to do some shady dotnet stuff which involved AppDomains and arbitrary assembly loading, I use ICorPublish interface, which is a debug related COM interface for dotnet, to fetch AppDomain related information from managed processes. when working with iCorPublish you will get to see following interesting interfaces often – ICorPublishProcess, ICorPublishAppDomain . The ICorPublishAppDomain has two methods GetID and GetName that fetch AppDomain ID and name of each AppDomain present in a managed process. The need for finding AppDomain related information arise when building tools, so this is one way of doing it. SO OKAY WHAT IS THE ISSUE HERE? The issue is that you cannot fully trust apis like CreateInstance, CoCreateInstance and CoCreateInstanceEx. If the instance (COM object) returned by these apis is not compatible with CLR loaded in a target dotnet application, then behavior cannot be predicted, oftentimes it leads to failure when invoking methods through such instances. What is the reason behind this? They tend to load wrong version of mscordbi.dll from Microsoft.NET directory.

This post is about how to fix such issue.. When I was playing around with ICorPublish interface for first time, I had to clue about this issue, so I am going to use this interface as a medium to address the issue, If this is your first time hearing it, I hope you find this post useful.

The problem

It is trivial to call CoCreateInstance (or CreateInstance) to instantiate COM class, as shown in the image below, ICorPublish is instantiated and made available to user through the variable pub. This is what we do normally right? RIGHT.. RIIGGHHHHT?.

Managed process enumeration

The ICorPublish interface exposes a method called EnumProcess, this can be used to enumerate managed processes running on the system. It returns an instance of ICorPublishProcessEnum, consider this as a list of information regarding each managed process enumerated where each process is represented by ICorPublishProcess interface. This is accessible to user through the variable pEnum.

Lets see how above code is going to behave at runtime! As shown in the image below, check the value of the variable pEnum – its all NULL. This is a clear indicator something went wrong behind the scene especially when the return value hr has S_OK status which in COM’s terms all good. This is weird!

Lets test another method of ICorPublish interface – GetProcess.

Information about a specific process

The GetProcess method exposed by ICorPublish interface is similar to teh EnumProcess method seen earlier but difference is that it is used to fetch the information of a particular managed process via its PID. The variable proc stores the process information.

At runtime above code generates following error shown below. It says one or more arguments are invalid, also check value of variable proc which is null. The arguments fed into api are all valid then what does this error tell you? Sometimes errors like this will question your sanity.. LOL

The Fix

My stupid ass spent almost a day on above issues, after hours of googling I accidently landed on a random ass a Processhacker plugin source. Absolutely a blessing to find below comment!! Talk about the luck, the plugin uses ICorPublish interface. LOL

As I mentioned in the beginning, if COM object is incompatible with CLR version, COM method fails or worse behave indifferently leaving no signs of any issues, which is the case with EnumProcess method as seen above.

In the processHacker code there a mention of IClassFactory_CreateInstance, note that it is written in C, when I used the same in C++ code, it gave me an error (error – not defined), I thought its a missing header sighhhhh…..after a bit of digging, I got the header where it is defined – Unknwn.h

Adding of the header in my project did not solve the issue, so I dived into header file and saw below code. The IClassFactory_CreateInstance is a macro made for C not C++, the COBJMACROS definition confirms it. Nevertheless seeing the definition of IClassFactory_CreateInstance macro below, it is very easy to reproduce the C++ code.


The solution is :

  • Load correct mscordbi.dll
  • Use DllGetClassObjectInternal Win32 api to interface pointer to target class, in our case ICorPublish COM class.
  • Call CreateInstance on above pointer to properly instantiate the COM class.

i have written C++ code shown below to manually use correct dotnet core library to activate a COM class, the variable Publish has CLR v4 compatible ICorPublish instance.

Now we can use this object (Publish) to invoke interface methods discussed earlier EnumProcess and GetProcess.

Now when executing the new code, pEnum returned valid data after the call to EnumPrcocess interface method. Earlier this variable returned null values.

Same is the case with method GetProcess interface method, the variable proc now returns valid data and hr is S_OK after call to the method.

Just for the sake of it, below code prints the AppDomain friendly name and associated ID.

The domain CUJaNPduJedJEiCCglSw is created by CreateDomain method exposed by ICorRuntimeHost interface.

Leave a Reply

Your email address will not be published. Required fields are marked *