Merging C# Assemblies using dnMerge

Introduction

When it comes to automating builds for any project that I undertake, my goto OS is usually Linux. Generally I find the deployment of build nodes easier to deploy and manage and usually cheaper than their Windows counterparts. The problem with this of course is Windows based software generally needs cross-compiling in someway or other. For C/C++ based tooling my goto build system is CMake and MinGW which makes cross compiling for Windows relatively easy.

When is comes to C# tool building on Linux we have the ability to use the dotnet core SDK. With the release of the Microsoft.NETFramework.ReferenceAssemblies nuget package, targeting most legacy .NET Frameworks is a breeze. A quick dotnet msbuild invocation within your C# project directory will generally yield executables that will run on Windows targeting the older framework versions as opposed to the newer .NET Core technology stack.

Unfortunately things start to fall apart when it comes to offensive C# tooling. With the ability to load and execute .NET assemblies through C2 frameworks such as Cobalt Strike, compilation usually involves the step of merging dependant assemblies into a monolithic exe ready for execution. Up until recently, my preference was to use Costura. Costura is a Fody extension that will compress and merge assemblies inside the main executable and uncompress and load from memory on demand during execution. That’s great, so what’s the problem? Since Costura is a build time dependency, on Linux it is executed under .NET Core during the build process. The net effect of this is additional .NET Core references will get pulled into your final assembly and commands such as execute-assembly will no longer work with assemblies cross-compiled from Linux due to the addition of .NET Core assembly references.

dnMerge Primer

I had a play around with MSBuild and developed a new build plugin called dnMerge. It works exactly like Costura where reference assemblies are compressed and merged during compilation but with the added benefit of retaining execute-assembly support when cross-compiling on Linux. Another benefit of dnMerge over Costura is the use of the LZMA compression algorithm over the traditional deflate algorithm used by Costura. When using Costura, a simple C# program with dependencies on cobbr’s (and other contributors) amazing SharpSploit library will result in a merged executable file a touch over 1MB. This eliminates the possibility of using execute-assembly inside Cobalt Strike due the 1MB or below hard limit on a single data transfer. With dnMerge the same project results in an executable size of around 800K which is usable with Cobalt Strike’s execute-assembly.

Using dnMerge in your project is as easy as adding the NuGet package from the central nuget.org repo. Debug builds are left alone and unmerged to allow easy debugging, but release builds will automatically compress and merge dependant assemblies ready for use with execute-assembly.

Below is a quick Microsoft SDK project template to get you started that can be used to build within Visual Studio or the dotnet Core SDK on Linux. Alternatively, add the dnMerge NuGet package to your project and build.

I have been using dnMerge for a couple of months now and it is also used for merging assemblies for BOF.NET. But if you do encounter any problems using within your own project, please let me know by creating an issue on GitHub.

dnMergeSample.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <!--<TargetFrameworks>net40;net35</TargetFrameworks>-->
    <TargetFrameworks>net40</TargetFrameworks>
    <RepositoryUrl></RepositoryUrl>
    <PackageProjectUrl></PackageProjectUrl>
    <GeneratePackageOnBuild>false</GeneratePackageOnBuild>
    <AssemblyName>dnMergeSample</AssemblyName>
    <RootNamespace>dnMergeSample</RootNamespace>
  </PropertyGroup>

  <ItemGroup>

    <PackageReference Include="dnMerge" Version="0.5.13">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>

  </ItemGroup>

  <PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net45|AnyCPU'">
    <DebugType>none</DebugType>
    <DebugSymbols>false</DebugSymbols>    
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net45|AnyCPU'">
    <DebugType>Full</DebugType>
    <DebugSymbols>true</DebugSymbols>
    <DefineConstants>DEBUG</DefineConstants>
  </PropertyGroup>

</Project>

The full project source can be found on my GitHub repo.

Credits

dnMerge uses the brilliant dnLib library for .NET assembly modifications. Without this library dnMerge would not be possible.

7-Zip’s LZMA SDK is used for compressing and uncompressing merged assemblies.

Weaponizing your favorite Go program for Cobalt Strike

Introduction

There are a myriad of ways currently to weaponize various offsec tools for use within Cobalt Strike. Many of these methods remain undetectable by modern day AV and EDR engines. Anything from in-memory PowerShell execution to using the execute-assembly command to run your latest SharpXXX .NET binary completely from memory.

Recently I have noticed an increase in the use of Golang for writing many offsec tools. Why? Well personally I put it down to the ease of compiling on both Windows, Linux and dare I say MacOS. In addition to ease of compiling, Golang can produce monolithic binaries that have no dependencies what so ever, other than the DLL’s or shared objects that are distributed as part of the operating system. The drawback of this of course is fairly large binaries. A simple helloworld program with Go 1.7+ comes in at around 1MB after stripping debug symbols. Once you start throwing in imports to 3rd party libraries this can easily reach 8MB+ and beyond.

So with the single monolithic binary in mind, I started looking at how a Go program can be weaponized for offsec purposes within Cobalt Strike. Cobalt Strike and metasploit have the capability of reflectively loading a DLL and executing it directly from memory. I won’t cover reflective loading here, since there are plenty of write-ups already on the subject, but if you are interested, head over to Stephen Fewer’s ReflectiveDLLInjection project which is one of the originals that many are based on today.

