Powered By GitBook
Injecting .NET Assembly to an Unmanaged Process
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.
This is the technique that makes execute-assembly command possible in Cobalt Strike.

Overview

At a high level, it works as follows:
    1.
    CLRCreateInstance is used to retrieve an interface ICLRMetaHost
    2.
    ICLRMetaHost->GetRuntime is used to retrieve ICLRRuntimeInfo interface for a specified CLR version
    3.
    ICLRRuntimeInfo->GetInterface is used to load the CLR into the current process and retrieve an interface ICLRRuntimeHost
    4.
    ICLRRuntimeHost->Start is used to initialize the CLR into the current process
    5.
    ICLRRuntimeHost->EecuteInDefaultAppDomain is used to load the C# .NET assembly and call a particular method with an optionally provided argument

Code

    unmanaged.cpp (in my lab compiled to LoadCLR.exe) - a C++ program that loads a C# assembly
    CLRHello1.exe and invokes its method spotlessMethod
    managed.cs (in my lab compiled to CLRHello1.exe) - a C# program that is loaded by the unmanaged process (LoadCLR.exe). It has a method spotlessMethod that is invoked via ExecuteInDefaultAppDomain.O
Once invoked, the spotlessMethod prints out Hi from CLR to the console window.
unmanaged.cpp
managed.cs
1
// code mostly stolen from pabloko's comment in https://gist.github.com/xpn/e95a62c6afcf06ede52568fcd8187cc2
2
#include <iostream>
3
#include <metahost.h>
4
#include <corerror.h>
5
#pragma comment(lib, "mscoree.lib")
6
7
int main()
8
{
9
ICLRMetaHost* metaHost = NULL;
10
ICLRRuntimeInfo* runtimeInfo = NULL;
11
ICLRRuntimeHost* runtimeHost = NULL;
12
DWORD pReturnValue;
13
14
CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&metaHost);
15
metaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID*)&runtimeInfo);
16
runtimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&runtimeHost);
17
runtimeHost->Start();
18
HRESULT res = runtimeHost->ExecuteInDefaultAppDomain(L"C:\\labs\\CLRHello1\\CLRHello1\\CLRHello1\\bin\\Debug\\CLRHello1.exe", L"CLRHello1.Program", L"spotlessMethod", L"test", &pReturnValue);
19
if (res == S_OK)
20
{
21
std::cout << "CLR executed successfully\n";
22
}
23
24
runtimeInfo->Release();
25
metaHost->Release();
26
runtimeHost->Release();
27
return 0;
28
}
Copied!
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4
using System.Text;
5
using System.Threading.Tasks;
6
7
namespace CLRHello1
8
{
9
class Program
10
{
11
static void Main(string[] args)
12
{
13
return;
14
}
15
16
// important: methods called by ExecuteInDefaultAppDomain need to stick to this signature
17
static int spotlessMethod(String pwzArgument)
18
{
19
Console.WriteLine("Hi from CLR");
20
return 1;
21
}
22
}
23
}
Copied!

Demo

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:

References

@_xpn_ - Hiding your .NET - ETW
XPN InfoSec Blog
Last modified 1yr ago