Tale of Hosting .NET in unmanaged code- PART/0x1

offensive .NET

Amazing folks in the community especially those who are inclined towards adversary simulation and other advanced attack vectors started tooling in C#. Why? you might ask. The reason is the .NET is at the heart of Windows and it is heavily integrated with the architecture itself, one could simply harness the power of the platform to develop malicious programs creatively by utilizing various features offered to the user. In last few years, the community has witnessed an enormous increase in offensive tools written in C#, this repo gives you an idea. For me personally its DInvoke project and weaponizing reflection feature that top the list. There is one more thing that deserves a special mention, weaponizing the Add-Type feature in PowerShell to integrate .NET classes in a powershell session. All of these tools and ideas that revolve around the .NET assemblies come in handy especially in post exploitation scenarios.

Is it possible to give operator the flexibility of running his favorite .NET assembly through a native application in a post exploitation scenario? If its possible then that would be awesome isn\’t?

Cobalt Strike to rescue: A history

The Cobalt Strike C2 framework ; needs no introduction, anyways 🙂 , introduced a very powerful feature called \”execute-assembly\” in its 3.11 version [2018]. This post is not going to focus on its official implementation, rather we will go through research done by brilliant folks in the community to decode the magic behind it to understand its functioning. In a nutshell \”execute-assembly\” made it possible to execute the .Net assemblies through unmanaged code. Cobalt Strike takes a \”Fork and Run\” approach to execute the operator provided assembly. A sacrificial process is created and with some reflective dll loading magic the CLR is hosted to run the assembly.

In this blog we are interested in how its possible to run a program coded in, lets say C#, through a program written in C/C++. Lets dive! 🙂

technical introduction

The Common Language Runtime/CLR is the soul of .NET, the runtime provides the environment for the code execution. It is responsible for doing memory management, Garbage collection just to name a few. Refer this to know more about its functions. The point is CLR is crux of the .NET and we need it for the execution of assemblies. Lets start with the concept of \”Hosting\” first!

Hosting

Hosting offers powerful APIs that enables the unmanaged host [C program] to integrate CLR into their applications. Simply by using the hosting APIs we can use CLR in unmanaged code to execute some .NET assembly [that is what we want right? 😉 ].

Here is a very popular way to host CLR in unmanaged host using following powerful Interfaces.

These interfaces expose various methods that we can use to perform various tasks from collecting basic information of CLR available on the system to execution of the user provided assemblies. Only above three interfaces and associated methods are all needed to execute an assembly from the disk through a C/C++ code.

execute a simple assembly via c

\"\"
  • Our objective is to run above C# assembly executing \”MyMethod\” that pops a messagebox via a host program written in C.
  • We need to specify path to assembly, the type, method and pass the arguments from the unmanaged C program to the target method in the assembly. We will accomplish it all using above mentioned hosting APIs.

This is how we are going to make it happen:

  • In our host program we will Instantiate the CLR by creating an instance of it in the memory.
  • Then load the CLR into current unmanaged process.
  • Finally we will start the CLR and execute the target assembly. In this post our program requires the user to explicitly provide the Assembly path, type information, and the target method to call. We will look at more flexible options of executing the assembly without much hassle in following series of this post.

The Unmanaged Host

Below C program hosts CLR and passes the user arguments to target method in the .NET assembly and finally executes it.

