Devolutions / MsRdpEx

Microsoft RDP Client Extensions
MIT License
178 stars 31 forks source link

COM Native library resolution #1

Closed raffaeler closed 2 years ago

raffaeler commented 2 years ago

With regards to the DllImport loading library hooking, if the target framework is .NET Core (>= 3.0) the best solution is NativeLibrary.SetDllImportResolver (https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.nativelibrary.setdllimportresolver?view=net-5.0)

I know you already found a different workaround to make it work on .NET Framework. But it would be better (IMO) to use this API when the runtime is .NET Core.

HTH

awakecoding commented 2 years ago

Even if NativeLibrary.SetDllImportResolver could work, I have some doubts that it would be sufficient: there would be no way to hook the .NET class factory to modify the behavior for all created COM interop classes. I also suspect that this hook would only affect DllImport and not the native library loaded as a result of finding the registered DLL containing a specific CLSID for COM activation.

This being said, I found a .NET design document about COM Activation that covers both hosting COM and loading COM. What we need to figure out is the current internal process for COM activation and see if there's a way to hook it beyond the current registration-free COM mechanism in the application manifest.

I'll keep digging into the .NET runtime sources, the code implementing the shim for registration-free COM is probably the part that will reveal us the most valuable information. We need to do the same, but dynamically, at runtime, without a static manifest. In other words, I need to create those same COM-interop classes with either mstcax.dll or rdclientax.dll multiple times within the same .NET process.

awakecoding commented 2 years ago

I did some digging inside the .NET runtime sources, and found a bunch of related code for both COM hosting and COM loading, but I didn't yet pinpoint the exact place I am looking for:

https://github.com/dotnet/runtime/blob/main/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComActivator.cs https://github.com/dotnet/runtime/blob/main/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComActivationContextInternal.cs https://github.com/dotnet/runtime/blob/main/src/coreclr/interop/interoplib.cpp https://github.com/dotnet/runtime/blob/main/src/coreclr/interop/comwrappers.cpp https://github.com/dotnet/runtime/blob/main/src/coreclr/utilcode/util.cpp https://github.com/dotnet/runtime/blob/main/src/coreclr/hosts/coreshim/ComActivation.cpp https://github.com/dotnet/runtime/blob/main/src/installer/managed/Microsoft.NET.HostModel/ComHost/RegFreeComManifest.cs

awakecoding commented 2 years ago

I think I'm getting closer now... I did a search for "FEATURE_COMINTEROP_UNMANAGED_ACTIVATION" which is an ifdef used throughout the code for the unmanaged COM interop activation (and therefore the one loading unmanaged COM interfaces by searching for a CLSID registration DLL in the registry).

Two source files contain most of the relevant code: https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/interoputil.cpp https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/runtimecallablewrapper.h

The most important function we can use as a starting point is ClassFactoryBase *GetComClassFactory(MethodTable* pClassMT) - it returns a ClassFactoryBase (ComClassFactory) object used to create the unmanaged COM object wrapped within managed code. This is where we'd ideally hook ourselves to use a special class factory that could override the COM registration information and load the implementation from either mstscax.dll / rdclientax.dll at runtime.

raffaeler commented 2 years ago

Good work. I have currently no time to dig in, but it would be far easier with WinDbg and breaking upon loading a dll. The symbols + sources of the entire runtime are available therefore you should have a pretty call stack to dig into, starting from your specific use-case.

Also, the team usually answers to these questions, but it may take a while.

awakecoding commented 2 years ago

There's no pressure to spend time on this, but you can follow the progress and maybe give pointers if you have ideas. For the moment I decided to just make progress on the native DLL hooking instead, as it the only way I'll be able to correctly inject myself into mstsc.exe + msrdc.exe (I need to use Detours for a few things).

I don't know how accurate the stack trace from procmon is, but with Remote Desktop Manager (.NET Framework) we can see CoCreateInstance being called from the managed code, leading up to the LoadLibrary call for my registered DLL. Unless this code path has significantly changed in .NET 6, it's 99% certain that the resulting LoadLibrary call is not hookable from the managed code, since it's called from CoCreateInstance in combase.dll:

image

As you can see from the screenshot, I already have a functional replacement DLL loaded in Remote Desktop Manager, the only function called in this use case is DllGetClassObject. The additional function exports will be called when loaded in mstsc.exe / msrdc.exe when I successfully add Detours hotpatching to hook the native LoadLibrary calls both RDP clients do for a manual COM activation - it turns out they doesn't look in the registry at all for the COM registration.

raffaeler commented 2 years ago

Interesting. I'll take a close look to your progress and let you know :-)

awakecoding commented 2 years ago

This is now supported with https://github.com/awakecoding/MsRdpEx/pull/9