CobaltStrike Guide. #8 Beacon Object Files

19 June 2023 12 minutes Author: Lady Liberty

The CobaltStrike Guide to Beacon Object Files and Effective Cyber Attack Management

Our CobaltStrike guide offers detailed information and guidance on using Beacon Object Files, which are key elements for deploying and managing Beacon agents in CobaltStrike. You will learn about the basic principles of operation of Beacon Object Files, their role in communication with the target system, as well as how to optimize and configure them to achieve maximum effectiveness of cyber attacks. In the guide, we will present different options for using Beacon Object Files, including creating and configuring your own files, as well as using ready-made templates. We’ll also look at obfuscation and protection techniques for Beacon agents, allowing you to avoid detection and analysis by defense mechanisms.

Our CobaltStrike guide provides clear explanations, step-by-step instructions and practical tips for using Beacon Object Files. You will gain the necessary knowledge and skills to effectively use these files to manage cyber-attacks and ensure a robust presence on the target system. Access the CobaltStrike Guide today and unlock the potential of Beacon Object Files to effectively manage your cyberattacks in the digital space. In the CobaltStrike: Beacon Object Files guide, we’ll take a deep dive into this key CobaltStrike functionality that allows you to create and configure Beacon agents to manage and interact with target systems. We will cover various aspects of using Beacon Object Files, such as creation, configuration and optimization, as well as the principles of communication with the CobaltStrike server. In the guide, you will find practical tips, step-by-step instructions and code examples to help you understand and use Beacon Object Files in practice. You will learn how to create configuration files for Beacon agents, how to ensure reliable communication with your agents, and how to perform optimizations to improve the performance and effectiveness of your cyber attack.

Beacon Object Files

A Beacon Object File (BOF) is a compiled C program written according to certain conventions that allow it to run as part of a Beacon process and use Beacon’s internal APIs. BOF is a way to quickly extend the Beacon agent with new functionality after deployment.

What are the advantages of converters?

One of the key roles of the command and control platform is to provide ways to use external functionality for post-operation.

Cobalt Strike already has tools for using PowerShell, .NET, and Reflective DLLs. These tools rely on the fork and run OPSEC pattern, which includes creating and implementing processes for each post-operational activity. Converters have an easier way. They run inside the Beacon process and the memory can be managed using the Malleable c2 profile in the process injection block.

Converters are also very small. A reflection DLL implementation for bypassing UAC with privilege escalation can weigh more than 100KB. The same exploit created as a converter is <3Kb in size. This can be of great importance when using bandwidth-constrained schemes such as DNS.

Finally, the buck converter is easy to design. You just need a Win32 C compiler and a command line. MinGW and Microsoft’s C compiler can create converter files. You won’t have to mess with project settings, which sometimes require more effort than the code itself.

How do converter converters work?

From Beacon’s perspective, a converter is simply a block of position-independent code that receives pointers to some internal beacon API.

From the point of view of Cobalt Strike, a converter is an object file created by the C compiler. CobaltStrike parses this file and acts as a linker and loader of its contents. This approach allows you to write position-independent code for use in Beacon without the tedious work of manipulating strings and dynamically calling the Win32 API.

What are the disadvantages of converters?

BOFs are single-file C programs that call Win32 APIs and limited beacon APIs. Don’t expect to plug in other functionality or build large projects with this engine.

Cobalt Strike does not link your BOF with libc. This means you’re limited by the intricacies of compilers  (like stosb in Visual Studio for memset), open internal beacon APIs, Win32 APIs, and functions you write. Expect that many distributed functions (like strlen, stcmp, etc.) will not be available to you via BOF.

The converter runs inside your beacon. If the converter fails, you or your friend will lose access. Carefully write your oxygen.

Cobalt Strike expects your converters to be single-threaded programs that run for short periods of time. BOF will block other tasks and functions of the beacon. There is no converter pattern for asynchronous or long-running tasks. If you want to make long-term performance possible, consider a reflective DLL that runs inside the victim process.

How to create a converter?

It’s easy. Open a text editor and start writing a C program. Below is the Hello World program for BOF:

#include <windows.h> #include "beacon.h"
void go(char * args, int alen) {
BeaconPrintf(CALLBACK_OUTPUT, "Hello World: %s", args);
}

