Python framework for building and utlizing interfaces to transfer data between frameworks, with a specific focus on serving as an extension to Command and Control frameworks.
Currently, this is only intended as an implementation of Cobalt Strike's External C2 specification as described in this spec document, but is subject to change as the project matures.
This project consists of the following main parts:
- Manager (not yet implemented)
The builder reads in a config file, and uses configured options to generate a build by replacing
The builder can be used with
build_files.py. A sample builder configuration is provided as
skeletons are the different "skeletons" of code that the
builder will dynamically populate to generate a completely usable build.
markers that will be replaced with usable values by the
builder. There are three different 'types' of skeletons:
A marker can be placed inside any file in a
skeleton, which will be replaced with a value specified in the builder config. In best practice, markers should never be used to directly write variables, and should only ever be used to set values. If a marker's value has to be reused, one should opt to store the value in a variable and reference it that way, instead of reusing the same marker.
The marker format is:
Strings will be written into a skeleton directly wrapped in single quotes, and numbers will be written as is.
In the event a string in the config is wrapped in double quotes, the string will be written directly to the file wrapped in double quotes instead, and the wrapping single quotes will be stripped.
This relationship can demonstrated as:
################# # Skeleton Code # ################# # Skeleton contains the following line of code: foo = ```[var:::bar]``` ############## # End Result # ############## # Stored in config as: # foo = bar # Written as: foo = 'bar' # Stored in config as: # foo = "bar" # Written as: foo = "bar" # Stored in config as: # foo = 2 # Written as: foo = 2 # Stored in config as: # foo = "2" # Written as: foo = "2"
Frameworks are the base application that determines what data is being used by the
encoder, and how that data is used. What a specific
framework actually does doesn't really matter, so long as logic exists to import and use the
transport. Most of the essential portions of a framework (primarily
client logic) will be stored as a
skeleton, with an interface to interact with the server portion of it being stored as a base
Generally, a framework is contains a
client, and makes use of
transports to relay data between them.
There are few fundamentals to consider when building a
frameworkis responsible for ensuring that the
encoderis made available to the
transportto be used.
- If the
frameworkuses a client-server relationship, they should be appropriately organized as such.
- Understand that in a majority of cases, the end-user will never directly interact with a framework's
client, so if you want things to be reconfigurable on the
client, it needs to be able to do that during runtime with no direct interaction.
- There should be little need for creating a
skeletonbecause the end-user is going to be directly interacting with a framework's
server. Instead, opt to both read in options from a configuration, and give the end-user the ability to modify options (such as a block timer or vebosity) during runtime.
frameworkskeleton will be processed by the builder, iterating through every file in it, so if a certain argument needs to be configurable at build time, it can be easily done.
servershould be able to be interfaced by a common
The server is the application that brokers communication between the
client and the
c2 server. The server logic is primarily static. The logic for the server for the
cobalt_strike framework, referred to as
third-party Client Controller within the spec, is shown below:
- 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
- Transport the stager with the
- Await for a metadata response from the client received via the
- Decode the metadata with the
- 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
- Receive for a response from the client received via the
- Decode the response via the
- Relay the response to the c2 server.
- Repeat steps 11-16
server should support the ability to handle multiple clients (not yet implemented), and be interfaced by a
The client is essentially the payload that runs on the endpoint. The logic of the client for the
cobalt_strike framework is primarily static, and shown below:
- Run any preparations need to be utilizing the
- 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
- Watch the
transportfor new tasks
- Relay new tasks to the beacon
- Relay responses from the beacon via the
- Repeat steps 6-8.
The client makes use of the specified
transport to relay data between itself and its respective
Encoders recieve data, then modify that data to either prepare it for use to be sent via the transport, or decode data recieved via the transport back into its raw form to be interpreted by whatever
framework component is utilizing it.
Encoders should expect to be interfaced directly by the transport, and handle data in a framework and component agnostic manner.
Transports serve the role of sending and receiving data through a communication channel, and interfacing with the encoder to ensure that data is transformed to whatever format is nesessary. Transports should expect to recieve data from a framework component, or via the communication channel, and have the ability to relay data through the communication channel. Transports are responsible for calling the
encoder to encode or decode data as nesessary.
Transports should expect to be interfaced directly by the
framework component, and handle data in a framework and component agnostic manner.
How to use this
First, determine which transport and encoding module you'd like to use. We'll use
encoder_lsbjpgfor the following example.
Next, create a
builder_config.configto suit your needs, refer to the provided sample config and template for direction on how to do this.
Generate a build with
build_files.py. As an example, one would generate a build in the
transport_imgurfor Cobalt Strike, verbosely, with the following command:
python build_files.py -b builds -f cobalt_strike -c sample_builder_config.config.sample -e encoder_lsbjpg -t transport_imgur -v
- Next, start running your built server and distribute your client.
On the machine running the server, execute:
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 Cobalt Strike 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.
How do I compile the client into an executable I can distribute?:
I've tested this successfully in Kali, to recreate my environment, just ensure you have veil-evasion installed and that you ran through its setup. It should have setup a wine environment with python installed that has all of the dependencies you need. You MAY have to install the
pefile module into this environment as well.
Then, you can go to the directory for the client you want to generate an executable for and run:
chmod +x compile_dll.sh ./compile_dll.sh wine "C:\\Python27\\python.exe" /usr/share/veil/pyinstaller/pyinstaller.py -F -r c2file.dll -w --key "ayyyyyyylmao" client.py
Replace the value for
key with whatever you want. You should see the client executable in the
dist/ directory. If you want to generate an executable that provides a console that you can use for debugging, compile the executable with
wine "C:\\Python27\\python.exe" /usr/share/veil/pyinstaller/pyinstaller.py -F -r c2file.dll -c client.py