Und3rf10w / external_c2_framework

Python api for usage with cobalt strike's External C2 specification
224 stars 95 forks source link

Add ability to support multiple, simultaneous clients #30

Closed Und3rf10w closed 6 years ago

Und3rf10w commented 6 years ago

This pull request adds the ability to support multiple simultaneous clients. Detailed development notes are available in #14.

image

Changelist

Detailed changes

For my own accounting purposes, and for the sanity of anyone else trying to work with this, the below section basically contains a more thorough and detailed breakdown of the changes mentioned in the overview above. This section is probably redundant, and likely only relevant if you intend on modifying the code. Readers seeking additional reference are encouraged to see #14.

Base logic modifications

A number of changes were added to the base logic of the client and server communication chain.

Most importantly, each client is now required to store their own desired options, as they will be communicated to the server. This allows us to support instances where a user desires to use different pipe names, architectures, and/or block timers.

Previously, the server would send a stager and wait for a client to acknowledge that they have received it. Now, the client will instead send an initialization message to the server, that contains the options used to generate the desired stager.

The server will now check for new clients via the transport independently of handling communication with each client.

Finally, a common dataframe was created that is used to pass information between the client and server.

Common Dataframe

To facilitate communication for multiple clients, a data frame was created that contains the client's id and data to be transferred within that frame. First, the "data" field of the frame is base64 encoded, then the entire frame is base64 encode to prevent issues in transit and with encoder.encode(). This base64 encoded string is then passed to encoder.encode().

The frame itself is simply a python list. The client ID MUST be the first element of that list, and the data MUST be the second element. Eventually, this will likely be converted into a dictionary, but preliminary tests have shown no issues with this approach.

Sending dataframe example

import base64

client_id = 1
data = "ayylmao"

data_frame = [client_id, base64.b64encode(data)]
preped_data_frame = (base64.b64encode(str(data_frame))

Receiving dataframe example

import base64
from ast import literal_eval

# `encoded_data_frame` is the data received from the transport after already being passed through `encoder.decode()`

decoded_df = base64.b64decode(encoded_data_frame)
raw_data_frame = literal_eval(decoded_df)
decoded_data_frame = [raw_data_frame[0], base64.b64decode(raw_data_frame[1])]
Und3rf10w commented 6 years ago

I'm gonna go ahead and merge this into dev. I'll make new branches from there to handle any issues that pop up.