Covenant is a freely open source C2 framework developed in .NET. Covenant is a collaborative framework that demonstrates modern capabilities of .NET offensive tooling.
There are a number of commonalities between C2 frameworks that should be considered prior to integration with C3:
* Payload generation – all frameworks have some manual or automated way of generating payloads.
* Staging – the majority of frameworks provide access to staged and stageless payloads.
* Implant tracking – any framework that expects to handle multiple implants running across several compromised system must internally track those implants.
In Covenant’s case:
* Payload generation – Grunts (implants) can be generated from the Web UI or the Web API.
* Staging – payloads have 3 stages before a fully established C2 channel is setup.
* Implant tracking – Grunt’s are set with a unique GUID that is sent in any communication for internal tracking.
The second consideration to make is the ability for the given framework to accept connections and receive implant data. Cobalt Strike for example has the ExternalC2 specification. For Covenant the “Bridge Listener” provides similar functionality.
Finally, for ease of integration it helps if the chosen framework provides access to implants that communicate with each other over internal communication channels. Specifically, C3 expects the implants it runs in memory to create a named pipe for which data is read from and written to.
At a high level, the objective of integration would result in the communication flow described in the figure below:
The first step for integrating Covenant involves creating the new source files within C3. Initially this is performed by copying the the code from `/src/Common/MWR/C3/Interfaces/Connectors/TeamServer.cpp` to `Covenant.cpp`.
After replacing references to TeamServer, we end up with a number of methods that we simply need to redesign to work with Covenant. Highlighted below are the main functions which require alteration.
```c++
namespace MWR::C3::Interfaces::Connectors
{
/// A class representing communication with Covenant.
It is also worth noting that Covenant provides access to a RESTful API, of which the following methods will aid in the integration of this framework:
*`/api/users/login`– a POST request that provides us with a JWT token for use in subsequent requests.
*`/listener/createbridge`– not part of the API, but allows the client to create a C2Bridge listener.
*`/api/launchers/binary`– PUT and GET requests are used to populate a Grunt template and then retrieve the Base64 encoded shellcode respectively.
## Setting up a Listener
With the relevant code in place, the next stage involves working towards payload generation. There are multiple steps required to achieve this goal. First and foremost, Covenant requires an active listener prior to allowing a payload to be generated.
**Stage 1 - Getting User Input**
The first function to be implemented is `GetCapability`, this is the form that users interact with when the create a new Covenant connector. Covenant's API requires authentication, as such we will need the user to specify at minimum a username, password, the location of the Covenant instance, and a port for the C2Bridge (to be discussed shortly).
Once this form is submitted the only thing we need to know is that the connector's constructor is called and passed a `ByteView` containing the user supplied arguments. As well as adding the form above, we also made sure to create member variables for the Covenant connector object that will store the user supplied arguments. As such, when the constructor is called we are able to retrieve and store the arguments by simply doing:
If you wanted to test this, you could simply add a print statement below this line and view the output in the Gateway's console.
**Stage 3 - Authenticate to Covenant**
Stage 3 involves actually authenticate with the Covenant API so that we can subsequently add a new listener. For those not familiar with the CPP Rest SDK that is used within C3, it is strongly advised that code snippets are taken from the Slack channel.
The code below demonstrates how we authenticate to Covenant within the constructor. At a high level we:
1. Create the JSON object containing credentials
2. Initialise a `http_client` that ignores certificate errors and is set to make a request to the user supplied host for `/api/users/login`
3. Make the request and if successful, retrieve the authentication token.
throw std::exception(OBF("[Covenant] Could not get token, invalid logon"));
```
**Stage 4 - Create the Listener**
With a valid token it is now possible to create the `C2BridgeListener` in Covenant. The logic implemented in C3 for this is as follows:
1. Check if the listener we want is already setup
2. If not, create it
The first step is handled by `UpdateListenerId`, which won't be described in depth. Simply put, this method checks if a listener named "C3Bridge" exists and if so, retrieves the `connectAddress`, `connectPort` and most importantly the `id`.
The second step involves parsing the user supplied data and then fudging a request to Covenant as `/listener/createbridge` is not technically part of the RESTful API. The high level description of listener creation is:
1. Extract the IP address of Covenant
2. Create a URL encoded POST request specifying the host and port to setup the listener
3. Execute the POST request and make a final call to `UpdateListenerId` in order to get the listener ID.
```c++
<...Followingonfrompreviouscodesnippet...>
//Get the token to be used for all other requests.
The final 2 lines of the Covenant connector's constructor may cause confusion. The listener that is created does not expect direct connections from Grunts. Instead, the C2Bridge project is meant to be used as a middle man. This is demonstrated in the Video here https://www.youtube.com/watch?v=0TVFBy_QXaI&feature=youtu.be.
The arguments (eg. `pipeame`, `delay`, `jitter`) are provided by the user from the `AddPeripheralX` command, where X could be `Beacon` or `Grunt`. As we have yet to add a Grunt Peripheral to C3 at this stage, it is simpler to add a call to `GeneratePayload` at the end of the constructor. Once the Grunt Peripheral code is added we can remove this call and simply use the Web form.
For payload generation we again turn to Covenant's API. The logic is simple:
1. Take user supplied arguments, such as the name of the SMB pipe to create.
2. Make a PUT request to generate a GruntSMB template.
3. Make a POST request to get the Base64 encoded .NET assembly in binary format.
4. Create a socket connection to the C2Bridge port.
5. Return the payload.
It is worth noting that we do not need to know where the payload is being returned to, such information is part of C3's core code, and we wanted to make extensions to C3 as easy as possible. All the developer needs to do is make sure the payload is correct, and it will make it's way to the correct Relay.
As described, when testing the code above we cheated by adding a call the `GeneratePayload` in the connector's constructor. What we actually need to do is add a Peripheral called `Grunt` to C3. In similar fashion to how the Covenant.cpp connector code was developed, we start by copying `/Src/Common/MWR/C3/Interfaces/Peripherals/Beacon.cpp` to `Src/Common/MWR/C3/Interfaces/Peripherals/Grunt.cpp` - the same is done for the header files.
**Stage 1 - Getting User Input**
In order to get user input that is passed to the Covenant connector `GeneratePayload` method, we need to edit the Grunt Peripheral's `GetCapability` function:
"description": "Name of the pipe Beacon uses for communication."
},
{
"type": "int32",
"min": 1,
"defaultValue" : 30,
"name": "Delay",
"description": "Delay"
},
{
"type": "int32",
"min": 0,
"defaultValue" : 30,
"name": "Jitter",
"description": "Jitter"
},
{
"type": "int32",
"min": 10,
"defaultValue" : 30,
"name": "Connect Attempts",
"description": "Number of attempts to connect to SMB Pipe"
}
]
},
"commands": []
}
)";
}
```
**Stage 2 - Grunt Execution**
As Covenant is a .NET framework, this process is far more involved than what occurs with CobaltStrike's beacons. For brevity only key points will be described.
First and foremost, when the user submits the form above and a payload is generated, this is eventually passed to the Relay through whatever channel it is communicating over, which in-turn calls the appropriate peripheral's constructor. The flow shown below may describe this best:
1. User submits AddPeripheralGrunt form from a Relay command centre
3. C3 Gateway sends generated payload to the relay over Slack/O365 or whatever channel has been setup
4. Relay calls the constructor defined in Grunt.cpp.
The constructor for a Grunt is slightly complex, essentially we execute the .NET assembly in our own CLR instance running in a seperate thread. Note the `arguments.Read` call below, these are passed in the same order they are presented in the web form.
The final stage of this integration involves making a change as to how the C3 Relay communicates with the SMB Grunt. Simply put, C3 needs to match how the C2 framework it is communicating with reads and writes data. For example, the SMB Grunt writes data as such:
In order for the C3 Relay to read this data, we must alter the named pipe communications. This requires creating a custom named pipe communication specific to Covenant. Essentially, we create a new read method `ReadCov` in `/Src/Common/MWR/WinTools/Pipe.cpp` as part of the `AlternatingPipe` class. This method reflects how an SMB Grunt writes data.
Similarly, the Grunt Peripheral's `OnCommandFromConnector` method is altered to call `m_Pipe->WriteCov();`, which in-turn reflects how an SMB Grunt reads data.
## Final Points
This is all that is required to integrate Covenant into C3. Of course there were changes that needed to be made to Covenant, in total this was roughly 5 or 6 lines worth of changes. Other C2 frameworks may need more or less work.
The overall point that we want to make with the walkthrough is that there is no requirement to read through C3's core code, or even have knowledge of the functionality. C3 is designed to be easy to extend, and hopefully this is demonstrated here.