This repository contains the User-Defined C2 Beacon Object File Visual Studio (UDC2-VS) Solution. This project is designed to simplify the design, development and debugging of UDC2 BOFs built using the Beacon Object File Visual Studio template (BOF-VS).
Note: This repository assumes familiarity with BOF-VS. The BOF-VS project README contains information about the Dynamic Function Resolution (DFR) macros and helper functions used throughout this project.
A UDC2 channel requires two separate components:
A high level architecture of UDC2 is shown here:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ │ │ │ │ │
│ Beacon │ <----> │ UDC2 Server │ <----> │ Team Server │
│ [UDC2 BOF] │ │ │ │ │
│ │ │ │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
The Cobalt Strike client will stomp the UDC2 BOF into an exported payload. At runtime, Beacon will call the go() entry point of the UDC2 BOF and pass it a pointer to a UDC2_INFO structure. This structure allows the UDC2 BOF to tell Beacon where it can find the core UDC2 functions - udc2Proxy() and udc2Close(). Beacon will then call udc2Proxy() each time it needs to send and receive data, and finally udc2Close() when the Beacon exits.
The udc2-bof-vs solution's default example (udc2_bof.cpp) is a simple TCP channel implementation. It is only intended as a quick start guide to help UDC2 developers get up and running quickly and to understand the concepts behind sending and receiving frame data.
There are four important functions in this file:
Beacon sends and receives data in encrypted frames. A frame is simply a buffer of data that contains a 4-byte little endian packed length value followed by the raw encrypted buffer. When Beacon calls your udc2Proxy function, it will pass a complete frame to you in the sendBuf parameter. This outbound frame simply needs to be sent out over your protocol as is. When your UDC2 server gets the response from the Team Server, it will also be in frame format and needs to be written back via the recvBuf parameter of your udc2Proxy function in that format as well. Do not strip off the packed length value when writing the data back to the read buffer. Problems with frame data formatting are going to be the most likely single biggest source of failure when designing your UDC2 implementation, so always double check that first.
The UDC2 server acts as a relay between your UDC2 BOF and the Cobalt Strike Team Server. It is responsible for unwrapping Beacon frames from your chosen protocol and sending those directly to the Team Server. If you are creating an HTTP channel for example, your UDC2 server would function as your HTTP endpoint and handle HTTP requests from your UDC2 BOF.
To build a UDC2 server:
The Debug target builds the UDC2 BOF as an executable, which provides the convenience of debugging it directly within Visual Studio with its built-in debugger. This makes it possible to work at the source code level without running the BOF through a Beacon.
Note: The Debug build's Main() function is not designed to replicate the UDC2 server component. Instead, it retrieves a Beacon payload and runs it within the Debug executable to act as a shim between the Beacon payload and the UDC2 client. This makes it possible to properly simulate a live environment. Do not re-use functions that are part of this debugging setup as they are designed specifically for that scenario. Refer to the example code instead.
To start Debugging:
python3 tcp_udc2_server.py --bind-addr <addr to bind to> --bind-port <port to listen on> --ts-addr <team server addr> --ts-port <udc2 debug listener port>gUDC2Server and gUDC2ServerPort variables in udc2_bof.cpp to your UDC2 server bind address and portUDC2_DEBUG_HOST and UDC2_DEBUG_PORT variables in the debug main function (used to retrieve a debug payload from the UDC2 listener)The Release target builds the UDC2 BOF into an object file for use in Cobalt Strike. You can build both x86 and x64 versions of the BOF.
To start using your UDC2 BOF:
gUDC2Server and gUDC2ServerPort variables in udc2_bof.cpp to the address you will bind your UDC2 server to and the port you will useReleasepython3 tcp_udc2_server.py --bind-addr <addr to bind to> --bind-port <port to listen on> --ts-addr <team server addr> --ts-port <udc2 listener port> making sure that the bind address and bind port match what you set in step 1tasks_max_size malleable c2 profile setting and by default starts at 1 MB$ claude mcp add udc2-vs \
-- python -m otcore.mcp_server <graph>