Beacon Object Files
Beacon Object Files (BOFs) are a post-ex capability that allows for code execution inside the Beacon host process. The main advantage is to avoid the fork & run pattern that commands such as powershell
, powerpick
and execute-assembly
rely on. Since these spawn a sacrificial process and use process injection to run the post-ex action, they are heavily scrutinised by AV and EDR products.
BOFs are essentially tiny COFF objects (written in C or C++) on which Beacon acts as a linker and loader. Beacon does not link BOFs to a standard C library, so many common functions are not available. However, Beacon does expose several internal APIs that can be utilised to simplify some actions, such as argument parsing and handling output. Note that because they run inside the Beacon process, an unstable BOF may crash your Beacon. Run with care, or in a Beacon you don't mind losing.
To get started with writing a BOF, you'll want the Beacon header file - beacon.h (freely available to download), which defines the internal Beacon APIs that can be called.
beacon.h can be found on Attacker Desktop under C:\Tools\cobaltstrike\beacon.h
.
A BOF can be built using both the Visual Studio command line and MinGW compilers. For that reason, it doesn't matter if you dev on Windows or Linux, but it's quicker to build/test on the same machine you're running the CS GUI.
\
Hello World
This is a basic BOF which sends the string "Hello World" as output. Save it as hello-world.c
.
\
To compile on Windows, open the x64 Native Tools Command Prompt for VS 2019, ensure your hello-world.c
and beacon.h
files are in the same directory, then run: cl.exe /c /GS- hello-world.c /Fohello-world.o
. If on Linux (e.g. Ubuntu in WSL), then: x86_64-w64-mingw32-gcc -c hello-world.c -o hello-world.o
.
To execute the BOF, run: inline-execute \path\to\hello-world.o
.
\
\
This built-in inline-execute
command expects that the entry point of the BOF is called go. Otherwise, you will see an error like:
\
\
BOFs can be integrated with Aggressor by registering custom aliases and commands.
\
The third argument of beacon_inline_execute
is the entry point of the BOF. If you use something other than "go", specify it here.
\
\
Handling Arguments
Naturally, there will be times where we want to pass arguments down to a BOF. A typical console application may have an entry point which looks like: main(int argc, char *argv[])
. But a BOF uses go(char * args, int len)
. These arguments are "packed" into a special binary format using the bof_pack aggressor function, and can be "unpacked" using Beacon APIs exported via beacon.h. Don't try and unpack these yourself if you value your sanity.
Let's work on an example where we want to provide a username to our BOF.
First, call the BeaconDataParse API to initialise the parser; then BeaconDataExtract to extract the username argument.
\
Arguments should be unpacked in the same order they were packed. For instance, if we were sending both a username and password to the BOF, we would do:
\
You may also extract integers with BeaconDataInt. For example if you wanted to sent a PID as an argument.
\
Arguments passed on the CS GUI command line are separated by whitespace. The first argument, $1, is always the current Beacon, then $2, $3, $n, is your input. We can pack the arguments we want to send in our aggressor script like so:
\
$2 will simply hold the username.
"z" tells Cobalt Strike what format of data this is, where "z" represents a zero-terminated and encoded string.
\
The Cobalt Strike documentation provides the following table of valid data formats and how to unpack them:
\
Multiple arguments should be packed at the same time:
\
beacon_inline_execute
accepts a forth parameter, of packed args.
\
\
Calling Win32 APIs
APIs such as LoadLibrary and GetProcAddress are available from a BOF, which can be used to resolve and call other APIs at runtime. However, BOFs provide a convention called Dynamic Function Resolution (DFR), which allows Beacon to perform the necessary resolution for you.
The definition for a DFR is best shown with an example. Here is what it would look like to call MessageBoxW from user32.dll.
\
Most of this information comes from the official API documentation, whilst DECLSPEC_IMPORT and WINAPI provide important hints for the compiler. Putting it together:
\
\
\
There are some excellent BOFs out there which I encourage you to experiment with. To name a few:
NanoDump by HelpSystems.
Last updated