Python framework for usage with Cobalt Strike's External C2 specification as described in the spec.
The primary design goal is to be a very modular implementation of the external c2 spec that provides enough abstraction to easily implement C2 channels for Cobalt Strike. Ideally, all a user would have to do is create a transport
module, an encoder
module, and populate a configuration file to implement a new channel.
This project consists of three main parts:
- Builder (not yet implemented)
- Client
- Server
The builder dynamically builds client and server deployments based on the specified configuration. Ideally, the client would be able to be distributed as a single compiled file such as a dll or exe.
The client is essentially the payload that runs on the endpoint, referred to as third-party client
within the spec. The logic of the client is primarily static:
- Run any preparations need to be utilizing the
transport
- Receive the stager
- Inject the stager and open the handle to the beacon
- Obtain metadata from the beacon
- Relay the metadata from the beacon to the C2 server via the
transport
- Watch the
transport
for new tasks - Relay new tasks to the beacon
- Relay responses from the beacon via the
transport
- Repeat steps 6-8.
Configurations needed for the transport and encoding mechanisms are statically copied into the client. Function logic for transporting and encoding mechanisms are also statically copied into from their respective modules.
Process injection logic is determined from the builder.
The server is the application that brokers communication between the client
and the c2 server
, referred to as third-party Client Controller
within the spec. The server logic is primarily static, but supports verbose and debug output to assist with development:
- Parse the configuration
- Import the specified encoding module
- Import the specified transport module
- Establish a connection to the c2 server
- Request a stager from the c2 server
- Encode the stager with the
encoder
module - Transport the stager with the
transport
module - Await for a metadata response from the client received via the
transport
- Decode the metadata with the
encoder
module - Relay the metadata to the c2 server.
- Receive a new task from the c2 server.
- Encode the new task
- Relay the new task to the client via the
transport
- Receive for a response from the client received via the
transport
- Decode the response via the
encoder
module - Relay the response to the c2 server.
- Repeat steps 11-16
The determination of which encoder
and transport
module the server imports is determined from the values stored in config.py.
No imports of ununsed transport
or encoder
modules are performed.
The following tables describe shared functions between the encoding
and transport
modules, and the client. Shared functions are essentially the exact same code.
A VERY IMPORTANT NOTE: The data send to the client's sendData
and recvData
functions should be raw data, where data send to the transport module's sendData
and retrieveData
functions should already be encoded or decoded as needed.
Transport Function | Client Function | Description |
---|---|---|
prepTransport | prepTransport | Performs any preconfigurations required to utilize the transport mechanism |
sendData | sendData | Defines how data is sent through the transport mechanism |
retrieveData | recvData | Defines how data is received through the transport mechanism |
Encoder Function | Client Function | Description |
---|---|---|
encode | encode | Defines modifications done to raw data to prepare it for transport |
decode | decode | Defines modifications done to raw data received from the transport to be relayed to its destination |
First, determine which transport and encoding module you'd like to use. We'll use transport_gmail
and encoder_b64url
for the following example.
Next, modify server/config.py
to suit your needs, ensuring the ENCODER_MODULE
and TRANSPORT_MODULE
are properly configured and pointed to your desired modules:
EXTERNAL_C2_ADDR = "127.0.0.1"
EXTERNAL_C2_PORT = "2222"
C2_PIPE_NAME = "foobar"
C2_BLOCK_TIME = 100
C2_ARCH = "x86"
IDLE_TIME = 5
ENCODER_MODULE = "encoder_b64url"
TRANSPORT_MODULE = "transport_gmail"
verbose = False
debug = False
Next, modify the configuration section for your selected transport
and encoder
module.
Ensure that client/mechanism/$mechanism_client.py
's configuration section matches with any configurations you have defined thus far.
On the machine running the server, execute:
python server.py
For more verbose output, you may run:
python server.py -v
For more verbose output and additional output that is useful for debugging, you may run:
python server.py -d
Next, execute the client on the targeted endpoint.
If everything worked, a new beacon will be registered within the Cobalt Strike console that you may interact with.
Why would you write this?: There weren't very many released implementation of the spec, and of the ones that are released, they either are not in a language that I am familiar with or do not have the modularity and abstraction that I was seeking.
Why Python 2?: I'm lazy and it's easy to implement new transport and encoding channels in it.
Your code sucks: That's not a question.
Can I submit new transport and/or encoder modules?: Yes please! Submit a pull request and I would be happy to review.
-
Similar abstraction and modularity will be implemented in the client component as well, to support different methods of process injection for the beacon payload and other features on the roadmap.
-
Currently, this is missing the builder functionality, which is planned to dynamically build client and server deployments, but it is on the roadmap.