Download beacon.h.

To compile it using Visual Studio:

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

To compile it with x86 MinGW:

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

To compile it with x64 MinGW:

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

The above commands create the hello.o file. Use inline-execute in Beacon to run the converter.

маяк> inline-execute /путь/до/hello.o аргументи

beacon.h contains definitions for several internal Beacon APIs. The go function is similar to the main function in any other C program. It is a function that is called from inline-execute and arguments are passed to it. BeaconOutput is a Beacon API function to send output to the operator. There is nothing special about it.

Resolution of the dynamic function

GetProcAddress, LoadLibraryA, GetModuleHandle, and FreeLibrary are available in BOF files. You have the option of using them to resolve the Win32 API functions you want to call. Another option is to use dynamic feature resolution (DFR).

Dynamic function resolution is a convention of declaring and calling the Win32 API as a LIBRARY$ function. This agreement provides Beacon with the information it needs to explicitly allow a certain feature and provide access to your converter file before it runs. If this process fails, Cobalt Strike will refuse to run the converter and tell you which function it failed to remove.

Below is an example of a converter that uses DFR and gets 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 to dynamically resolve functions, pay special attention to the decorators added to the function declaration. Keywords like WINAPI and DECLSPEC_ IMPORT are very important. These decorators give the compiler the necessary hints to pass arguments and generate the correct instruction to call.

Aggressor scenario and converters

You’ll probably want to use Aggressor Script to run your final BOF implementations in Cobalt Strike. BOF is a good tool for implementing a lateral movement technique, a privilege escalation tool, or a new research opportunity. The &beacon_inline_execute function is the entry point of the aggressor script to run the Oxygen Converter file.

Here is the script to run a simple Hello World application:

alias hello {
local('$barch $handle $data $args');

# Визначення архітектури цієї сесії
$barch = барч ($1);

# читання з потрібного киснево-конвертерного файлу
$handle = openf(script_resource("привіт. $+ $barch $+ .o"));
$data	= readb($handle, -1);  closef($handle);

# Пакуємо наші аргументи
$args	= bof_pack($1, "zi", "Hello World", 1234);

# Оголошення про те, що ми робимо
btask($1, «Біг Hello BOF»);

# Страта
beacon_inline_execute($1, $data, "демо", $args);
}

First, the script defines the session architecture. x86 BOF will only work in an x86 beacon session. Conversely, x64 BOF will only work in an x64 beacon session. This script then reads the target BOF into the aggressor’s script variable. The next step is to package our arguments. The &bof_pack function  packs the arguments in a way that is compatible with Beacon’s internal data parser API. This script uses the normal &btask function to log actions that the user has entrusted to Beacon. A &beacon_inline_execute runs a BOF with its arguments.

The &beacon_inline_execute function takes the beacon ID as the first argument, a string containing the contents of the BOF as the second argument, the entry point as the third argument, and the packed arguments as the fourth argument. The option of choosing an entry point exists in case you decide to combine similar functions in one converter.

Below is the C program that corresponds to the above scenario:

/*
1.	Компіляція за допомогою:
1.	x86_64-w64-mingw32-gcc -c hello.c -o hello.x64.o
2.	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 feature is our entry point. Declare the datap structure on the stack. This is an empty and uninitialized structure with state information to receive arguments prepared with &bof_pack. BeaconDataParse initializes our parser. BeaconDataExtract extracts a binary block of specified length from our arguments. Our packing function has the ability to pack binary blocks as strings with a terminal null encoded in the default session character set, a string with a wide character and a terminal null, or a binary block without conversion. BeaconDataInt gets the integer packed into our arguments. BeaconPrintf is one way to format the output and provide it to the operator.

BOF C API  data analyzer

The data parser API receives arguments packed using the &bof_pack function of the aggressor script.

  • char * BeaconDataExtract (datap * parser, int * size) Gets a binary block with a specified length. size can be NULL.

If an address is specified, the size will be filled with the number of bytes removed.

  • int BeaconDataInt (datap *parser) Gets a four-byte integer.

  • int BeaconDataLength (datap * parser) Gets the amount of data that has yet to be parsed.

  • void BeaconDataParse (datap * parser, char * buffer, int size) Prepares the data parser to receive arguments from the specified buffer.

  • короткий BeaconDataShort (datap * parser) Gets a two-byte integer.

Output API

The output API returns Cobalt Strike.

  •  void BeaconPrintf (int type, char * fmt, …) Formats and outputs the Beacon statement.

  •  void BeaconOutput (int type, char * data, int len) Sends output data to the beacon operator.

Each of these functions takes a type argument. This type determines how Cobalt Strike will process the output and in what form it will be displayed. Types include:

  • CALLBACK_OUTPUT – this is standard output. Cobalt Strike converts this output to UTF-16 (internally) using the standard character set.

  • CALLBACK_OUTPUT_OEM – this is standard output. Cobalt Strike converts this output to UTF-16 (internally) using the OEM character set. You probably won’t need this unless you’re dealing with output from cmd.exe.

  • CALLBACK_ERROR є standard error message.

  • CALLBACK_OUTPUT_UTF8 – this is standard output. Cobalt Strike converts this output to UTF-16 (internally) from UTF-8.

Formatting API

The formatting API is used to create large or repetitive output.

  • void BeaconFormatAlloc (formatp * obj, int maxsz) Allocates memory for formatting complex or large output.

  • void BeaconFormatAppend (formatp * obj, char * data, int len) Appends data to this format object.

  • void BeaconFormatFree (formatp * obj) Frees a formatting object.

  • void BeaconFormatInt (formatp * obj, int val) Adds a four-byte integer (big end) to this object.

  • void BeaconFormatPrintf (formatp * obj, char * fmt, …) Appends a formatted string to this object.

  • void BeaconFormatReset (formatp * obj) Returns the format object to its default state (before reuse).

  • char * BeaconFormatToString (formatp * obj, int * size) Gets formatted data in a single string. Fill in the missing size variable with the length of this string. These parameters are suitable for use with the BeaconOutput function.

Internal APIs

The following functions control the token used in the current beacon context:

  • BOOL BeaconUseToken (HANDLE token) Uses the specified token as the token for the current Beacon stream. The new token will also be notified to the user. Returns boolean TRUE on success. FALSE on failure.

  • void BeaconRevertToken () Resets the token of the current thread. Use this function instead of calling RevertToSelf directly. This function clears other token state information.

  • BOOL BeaconIsAdmIn () Returns Boolean TRUE if the beacon has a high integrity context.

The following functions provide specific access to the beacon’s ability to be included in a process:

 void BeaconGetSpawnTo (BOOL x86, char * buffer, int length) Заповнює вказаний буфер x86 або x64 значенням spawnto, налаштованим для цього сеансу Beacon.

  • BOOL BeaconSpawnTemporaryProcess  (BOOL x86, BOOL ignoreToken, STARTUPINFO * sInfo, PROCESS_INFORMATION * pInfo)   This function spawns a transient process based on the ppid, spawnto, and blockdlls parameters. Grab PROCESS_INFORMATION to infiltrate or manipulate this process. Returns TRUE on success.

  • void BeaconInjectProcess (HANDLE hProc, int pid, char * payload, int payload_len, int payload_ offset, char * arg, int arg_len) This function injects the specified payload into an existing process. Use payload_offset to specify the offset within the payload for initial execution. The arg value is for arguments. arg can be NULL.

  • void BeaconInjectTemporaryProcess (PROCESS_INFORMATION * pInfo, char * payload, int payload_len, int payload_offset, char * arg, int arg_len) This function injects the specified payload into the temporary process your converter has chosen to run. Use payload_offset to specify the offset within the payload for initial execution. The arg value is for arguments. arg can be NULL.

  • void BeaconCleanupProcess (PROCESS_INFORMATION * pInfo) This function cleans up some handles that are often forgotten. Call it when you’re done interacting with process handles. You don’t need to wait for the process to exit or complete.

This function is useful:

  • BOOL toWideChar (char * src, wchar_t * dst, int max) Convert the string src to a wide character string in UTF16-LE format, Using the default encoding. max – the size (in bytes!) of the destination buffer.

Thanks to various open source guides.

Other related articles
Found an error?
If you find an error, take a screenshot and send it to the bot.