#include <stdio.h>
#include <metahost.h>
#include <comdef.h>
#pragma comment(lib,\"mscoree.lib\")
int main()
{
	ICLRMetaHost* CLRMetaHost = NULL;
	ICLRRuntimeInfo* CLRRuntimeInfo = NULL;
	ICLRRuntimeHost* CLRRuntimeHost = NULL;
	HRESULT hr;
	if (CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&CLRMetaHost) != S_OK)
	{
		printf(\"Failed to instantiate the CLR\");
	}
	printf(\"[+]CLR Created!\");
	
	if (CLRMetaHost->GetRuntime(L\"v4.0.30319\", IID_ICLRRuntimeInfo, (LPVOID*)&CLRRuntimeInfo) != S_OK)
	{
		printf(\"Failed to get runtime info\");
		return 0;
	}
	printf(\"\\n[+]RuntimeInfo fetched!\");
	if (CLRRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&CLRRuntimeHost) != S_OK)
	{
		printf(\"\\nFailed to load CLR into current processs\");
		return 0;
	}
	printf(\"\\n[+]CLR loaded into current process!\");
	if (CLRRuntimeHost->Start() != S_OK)
	{
		printf(\"\\nCLR failed to start\");
		return 0;
	}
	printf(\"\\n[+]CLR Started!\");
	
	DWORD pReturnValue;
	hr=CLRRuntimeHost->ExecuteInDefaultAppDomain(
		L\"C:\\\\Users\\\\ADK\\\\source\\\\repos\\\\AsmEx\\\\Program.exe\",
		L\"AsmEx.Program\",
		L\"MyMethod\",
		L\"Hello World from C#/.NET\",
		&pReturnValue);
	if (hr != S_OK)
	{	
		/*_com_error err(hr);
		 LPCTSTR errMsg = err.ErrorMessage();
		 wprintf(errMsg);
		*/
		printf(\"\\nFailed to execute assembly\");
	}
	
	
	
	printf(\"\\n[+]Assembly executed\");
	CLRRuntimeHost->Release();
	CLRRuntimeInfo->Release();
	CLRMetaHost->Release();
	return 0;
}

The breakdown

Before we begin, I had to glance over the implementation details of the above hosting interfaces to actually see how thing are looking under the hood. The header file \”metahost.h\” has all the declarations like one shown below.

\"\"

But this got me curious, I am not aware of \”interface\” keyword. After spending sometime doing the stackoverflow, I got this one. Apparently \”interface\” type is an alias to our good friend \”struct\”. Interface , in this MSVC context, are structures and it makes sense if we look at how we are calling methods from theses interfaces. Now that out of our way, lets take a close look at the host program.

CLR instance creation

The CLRCreateInstance API is used to create an instance of the CLR as shown in the image below. The function returns the interface ICLRMetaHost, I guess it instantiates the underlying structures.

\"\"

Loading clr

Now using the ICLRMetaHost interface we can call GetRuntime method to retrieve all the information of the runtime provided by the user before loading the CLR. In my case I ran \”clrver\” command in VS developer command prompt to fetch my installed runtime version as shown below. As this API expects pwzVersion from the user as first argument.

\"\"

The ICLRRuntimeInfo interface is returned in the CLRRuntimeInfo object as shown below. Using which we can simply loads the corresponding CLR into the host process.

\"\"

The GetInterface method of ICLRRuntimeInfo interface loads the CLR and gives access to the runtime itself through ICLRRuntimeHost interface as shown below.

\"\"

Starting CLR

The start method of ICLRRuntimeHost interface can start the CLR as shown below.

\"\"

Now we are almost done, we have the running CLR. What is left is to simply execute the target assembly via obtained ICLRRuntimeHost interface. There is an interesting method ExecuteInDefaultAppDomain exposed by the interface as shown below. We have to provide following data to call this method:

  • Assembly path
  • The type [class]
  • target method
  • Arguments expected by the method
  • a reference to fetch the return value of the target method
\"\"

I left some error handling code in there which is being commented out. During the execution, for some strange reason my VS was outputting a native exe file instead of an assembly with manifest and I couldnt make the assembly run. This error handling helped me to fix the issue. I later compiled the cs file using csc over the commandline.

Cleanup

We need to finally release everything using release method available in all hosting interfaces as shown below.

\"\"

Result

VOILA! Sweet isn\’t!

\"\"
\"\"

Below image shows the output of ProcessHacker, as we can see that our unmanaged process has successfully loaded the CLR and executed our class \”Program\” in DefaultDomain.

\"\"

conclusion

This is a very simple implementation to show that we can execute .NET assemblies through unmanaged and native applications by loading the CLR. Clearly the hosting Interfaces are doing the heavy lifting for us. In the next posts we will look at how we can improve upon this by finding a way to load the assembly directly from the memory itself rather than a file on the disk. We will also see \”InlineExecute-Assembly\” implementation in opensource C2 frameworks.

reference

https://mez0.cc/posts/extracting-execute-assembly/

Leave a Reply

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