support

Beacon Object Files

A Beacon Object File (BOF) is a compiled C program, written to a convention that allows it to execute within a Beacon process and use internal Beacon APIs. BOFs are a way to rapidly extend the Beacon agent with new post-exploitation features.

What are the advantages of BOFs?

One of the key roles of an command&control platform is to provide ways to use external post-exploitation functionality. Cobalt Strike already has tools to use PowerShell, .NET, and Reflective DLLs. These tools rely on an OPSEC expensive fork&run pattern that involves a process create and injection for each post-exploitation action. BOFs have a lighter footprint. They run inside of a Beacon process and are cleaned up after the capability is done.

BOFs are also very small. A UAC bypass privilege escalation Reflective DLL implementation may weigh in at 100KB+. The same exploit, built as a BOF, is <3KB. This can make a big difference when using bandwidth constrained channels, such as DNS.

Finally, BOFs are easy to develop. You just need a Win32 C compiler and a command line. Both MinGW and Microsoft's C compiler can produce BOF files. You don't have to fuss with project settings that are sometimes more effort than the code itself.

How do BOFs work?

To Beacon, a BOF is just a block of position-independent code that receives pointers to some Beacon internal APIs.

To Cobalt Strike, a BOF is an object file produced by a C compiler. Cobalt Strike parses this file and acts as a linker and loader for its contents. This approach allows you to write position-independent code, for use in Beacon, without tedious gymnastics to manage strings and dynamically call Win32 APIs.

What are the disadvantages of BOFs?

BOFs are single-file C programs that call Win32 APIs and limited Beacon APIs. Don't expect to link in other functionality or build large projects with this mechanism.

Cobalt Strike does not link your BOF to a libc. This mean you're limited to compiler intrinsics (e.g., __stosb on Visual Studio for memset), the exposed Beacon internal APIs, Win32 APIs, and the functions that you write. Expect that a lot of common functions (e.g., strlen, stcmp, etc.) are not available to you via a BOF.

BOFs execute inside of your Beacon agent. If a BOF crashes, you or a friend you value will lose an access. Write your BOFs carefully.

Cobalt Strike expects that your BOFs are single-threaded programs that run for a short period of time. BOFs will block other Beacon tasks and functionality from executing. There is no BOF pattern for asynchronous or long-running tasks. If you want to build a long-running capability, consider a Reflective DLL that runs inside of a sacrificial process.

How do I develop a BOF?

Easy. Open up a text editor and start writing a C program. Here's a Hello World BOF:

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

void go(char * args, int alen) {
      BeaconPrintf(CALLBACK_OUTPUT, "Hello World: %s", args);
}

