https://github.com/thiagomayllart/DarkMelkor

Loading Managed Code into Unmanaged Processes | Accenture

Why sacrificial appdomain

The normal method (melkor)

AppDomain oDomain = AppDomain.CreateDomain("bruh", null, null, null, false);
ShadowRunnerProxy pluginProxy = (ShadowRunnerProxy)oDomain.CreateInstanceAndUnwrap(typeof(ShadowRunnerProxy).Assembly.FullName, typeof(ShadowRunnerProxy).FullName);
pluginProxy.LoadAssembly(bMod, sMethod); // This also executes

The flaw

To instantiate the proxy object, the proxy object’s class must be known to the other domain. This requires that the class’s containing assembly be loaded into that domain; it must be resolved by name.

The CLR has a hierarchy of trust for assemblies and primarily expects them to come from files. Assemblies can be loaded from byte arrays, but these are loaded as “no context” assemblies and cannot easily be used across AppDomain boundaries without extra effort. Since our parent assembly has itself been loaded from memory, it cannot be resolved by name.

Basically, normal melkor works if the parent assembly (loader/beacon) is on disk; assuming we are in memory or injected, however, means we have no file to back our parent assembly. This causes the instantiation of the proxy object to explode.

Normally, with an on-disk loader/beacon, the proxy object can be resolved through its parent assembly which is backed by the file on disk. However, being in memory/injected, there is no file on disk that the CLR can use to resolve the proxy object’s definition.

When using melkor from a process running in memory, a FileNotFound exception will be thrown. This happens during the instantiation of the proxy object, for the reasons listed above.

The Logic (darkmelkor)