This is a quick lab to see what API sequence makes it possible to inject C# .NET assemblies / PE files (.exe and .dll) into an unmanaged process and invoke their methods.
{% hint style="info" %}
This is the technique that makes execute-assembly
command possible in Cobalt Strike.
{% endhint %}
At a high level, it works as follows:
CLRCreateInstance
is used to retrieve an interfaceICLRMetaHost
ICLRMetaHost->GetRuntime
is used to retrieveICLRRuntimeInfo
interface for a specified CLR versionICLRRuntimeInfo->GetInterface
is used to load the CLR into the current process and retrieve an interfaceICLRRuntimeHost
ICLRRuntimeHost->Start
is used to initialize the CLR into the current processICLRRuntimeHost->ExecuteInDefaultAppDomain
is used to load the C# .NET assembly and call a particular method with an optionally provided argument
-
unmanaged.cpp
(in my lab compiled toLoadCLR.exe
) - a C++ program that loads a C# assemblyCLRHello1.exe
and invokes its methodspotlessMethod
-
managed.cs
(in my lab compiled toCLRHello1.exe
) - a C# program that is loaded by the unmanaged process (LoadCLR.exe
). It has a methodspotlessMethod
that is invoked viaExecuteInDefaultAppDomain.
O
Once invoked, the spotlessMethod
prints out Hi from CLR
to the console window.
{% tabs %} {% tab title="unmanaged.cpp" %}
// code mostly stolen from pabloko's comment in https://gist.github.com/xpn/e95a62c6afcf06ede52568fcd8187cc2
#include <iostream>
#include <metahost.h>
#include <corerror.h>
#pragma comment(lib, "mscoree.lib")
int main()
{
ICLRMetaHost* metaHost = NULL;
ICLRRuntimeInfo* runtimeInfo = NULL;
ICLRRuntimeHost* runtimeHost = NULL;
DWORD pReturnValue;
CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&metaHost);
metaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID*)&runtimeInfo);
runtimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&runtimeHost);
runtimeHost->Start();
HRESULT res = runtimeHost->ExecuteInDefaultAppDomain(L"C:\\labs\\CLRHello1\\CLRHello1\\CLRHello1\\bin\\Debug\\CLRHello1.exe", L"CLRHello1.Program", L"spotlessMethod", L"test", &pReturnValue);
if (res == S_OK)
{
std::cout << "CLR executed successfully\n";
}
runtimeInfo->Release();
metaHost->Release();
runtimeHost->Release();
return 0;
}
{% endtab %}
{% tab title="managed.cs" %}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CLRHello1
{
class Program
{
static void Main(string[] args)
{
return;
}
// important: methods called by ExecuteInDefaultAppDomain need to stick to this signature
static int spotlessMethod(String pwzArgument)
{
Console.WriteLine("Hi from CLR");
return 1;
}
}
}
{% endtab %} {% endtabs %}
Below shows how LoadCLR.exe
loaded our C# assembly CLRHello.exe
(seen in LoadCLR.exe
loaded modules tab) and invoked the spotlessMethod
, that printed Hi from CLR
to the console:
{% embed url="https://blog.xpnsec.com/hiding-your-dotnet-etw/" %}
https://gist.github.com/xpn/e95a62c6afcf06ede52568fcd8187cc2