Creating a simple beacon object file for Havoc C2
A step-by-step guide to implement a minimalistic Beacon Object File (BOF) that works with Havoc C2.
One of the best ways to extend the functionality of a C2 beacon is via a Beacon Object File (BOF). BOF allows a modular approach for adding new capabilities to a live beacon. Originally introduced as part of Cobalt Strike 4.1 release, other C2 software, such as, Havoc C2 and Sliver also integrated Cobalt Strike’s implementation of Beacon APIs. This integration allowed red team operators to leverage, with minimal modification, BOFs written for Cobalt Strike with other C2 software as well.
I have been working with Havoc C2 since last few days, so I thought of creating a simple BOF that can work with Havoc C2. What I assumed would be a simple process, turned out to be a little complicated and taught me a few things.
Follow my journey of 100 Days of Red Team on WhatsApp, Telegram or Discord.
To load a BOF in beacon, there are two options.
Use inline-execute command.
inline-execute </path/to/object/file>
Register a command in Havoc C2 GUI. This approach requires a Python script.
I went with the second approach for this experiment but I found inline-execute
command to be super helpful for troubleshooting.
I used the Object Files documentation available on Havoc’s website as a starting point. Based on this, I created the following Python script to register a command:
from havoc import Demon, RegisterCommand
def hello_world( demonID, *params ):
TaskID : str = None
demon : Demon = None
# create an instance of the argument packer
packer = Packer()
# get an instance of the demon
demon = Demon(demonID)
# check the parameters
if len(params) != 1:
demon.ConsoleWrite( demon.CONSOLE_ERROR, "wrong parameters!" )
return False
# pack the parameters
packer.addstr( params[ 0 ] )
# create a task ID
TaskID = demon.ConsoleWrite( demon.CONSOLE_TASK, f"Tasked demon to execute Hello World BOF" )
# instruct Havoc to run a BOF with certain parameters
demon.InlineExecute( TaskID, "go", f"hello-world.o", packer.getbuffer(), False )
# return the new task ID
return TaskID
RegisterCommand( hello_world, "", "hello-world", "Prints a simple message.", "", , "usage info" )
Note: At this point, hello-world.o represents a dummy BOF which will be created later.
To load this module in Havoc C2, I used Scripts → Script Manager → Load Script, navigated to the path where the Python script is stored, selected it and pressed Open.
I received the following error:
After a little bit of troubleshooting, I figured out that the number and sequence of arguments mentioned in the following line in Object Files documentation is incorrect.
RegisterCommand( my_new_command, "", "command-name", "A short description of what it does", "", , "usage info", "usage example" )
As per line 187 of Havoc.cc (this file contains implementation of Python API exposed by Havoc C2), it should be:
The number of arguments should be 7 (instead of 8) and the argument 5 should be of type int
instead of str
. Based on this, I fixed the RegisterCommand line in my Python script (everything else stayed the same):
RegisterCommand( hello_world, "", "hello-world", "Prints a simple message.", 0, "Usage: hello-world", "Example: hello-world" )
This time the script loaded successfully.
Since I just wanted to print a simple message, I got rid of extra code that was not required for this purpose. The final code of my Python script is listed below:
from havoc import Demon, RegisterCommand
def hello_world( demonID, *params ):
TaskID : str = None
demon : Demon = None
# create an instance of the argument packer
packer = Packer()
# get an instance of the demon
demon = Demon(demonID)
# create a task ID
TaskID = demon.ConsoleWrite( demon.CONSOLE_TASK, f"Tasked demon to execute Hello World BOF" )
# instruct Havoc to run a BOF with certain parameters
demon.InlineExecute( TaskID, "go", f"hello-world.o", packer.getbuffer(), False )
# return the new task ID
return TaskID
RegisterCommand( hello_world, "", "hello-world", "Prints a simple message.", 0, "Usage: hello-world", "Example: hello-world" )
To test the Python module, I launched the Havoc C2 implant on a Windows 10 target machine, received a beacon and entered the interaction mode (Right-click → Interact or double click the beacon).
In the interactive prompt, I typed help.
The command was visible in the help menu.
At this point, issuing the hello-world command would result in an error as I had not yet implemented the underlying BOF.
I used the following code to implement a minimalistic BOF that prints a message:
#include <windows.h>
#include <stdio.h>
#include "beacon.h"
int go(char * args, unsigned long length) {
BeaconPrintf(CALLBACK_OUTPUT, "This BOF was compiled as C++ file. Thank you for joining me on 100 Days of Red Team\n");
return 0;
}
I saved it as hello-world.cpp and used the following command to compile it:
x86_64-w64-mingw32-g++ -c hello-world.cpp -o hello-world.o -w
With the object file in place, I issued the hello-world
command in the beacon interactive prompt and received the [!] Symbol not found: __imp__Z12BeaconPrintfiPcz
error.
This error occurred because C++ changes function names when compiling (this is called name mangling) so that it can support features like function overloading.
This error can be resolved in two ways:
Compile the source code as C with GCC since C does not change function names (might not work if the code uses features exclusive to C++).
Use the
extern “C”
modifier and move#include “beacon.h”
within it. Theextern “C”
modifier instructs the C++ compiler to treat functions within beacon.h and also those enclosed withinextern “C”
block like a C functions and not to change their name.
To compile the source code as C, I saved the file as a .c file and used the following command:
x86_64-w64-mingw32-gcc -c hello-world.c -o hello-world.o -w
I then issued the hello-world
command in the beacon interactive prompt:
As I mentioned above, this approach might not work if the code is dependent on features that are specific to C++. In such cases, a better option is to use the extern “C”
modifier.
I added the extern “C”
modifier to the above code.
#include <windows.h>
#include <stdio.h>
extern "C" {
#include "beacon.h"
int go(char * args, unsigned long length) {
BeaconPrintf(CALLBACK_OUTPUT, "This BOF was compiled as C++ file. Thank you for joining me on 100 Days of Red Team\n");
return 0;
}
}
Compiled it using the following command:
x86_64-w64-mingw32-g++ -c hello-world.cpp -o hello-world.o -w
I then issued the hello-world
command in the beacon interactive prompt:
There we have it. A minimalistic working beacon for Havoc C2.
The code for this BOF is available in 100 Days of Red Team GitHub repository.
Red Team Notes
- Havoc C2 integrates Cobalt Strike beacon APIs for easier development of Beacon Object Files (BOFs) and extending a beacon's functionality.
- Use this this step by step guide to build a simple BOF that works with Havoc C2.
Follow my journey of 100 Days of Red Team on WhatsApp, Telegram or Discord.
Extremely detailed write-up, great work! Good job in finding a mistake in their documentation. Your knowledge of C is goal worthy.