Introduction

Beacon Object Files (BOFs) have been a game-changer in red teaming, allowing lightweight, in-memory execution of C code inside Cobalt Strike, Sliver, and other C2 frameworks. BOFs provide stealth, speed, and flexibility, making them a powerful tool for post-exploitation.

In this guide, we’ll walk through creating custom BOFs for Sliver, setting up the environment, compiling them, and executing them within an active session.

Do note, I am an absolute novice at this stuff. So dont take my word for it. Use the official Forta guide. https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics/beacon-object-files_how-to-develop.htm

What Are BOFs and Why Use Them?

BOFs are small position-independent code (PIC) modules that execute in-memory within an implant (like Sliver’s beacons). Instead of launching new processes or touching disk, BOFs execute directly in the beacon’s memory space, reducing forensic artifacts and improving stealth.

BOFs can be used for:
Credential dumping (LSASS injection, SAM parsing)
Privilege escalation (token impersonation, exploit execution)
Process injection (hollowing, shellcode execution)
Network enumeration (host discovery, ARP scanning)
File operations (reading protected files, registry modifications)

Setting Up Your BOF Development Environment

Before writing a BOF, we need to set up our development environment. Since BOFs are written in C, we’ll use MinGW-w64 for Windows compilation.

Install Dependencies

On Kali (or any Linux with Sliver C2 installed):

  • sudo apt update -y && sudo apt install mingw-w64

Now, let’s move on to writing a basic Sliver BOF.

In a text editor of choice, copy and paste the following C code.

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

typedef int (WINAPI *MESSAGEBOXA)(HWND, LPCSTR, LPCSTR, UINT);

void go(char *args, int len) {
    HMODULE user32 = LoadLibraryA("user32.dll");
    if (user32 == NULL) {
        BeaconPrintf(CALLBACK_ERROR, "Failed to load user32.dll");
        return;
    }

    MESSAGEBOXA pMessageBoxA = (MESSAGEBOXA)GetProcAddress(user32, "MessageBoxA");
    if (pMessageBoxA == NULL) {
        BeaconPrintf(CALLBACK_ERROR, "Failed to resolve MessageBoxA");
        return;
    }

    pMessageBoxA(NULL, "Hello, World!", "BOF Message", MB_OK | MB_ICONINFORMATION);
    
    FreeLibrary(user32);
}
  • save it as hello.c

This Beacon Object File (BOF) dynamically loads user32.dll, resolves the address of the MessageBoxA function, and then displays a message box with the text "Hello, World!".

  • LoadLibraryA("user32.dll") loads the User32.dll library into memory
  • This DLL contains Windows GUI-related functions, including MessageBoxA.
  • GetProcAddress(user32, "MessageBoxA") retrieves the memory address of the MessageBoxA function from user32.dll.
  • The function is then cast into a function pointer (MESSAGEBOXA), allowing it to be called dynamically.
  • FreeLibrary(user32); Unloads user32.dll from memory to avoid leaving unnecessary artifacts.

Next create a new file, and call it beacon.h

/*
 * Beacon Object Files (BOF)
 * -------------------------
 * A Beacon Object File is a light-weight post exploitation tool that runs
 * with Beacon's inline-execute command.
 *
 * Cobalt Strike 4.1.
 */

/* data API */
typedef struct {
	char * original; /* the original buffer [so we can free it] */
	char * buffer;   /* current pointer into our buffer */
	int    length;   /* remaining length of data */
	int    size;     /* total size of this buffer */
} datap;

DECLSPEC_IMPORT void    BeaconDataParse(datap * parser, char * buffer, int size);
DECLSPEC_IMPORT int     BeaconDataInt(datap * parser);
DECLSPEC_IMPORT short   BeaconDataShort(datap * parser);
DECLSPEC_IMPORT int     BeaconDataLength(datap * parser);
DECLSPEC_IMPORT char *  BeaconDataExtract(datap * parser, int * size);

/* format API */
typedef struct {
	char * original; /* the original buffer [so we can free it] */
	char * buffer;   /* current pointer into our buffer */
	int    length;   /* remaining length of data */
	int    size;     /* total size of this buffer */
} formatp;

