-
Notifications
You must be signed in to change notification settings - Fork 275
C2 Framework Integration
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: FIGURE1
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.
namespace MWR::C3::Interfaces::Connectors
{
/// A class representing communication with Covenant.
struct Covenant : Connector<Covenant>
{
/// Public constructor.
/// @param arguments factory arguments.
Covenant(ByteView arguments);
<...Snipped...>
}
<...Snipped...>
}
MWR::C3::Interfaces::Connectors::Covenant::Covenant(ByteView arguments)
{
<...Snipped...>
}
MWR::ByteVector MWR::C3::Interfaces::Connectors::Covenant::GeneratePayload(ByteView binderId, std::string pipename, uint32_t delay, uint32_t jitter, uint32_t connectAttempts)
{
<...Snipped...>
}
MWR::ByteView MWR::C3::Interfaces::Connectors::Covenant::GetCapability()
{
<...Snipped...>
}
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.
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).
This is achieved through the code below:
MWR::ByteView MWR::C3::Interfaces::Connectors::Covenant::GetCapability()
{
return R"(
{
"create":
{
"arguments":
[
{
"type": "uint16",
"name": "C2BridgePort",
"min": 2,
"defaultValue": 8000,
"randomize": true,
"description": "The port for the C2Bridge Listener if it doesn't already exist."
},
{
"type": "string",
"name": "Covenant Web Host",
"min": 1,
"defaultValue": "https://127.0.0.1:7443/",
"description": "Host for Covenant - eg https://127.0.0.1:7443/"
},
{
"type": "string",
"name": "Username",
"min": 1,
"description": "Username to authenticate"
},
{
"type": "string",
"name": "Password",
"min": 1,
"description": "Password to authenticate"
}
]
},
"commands":
[
{
"name": "Close connection",
"description": "Close socket connection with TeamServer if beacon is not available",
"id": 1,
"arguments":
[
{
"name": "Route Id",
"min": 1,
"description": "Id associated to beacon"
}
]
}
]
}
)";
}