Beacon Object Files (BOFs)

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.

To get started with writing a BOF, you'll want the Beacon header file - beacon.h (https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/beacon.h)

- Hello World

#include <windows.h>
#include "beacon.h"

void go(char * args, int len)
{
    BeaconPrintf(CALLBACK_OUTPUT, "Hello World");
}

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, then: x86_64-w64-mingw32-gcc -c hello-world.c -o hello-world.o.

To execute the BOF:

beacon> 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, we willsee an error)

- Integrating BOFs in Agressor

BOFs can be integrated with Aggressor by registering custom aliases and commands.

alias hello-world {
    local('$handle $bof $args');
    
    # read the bof file (assuming x64 only)
    $handle = openf(script_resource("hello-world.o"));
    $bof = readb($handle, -1);
    closef($handle);
    
    # print task to console
    btask($1, "Running Hello World BOF");
    
    # execute bof
    beacon_inline_execute($1, $bof, "go");
}

# register a custom command (entry point of the BOF.  If you use something other than "go", specify it here)
beacon_command_register("hello-world", "Execute Hello World BOF", "Loads hello-world.o and calls the \"go\" entry point.");

- 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).

Valid data formats and how to unpack them:

Format

Description

Unpack Function

b

Binary data

BeaconDataExtract

i

4-byte integer (int)

BeaconDataInt

s

2-byte integer (short)

BeaconDataShort

z

zero-terminated+encoded string

BeaconDataExtract

Z

zero-terminated wide string

(wchar_t *)BeaconDataExtract

For example, if we want to provide a username to our BOF:

/* irst, call the BeaconDataParse API to initialise the parser; then BeaconDataExtract to extract the username argument */

void go(char * args, int len)
{
	datap parser;
	BeaconDataParse(&parser, args, len);

/* Arguments should be unpacked in the same order they were packed */
	char * username;
	char * password;

	username = BeaconDataExtract(&parser, NULL);
	password = BeaconDataExtract(&parser, NULL);

	BeaconPrintf(CALLBACK_OUTPUT, "The username is: %s", username);
}

// extract integers with BeaconDataInt. 
int pid;
pid = BeaconDataInt(&parser);

/* 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. */
// pack 2 strings
$args = bof_pack($1, "zz", "str1", "str2");

// pack a string and an int
$args = bof_pack($1, "zi", "str1", 123);

beacon_inline_execute($1, $bof, "go", $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.

Example:

#include <windows.h>
#include "beacon.h"

void go(char * args, int len)
{
    DECLSPEC_IMPORT INT WINAPI USER32$MessageBoxW(HWND, LPCWSTR, LPCWSTR, UINT);

    datap parser;
    BeaconDataParse(&parser, args, len);

    wchar_t * message;
    message = (wchar_t *)BeaconDataExtract(&parser, NULL);

    USER32$MessageBoxW(NULL, message, L"Message Box", 0);
}
alias message-box {
    local('$handle $bof $args');
    
    # read the bof file
    $handle = openf(script_resource("msg-box.o"));
    $bof = readb($handle, -1);
    closef($handle);

    # pack args
    $args = bof_pack($1, "Z", $2);
    
    # print task to console
    btask($1, "Running MessageBoxW BOF");
    
    # execute bof
    beacon_inline_execute($1, $bof, "go", $args);
}

# register a custom command
beacon_command_register("message-box", "Pop a message box", "Calls the MessageBoxW Win32 API.");

- Interestings BOFs

CS-Situational-Awareness-BOF (https://github.com/trustedsec/CS-Situational-Awareness-BOF)

BOF.NET (https://github.com/CCob/BOF.NET)

NanoDump (https://github.com/fortra/nanodump)

InlineWhispers (https://github.com/outflanknl/InlineWhispers)

Last updated