DECLSPEC_IMPORT void    BeaconFormatAlloc(formatp * format, int maxsz);
DECLSPEC_IMPORT void    BeaconFormatReset(formatp * format);
DECLSPEC_IMPORT void    BeaconFormatFree(formatp * format);
DECLSPEC_IMPORT void    BeaconFormatAppend(formatp * format, char * text, int len);
DECLSPEC_IMPORT void    BeaconFormatPrintf(formatp * format, char * fmt, ...);
DECLSPEC_IMPORT char *  BeaconFormatToString(formatp * format, int * size);
DECLSPEC_IMPORT void    BeaconFormatInt(formatp * format, int value);

/* Output Functions */
#define CALLBACK_OUTPUT      0x0
#define CALLBACK_OUTPUT_OEM  0x1e
#define CALLBACK_ERROR       0x0d
#define CALLBACK_OUTPUT_UTF8 0x20

DECLSPEC_IMPORT void   BeaconPrintf(int type, char * fmt, ...);
DECLSPEC_IMPORT void   BeaconOutput(int type, char * data, int len);

/* Token Functions */
DECLSPEC_IMPORT BOOL   BeaconUseToken(HANDLE token);
DECLSPEC_IMPORT void   BeaconRevertToken();
DECLSPEC_IMPORT BOOL   BeaconIsAdmin();

/* Spawn+Inject Functions */
DECLSPEC_IMPORT void   BeaconGetSpawnTo(BOOL x86, char * buffer, int length);
DECLSPEC_IMPORT void   BeaconInjectProcess(HANDLE hProc, int pid, char * payload, int p_len, int p_offset, char * arg, int a_len);
DECLSPEC_IMPORT void   BeaconInjectTemporaryProcess(PROCESS_INFORMATION * pInfo, char * payload, int p_len, int p_offset, char * arg, int a_len);
DECLSPEC_IMPORT void   BeaconCleanupProcess(PROCESS_INFORMATION * pInfo);

/* Utility Functions */
DECLSPEC_IMPORT BOOL   toWideChar(char * src, wchar_t * dst, int max);
  • Save it as beacon.h

Our next file will be a Makefile. Used to compile the hello.c

BOFNAME := hello
CC_x64 := x86_64-w64-mingw32-gcc
CC_x86 := i686-w64-mingw32-gcc
CC=x86_64-w64-mingw32-clang

all:
	$(CC_x64) -o $(BOFNAME).x64.o -Os -c hello.c 
	$(CC_x86) -o $(BOFNAME).x86.o -Os -c hello.c 

test:
	$(CC_x64) hello.c -o $(BOFNAME).x64.exe
	$(CC_x86) hello.c -o $(BOFNAME).x86.exe

clean:
	rm *.o
  • Save it as Makefile

And finally, create the extension.json file which is used by Sliver


{
    "name": "Hello World!",
    "version": "1.0.0",
    "command_name": "hello-world",
    "extension_author": "Steve",
    "original_author": "Steve",
    "repo_url": "https://github.com/steve0ro/SliverBOFs/HelloWorld",
    "help": "Prints 'Hello World'",
    "depends_on": "coff-loader",
    "entrypoint": "go",
    "files": [
        {
            "os": "windows",
            "arch": "amd64",
            "path": "hello.x64.o"
        },
        {
            "os": "windows",
            "arch": "386",
            "path": "hello.x86.o"
        }
    ],
    "arguments": []
}

extension.json descibes how sliver should handle the extension that is created.

So, what is all this stuff?

beacon.h is a header file used when developing Beacon Object Files (BOFs) for Cobalt Strike. It provides essential function definitions, macros, and structures that allow BOFs to interact with the Beacon payload

Extension is a shared library that is reflectively loaded into the Sliver implant process, and is passed several callbacks to return data to the implant. As such these extensions must implement the Sliver API. Extensions may also have dependencies, which are other extensions. For example, the COFF Loader is a DLL extension that loads and executes BOFs, in turn BOFs simply extensions that rely on the COFF Loader as a dependency. These types of extensions do not need to implement any Sliver-specific API, since the Sliver API is abstracted by their dependency.

🔧 Next Steps: Adding to sliver!

You will need to have sliver running, and an established session/beacon.

  • beacon id: b07bfc60 is a local user
  • beacon id: 0f8a3c70 is Administrator

Run the make command in linux to compile hello.c into hello.x64.o and hello.x86.o

Next you will need to load and install into Sliver

Now if everything goes as planned. We can execute hello-world and get a pop up on our windows box.

And thats it!

If you are looking to remove the extension, just use

extensions rm hello-world

Here is the link for all the files: github

Creating Sliver BOFs (Beacon Object Files)