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 theMessageBoxA
function fromuser32.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
🔧 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