bofnet_executeassembly - in-process execution of standard .NET assemblies in Cobalt Strike

UPDATE: Since the original post, there's now also functions for bofnet_jobassembly (for non-blocking, background execution) and bofnet_patchexit (for avoiding assembly using Environment.Exit() killing Beacon). For more information, see the fork.

This post details an addition to BOF.NET for performing in-process execution of .NET assemblies in Cobalt Strike (see here for the code). It's a follow on to a previous post on the same topic but with a different methodology. Unlike the approach described there, this approach does not require modifying your .NET project's code to get it working with BOF.NET.

The function, bofnet_executeassembly, is part of BOF.NET itself, and this handles all of the console output capture, and feeding it back to Cobalt Strike. Therefore, you can use your standard .NET assemblies you use everywhere else with it.

The three topics covered briefly in this post are:

  1. Quick tips on getting the code compiled.
  2. How to use bofnet_executeassembly.
  3. Limitations and what to expect.

Compilation

When putting the code together I found there was a few intricacies to getting BOF.NET compiled and working. Firstly make sure you pull the code supporting the function. For this see the pull request here. Then as for a few tips to get it up and running:

  1. It's better to compile using Linux. It could very well just be me, but I couldn't get compilation on Windows working. Dependencies are mingw (see apt) and .NET Core (see here for installation guidance).
  2. Compilation is straight forward if you use CCob's provided commands, but it's important to note where the two output locations are as it may not be obvious. In your build folder there is a dist folder that contains the code for CLR versions 2.0 and 4.0. In these folders that's where your Aggressor (CNA) file is that you need to import into Cobalt Strike, along with the BOF.NET library, and the BOF file it uses to set everything up. To confirm that's:
    1. ../build/dist/{net20|net40}/bofnet.cna
    2. ../build/dist/{net20|net40}/BOFNET.dll
    3. ../build/dist/{net20|net40}/bofnet_execute.cpp.x86.obj
    4. ../build/dist/{net20|net40}/bofnet_execute.cpp.x64.obj

Usage

The only new command is bofnet_executeassembly, but there's a few other commands you need to run first to initialise BOF.NET and load your assembly before execution.

# initialise BOF.NET
bofnet_init
# Load your .NET assembly - this is the same function as with BOF.NET assemblies
bofnet_load /path/to/assembly.exe
# (Optionally) list the loaded assemblies to check the names
bofnet_listassemblies
# Call the entry point on your assembly and pass any desired arguments
bofnet_executeassembly AssemblyName argument1 argument2 

Limitations

  1. Output is provided post execution only, and Beacon blocks until execution is complete. If you want updates to be provided in real-time and for non-blocking behaviour see code option #2 on my previous post.
  2. You still need to deal with AMSI and ETW yourself. AMSI is particularly notable as the "amsi_disable" setting in the malleable profile only applies to fork and run operations, and it isn't patched within the Beacon process itself. Therefore, if you're going to use in-process execution be aware that your payloads are going to be scanned if you don't deal with AMSI.
  3. You need to watch for assemblies that call Environment.Exit() as it will as expected, kill your process, and therefore, also your Beacon session. To mitigate this without code changes to the assemblies themselves see this post by MDSec. This code like the one described above would need to be integrated into BOF.NET.

Leave a Reply