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.
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:
- Quick tips on getting the code compiled.
- How to use
- Limitations and what to expect.
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:
- 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).
- 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
buildfolder there is a
distfolder 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:
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
- 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.
- 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.
- 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.