Download beacon.h (you'll need it):

https://www.cobaltstrike.com/downloads/beacon.h

To compile this with Visual Studio:

cl.exe /c /GS- hello.c /Fohello.o

To compile this with x86 MinGW:

i686-w64-mingw32-gcc -c hello.c -o hello.o

To compile this with x64 MinGW:

x86_64-w64-mingw32-gcc -c hello.c -o hello.o

The above commands will produce a hello.o file. Use inline-execute in Beacon to run the BOF.

beacon> inline-execute /path/to/hello.o these are arguments

beacon.h contains definitions for several internal Beacon APIs. The function go is similar to main in any other C program. It's the function that's called by inline-execute and arguments are passed to it. BeaconOutput is an internal Beacon API to send output to the operator. Not much to it.

Dynamic Function Resolution

GetProcAddress, LoadLibraryA, GetModuleHandle, and FreeLibrary are available within BOF files. You have the option to use these to resolve Win32 APIs you wish to call. Another option is to use Dynamic Function Resolution (DFR).

Dynamic Function Resolution is a convention to declare and call Win32 APIs as LIBRARY$Function. This convention provides Beacon the information it needs to explicitly resolve the specific function and make it available to your BOF file before it runs. When this process fails, Cobalt Strike will refuse to execute the BOF and tell you which function it couldn't resolve.

Here's an example BOF that uses DFR and looks up the current domain:

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

DECLSPEC_IMPORT DWORD WINAPI NETAPI32$DsGetDcNameA(LPVOID, LPVOID, LPVOID, LPVOID, ULONG, LPVOID);
DECLSPEC_IMPORT DWORD WINAPI NETAPI32$NetApiBufferFree(LPVOID);

void go(char * args, int alen) {
	DWORD dwRet;
	PDOMAIN_CONTROLLER_INFO pdcInfo;

	dwRet = NETAPI32$DsGetDcNameA(NULL, NULL, NULL, NULL, 0, &pdcInfo);
	if (ERROR_SUCCESS == dwRet) {
		BeaconPrintf(CALLBACK_OUTPUT, "%s", pdcInfo->DomainName);
	}

	NETAPI32$NetApiBufferFree(pdcInfo);
}

The above code makes DFR calls to DsGetDcNameA and NetApiBufferFree from NETAPI32. When you declare function prototypes for Dynamic Function Resolution, pay close attention to the decorators attached to the function declaration. Keywords, such as WINAPI and DECLSPEC_IMPORT are important. These decorations provide the compiler with the needed hints to pass arguments and generate the right call instruction.

Aggressor Script and BOFs

You'll likely want to use Aggressor Script to run your finalized BOF implementations within Cobalt Strike. A BOF is a good place to implement a lateral movement technique, an escalation of privilege tool, or a new reconaissance capability.

The &beacon_inline_execute function is Aggressor Script's entry point to run a BOF file. Here is a script to run a simple Hello World program:

alias hello {
	local('$barch $handle $data $args');
	
	# figure out the arch of this session
	$barch  = barch($1);
	
	# read in the right BOF file
	$handle = openf(script_resource("hello. $+ $barch $+ .o"));
	$data   = readb($handle, -1);
	closef($handle);
	
	# pack our arguments
	$args   = bof_pack($1, "zi", "Hello World", 1234);
	
	# announce what we're doing
	btask($1, "Running Hello BOF");
	
	# execute it.
	beacon_inline_execute($1, $data, "demo", $args);
}

The script first determines the architecture of the session. An x86 BOF will only run in an x86 Beacon session. Conversely, an x64 BOF will only run in an x64 Beacon session. This script then reads target BOF into an Aggressor Script variable. The next step is to pack our arguments. The &bof_pack function packs arguments in a way that is compatible with Beacon's internal data parser API. This script uses the customary &btask to log the action the user asked Beacon to perform. And, &beacon_inline_execute runs the BOF with its arguments.

The &beacon_inline_execute function accepts the Beacon ID as the first argument, a string containing the BOF content as a second argument, the entry point as its third argument, and the packed arguments as its fourth argument. The option to choose an entrypoint exists in case you choose to combine like-functionality into a single BOF.

Here is the C program that corresponds to the above script:

/*
 * Compile with:
 * x86_64-w64-mingw32-gcc -c hello.c -o hello.x64.o
 * i686-w64-mingw32-gcc -c hello.c -o hello.x86.o
 */

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

void demo(char * args, int length) {
	datap  parser;
	char * str_arg;
	int    num_arg;
	
	BeaconDataParse(&parser, args, length);
	str_arg = BeaconDataExtract(&parser, NULL);
	num_arg = BeaconDataInt(&parser);
	
	BeaconPrintf(CALLBACK_OUTPUT, "Message is %s with %d arg", str_arg, num_arg);
}

The demo function is our entrypoint. We declare the datap structure on the stack. This is an empty and unintialized structure with state information for extracting arguments prepared with &bof_pack. BeaconDataParse initializes our parser. BeaconDataExtract extracts a length-prefixed binary blob from our arguments. Our pack function has options to pack binary blobs as zero-terminated strings encoded to the session's default character set, a zero-terminated wide-character string, or a binary blob without transformation. The BeaconDataInt extracts an integer that was packed into our arguments. BeaconPrintf is one way to format output and make it available to the operator.

BOF C API

Data Parser API

The Data Parser API extracts arguments packed with Aggressor Script's &bof_pack function.

char * BeaconDataExtract (datap * parser, int * size)
    Extract a length-prefixed binary blob. The size argument may be NULL. If an address is provided, size is populated with the number-of-bytes extracted.
int BeaconDataInt (datap * parser)
    Extract a 4b integer
int BeaconDataLength (datap * parser)
    Get the amount of data left to parse
void BeaconDataParse (datap * parser, char * buffer, int size)
    Prepare a data parser to extract arguments from the specified buffer
short BeaconDataShort (datap * parser)
    Extract a 2b integer

Output API

The Output API returns output to Cobalt Strike.

void BeaconPrintf (int type, char * fmt, ...)
    Format and present output to the Beacon operator
void BeaconOutput (int type, char * data, int len)
    Send output to the Beacon operator

Each of these functions accepts a type argument. This type determines how Cobalt Strike will process the output and what it will present the output as. The types are:

CALLBACK_OUTPUT is generic output. Cobalt Strike will convert this output to UTF-16 (internally) using the target's default character set.

CALLBACK_OUTPUT_OEM is generic output. Cobalt Strike will convert this output to UTF-16 (internally) using the target's OEM character set. You probably won't need this, unless you're dealing with output from cmd.exe.

CALLBACK_ERROR is a generic error message.

CALLBACK_OUTPUT_UTF8 is generic output. Cobalt Strike will convert this output to UTF-16 (internally) from UTF-8.

Format API

The format API is used to build large or repeating output.

void BeaconFormatAlloc (formatp * obj, int maxsz)
    Allocate memory to format complex or large output
void BeaconFormatAppend (formatp * obj, char * data, int len)
    Append data to this format object
void BeaconFormatFree (formatp * obj)
    Free the format object
void BeaconFormatInt (formatp * obj, int val)
    Append a 4b integer (big endian) to this object
void BeaconFormatPrintf (formatp * obj, char * fmt, ...)
    Append a formatted string to this object
void BeaconFormatReset (formatp * obj)
    Resets the format object to its default state (prior to re-use)
char * BeaconFormatToString (formatp * obj, int * size)
    Extract formatted data into a single string. Populate the passed in size variable with the length of this string. These parameters are suitable for use with the BeaconOutput function.

Internal APIs

The following functions manipulate the token used in the current Beacon context:

BOOL BeaconUseToken (HANDLE token)
    Apply the specified token as Beacon's current thread token. This will report the new token to the user too. Returns TRUE if successful. FALSE is not.
void BeaconRevertToken ()
    Drop the current thread token. Use this over direct calls to RevertToSelf. This function cleans up other state information about the token.
BOOL BeaconIsAdmIn ()
    Returns TRUE if Beacon is in a high-integrity context

The following functions provide some access to Beacon's process injection capability:

void BeaconGetSpawnTo (BOOL x86, char * buffer, int length)
    Populate the specified buffer with the x86 or x64 spawnto value configured for this Beacon session.
BOOL BeaconSpawnTemporaryProcess (BOOL x86, BOOL ignoreToken, STARTUPINFO * sInfo, PROCESS_INFORMATION * pInfo)
    This function spawns a temporary process accounting for ppid, spawnto, and blockdlls options. Grab the handle from PROCESS_INFORMATION to inject into or manipulate this process. Returns TRUE if successful.
void BeaconInjectProcess (HANDLE hProc, int pid, char * payload, int payload_len, int payload_offset, char * arg, int arg_len)
    This function will inject the specified payload into an existing process. Use payload_offset to specify the offset within the payload to begin execution. The arg value is for arguments. arg may be NULL.
void BeaconInjectTemporaryProcess (PROCESS_INFORMATION * pInfo, char * payload, int payload_len, int payload_offset, char * arg, int arg_len)
    This function will inject the specified payload into a temporary process that your BOF opted to launch. Use payload_offset to specify the offset within the payload to begin execution. The arg value is for arguments. arg may be NULL.
void BeaconCleanupProcess (PROCESS_INFORMATION * pInfo)
    This function cleans up some handles that are often forgotten about. Call this when you're done interacting with the handles for a process. You don't need to wait for the process to exit or finish.

The following function is a utility function:

BOOL toWideChar (char * src, wchar_t * dst, int max)
    Convert the src string to a UTF16-LE wide-character string, using the target's default encoding. max is the size (in bytes!) of the destination buffer.