Goreflect

I have released a template project on GitHub than can be used to convert your favorite Go tool and compile it as a reflective DLL. The template is based on gobuster, but the project can be adapted for any Go tool. Currently the project is built using CMake, GCC and of course the Golang compiler. It utilizes the CGO interface within Go, which allows your Go entry point to be called from the reflective DllMain

The top level project file, CMakeLists.txt, is the glue for building our reflective DLL. The project adds gobuster as a dependency to our goreflect program, which in turn is linked to our reflective DLL, libgoreflect.

project (goreflect)
#Dependency to add simple Go support to CMake
include(${CMAKE_SOURCE_DIR}/cmake/GolangSimple.cmake)
#Your favorite go tool definition
GO_GET(gobuster github.com/OJ/gobuster)
#Dependency for goreflect to allow parsing of a command line string
GO_GET(gsq github.com/kballard/go-shellquote)
#Our Go static library, result is linkable using GCC
ADD_GO_INSTALLABLE_PROGRAM(goreflect_prog
                           goreflect.go  # our lightweigth wrapper around gobuster
                           gobuster gsq) # everything else is a dependency
#Standard C shared library using metasploits version of the reflective loader code
add_library(goreflect SHARED "ReflectiveDll.c" "ReflectiveLoader.c" "ReflectiveLoader.h" "ReflectiveDLLInjection.h")
target_include_directories(goreflect PUBLIC ${CMAKE_BINARY_DIR})
target_compile_definitions(goreflect PUBLIC GF_EXPORTS REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN)
#Linking all our dependencies as static, including our go program.  Results in no dependancies
target_link_libraries(goreflect ${CMAKE_BINARY_DIR}/libgoreflect_prog.lib -static-libgcc -static-libstdc++ -static -lpthread )

Our goreflect program a is a simple wrapper that exports our CGO function to the C world under the name start, allowing it to be called from our reflective DLL. The arg parameter is then parsed into individual arguments that can be used to call our go program. Now since go does not allow multiple main packages to be declared within a single program, we cannot import or call gobusters main directly. But luckily most go programs are designed in such a way that the main function is a proxy for the real main inside a separate package, in this case github.com/OJ/gobuster/cli/cmd. If you find that this is not the case, you may need to replicate some of the main code inside the startfunction within goreflect.go

package main
import "C"
import (
	 "os"
	 "fmt"	
	 "github.com/OJ/gobuster/cli/cmd"
	 
	 gsq "github.com/kballard/go-shellquote"
)
func main(){
	//not used
}
//export start
func start(arg string) {
	//parse our monolithinc argument string into individual args
	args, err := gsq.Split(arg)
	//our first argument is usally the program name, to just fake it
	args = append([]string{"goreflect"}, args...)
	if err == nil {
		//replace os.Args ready for calling our go program
		os.Args = args
		//run our go program
		cmd.Execute()
	} else {
		//parsing arguments failed, so bail.  Possibly unterminated string quote, etc...
		fmt.Printf("Failed to parse start arguments, %v\n", err)
	}
}

Our final piece of logic sits inside the ReflectiveDll.c file. This file holds the entry point to our reflective DLL and will call our start function exported from our go program. The DllMain function again is a fairly lightweight.

//===============================================================================================//
// This is a stub for the actuall functionality of the DLL.
//===============================================================================================//
#include "ReflectiveLoader.h"
#include <libgoreflect_prog.h>
// Note: REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR and REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN are
// defined in the project properties (Properties->C++->Preprocessor) so as we can specify our own 
// DllMain and use the LoadRemoteLibraryR() API to inject this DLL.
// You can use this value as a pseudo hinstDLL value (defined and set via ReflectiveLoader.c)
extern HINSTANCE hAppInstance;
//===============================================================================================//
BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved )
{
    BOOL bReturnValue = TRUE;
	switch( dwReason ) 
    { 
		case DLL_QUERY_HMODULE:
			if( lpReserved != NULL )
				*(HMODULE *)lpReserved = hAppInstance;
			break;
        case DLL_PROCESS_ATTACH: {
			hAppInstance = hinstDLL;
            GoString goArgs;
            goArgs.p = (char*)lpReserved;
            goArgs.n = strlen(lpReserved);
            start(goArgs);
			
			break;
        }
		case DLL_PROCESS_DETACH:
		case DLL_THREAD_ATTACH:
		case DLL_THREAD_DETACH:
            break;
    }
	return bReturnValue;
}

Inside the DLL_PROCESS_ATTACH case statement, we convert the lpReserved argument to a GoString object, as this is what the start function is expecting as it’s prototype. The lpReserved parameter is what Cobalt Strike and metasploit use to pass arguments to the reflective DLL.

I have made a quick video below showing goreflect in action. Utilizing the inject program from the ReflectiveDLL project, it demonstrates injecting the libgoreflect.dll into itself along with the arguments to send to our in-memory gobuster.

The code for goreflect can be found on GitHub.

That’s it for now. In part two I’ll cover how we can work around the 1MB limit within Cobalt Strike for reflective loading of our goreflect DLL.

Acknowledgements