In this blog post, we are taking a deeper dive into Covenant. Covenant is one of the latest and greatest Command and Control (C2) Post Exploitation Frameworks which I covered in In my previous blog post. In that post, we discussed Covenant on a high level but now let’s go through the process of configuring and using Covenant to execute payloads on compromised hosts.
NOTE: This post demonstrates the capabilities of Covenant in Mid-September 2019.
Getting Setup and Starting Covenant
To start using Covenant, we’ll want to make sure we prep a machine for the Covenant application itself. This host functions as the Covenant database and the management application that controls the agents of the application aka “Grunts”.
Today, we’ll be installing Covenant on a Windows 10 Workstation, but any recent Windows Server OS or Linux Distributions that support .NET Core should do just fine as well. Given the cross-platform modern architecture design of Covenant, it purposefully does not provide an old-fashioned installer executable. To Install Covenant, we’ll first need to clone the project git repository, build, and then run the application. Note: Alternatively we can also run Covenant in a Docker container.
To build and run our instance of Covenant, we’ll need to ensure the following prerequisites are installed on the machine hosting Covenant:
Once we have the prerequisites installed, we can take the following steps to run Covenant:
git clone --recurse-submodules https://github.com/cobbr/Covenant cd Covenant/Covenant dotnet build dotnet run
These steps pull the latest code source, Build the Application, and finally RUN the Application.
At this point, the Covenant service should be up and running. Now we can browse to the Covenant application interface on its default web port of 7443.
Configuration of Covenant
If this is your first time running Covenant, you are prompted to enter a new username and password. Covenant has user authentication, and role-based permissions built right into the application. We can consider this a first-time set up to define the administrative account you wish to use for the application.
When you have finished specifying an administrative username and password, which effectively completes your first-time covenant account registration and redirects to the User Management page.
At this point, Covenant is up and running on our machine, we have created a new Administrative user, and we’ve logged into the application. Now it is time to start using Covenant!
Now that we have logged into the application – we are presented with a variety of pages; we can navigate to on the left side of the application to view the various functions and features of Covenant. Before we get ahead of ourselves, let’s visit the dashboard page.
Looks pretty empty! Well, this is because we have additional setup and configuration to do. We’ll need to ensure our Listener is configured so that we can set up our external agents (grunts) to communicate with Covenant.
Listeners are the point of contact between Grunts and the Covenant Application itself. Our first step is to configure a listener which exposes an endpoint. This endpoint is used in conjunction with a “Launcher.” These two components provide us with everything we need to connect a Grunt to Covenant.
By default, there are no listeners configured out of the box. Let’s set up a new listener by clicking the Create button on the Listener Page. Clicking the Create button opens the Create HTTP Listener prompt which allows for the creation and Configuration of a New HTTP Listener.
The Create HTTP LIsener prompt allows us to define our Listener configuration; we’ll need to update a few of the values provided to ensure our Grunts can communicate with Covenant. Firstly, you can opt to change the Name value to something more readable if you wish. By default, Covenant creates many of its objects with a simple “GUID” type name so you may want to rename these objects for convenience sake. The most crucial element on this prompt is the “BindAdress” and “Connect” controls. These likely need to be changed to an address that is reachable by the Grunts. SSL can also be configured for the listener by specifying an SSL Certificate file and password. Once we have completed our configuration, we can once again click the “Create” button to create and enable the listener.
Now my Listener is configured and ready to be paired with a Launcher!
The Launchers page is used to generate, host, and download binaries, scripts, and other methods that provide us with the tools to turn remote hosts into Grunts. These tools typically come in the form a payload package that we deliver and run on a remote host. The launcher has logic to establish a connection with Covenant, which registers the Grunt and finally allows us to execute tasks against them.
Upon browsing to the Launchers page, a list of Launchers is presented. Each launcher is named based on the way that launcher is intended to be executed. For Example, the “PowerShell” Launcher is a PowerShell script that has all the components inside a PowerShell script necessary to set up the Grunt and establish communication with Covenant from the specified Listener. The “Binary” Launcher is simply an executable to establish a connection from the grunt to covenant through the listener as well.
For our first launcher let’s keep it simple and select Binary.
The Binary Launcher is used to generate custom binaries and is one of the easier Launchers to use. Once we click on the Binary Launcher, the Binary Launcher configuration opens.
For any launcher, the first option presented is to specify the Listener. Let’s use the HTTP listener we created earlier!
Next, we need to specify the Template; in our example, we will use the “out of box” GruntHTTP Template.
Now we have a variety of settings to allow us to customize the behavior of our Launcher and how the grunt communicates with Covenant.
Typically these settings do not need to be modified, especially if we are just getting started, but let’s review some of the more common configurations:
Delay and Jitter control how often a Grunt communicates with Covenant
ConnectAttempts and KillDate control how long and when a Grunt with stop attempting to communicate with Covenant.
Once we have configured how we would like the launcher to function, we can click the Generate button to have Covenant compile a new launcher for us utilizing our settings. Once Generated we can click: Download and the browser downloads the payload.
Now that we have our Launcher, for the sake of demonstration, we’ll upload the launcher over to a Remote Host. There are many creative ways to deliver this payload, but for this blog post, we’ll stick to our simple “lab” example.
In my lab, I have copied the Launcher to the desktop of my host, and execute the Launcher.
Assuming the Grunt Launcher can communicate with Covenant we should have a new registered Grunt appear on our Dashboard in Covenant.
Now that we have an Active Grunt – we can start assigning our grunt tasks!
Once we have a confirmed that we have registered a Grunt, we can get to work. From the Covenant dashboard or the Grunts page, we can access a page containing status and controls for the Grunt that we have just registered.
The Grunt details page provides information and controls that show the connection status and functionality of the specified Grunt. This page also allows us to execute taskings to our Grunt or view previously executed tasks on the Taskings tab.
Let’s click on the Task tab and find a task to run on our new Grunt.
For our example, let’s run a screenshot task – this instructs the grunt to take a screenshot of the active users’ desktop. Here I have selected the “Screenshot” task from the Task dropdown and have chosen to execute the task which immediately queues the selected Task:
At this point, the Grunt has now been instructed to execute a task.
Now I can refresh the page to see the updated task status. When I refresh, I see my task has completed, and I have been provided with an output – which for this task is simply a screenshot. Note that the Output for a task varies depending on the type of task.
This illustrates that we have successfully executed a task on a Grunt – using a well designed C2 Framework.
For the sake of example, let’s run another task; in this case, let’s run Mimikatz using covenant.
To run another task we only need to navigate back to the Grunts Details Page and open the Tasks tab once again.
This time, let’s queue a new task and let’s select Mimikatz. Many tasks have their own sets of customizable input parameters. In this case, the Mimikatz tasks have a “Command” input parameter which allows me to specify which Mimikatz command I would like to execute.
Conveniently it has already been populated with the “sekurlsa::logonPasswords” command. This command instructs mimikatz to discover passwords stored in memory. Let’s assign this task to our grunt and see what happens…
It just so happens I had a Domain Admin logged into this Grunt host and now right in the output of my Mimikatz command, I was able to see the Domain Admins password in clear text!
Using Covenant to execute mimikatz has to be one of the fastest and easiest ways to run mimikatz. This is all possible because Covenant DOES NOTstore tasks directly on our Grunt host. Instead, Covenant dynamically compiles and deploys the task payload over its client connection at the time of tasking. Because of the dynamic compiling of tasks, this can make it very difficult to detect. Every time a new Grunt is generated or a new task is assigned, the relevant code is recompiled and obfuscated with ConfuserEx, avoiding static payloads. To further illustrate this point, during my demo Windows DEFENDER is enabled and up-to-date, even so, I can task my Grunt with mimikatz tasks – and they execute without issue.
Task and Data History
One of the great features of Covenant is how it organizes the results and history of my various taskings. Now that I have executed a few tasks – I can visits the “Taskings” page to get an overview of the very tasks I have executed.
Here we have a history of our mimikatz task and the screenshot task we ran earlier.
Clicking on one of the rows allows you to view to full output and parameters of previous takings, very convenient!
While we can view the history of Taskings, many tasks also save elements of their output to the “Data” page which is specifically designed to display certain types of information such as credentials, indicators, downloaded artifacts, and screenshots.
If I now navigate to my history page, a summary of captured information is provided such as the various credentials we discovered with our Mimikatz tasking and even the screenshots we captured.
Now, let’s run one more task on our Grunt to download a file so we can view it on the Data \ Downloads tab.
On my Grunt Host, I have discovered via other tasks that there is a “MySecretDocument.txt” file on that machine. Let’s use the Download task to obtain a copy of this file for ourselves!
Running the download task will output the retrieved file and make it immediately available on the Data \ Downloads tab. After executing this task, I see that I now have a copy of this file:
This task is simple in itself but has some fascinating use cases given the fact that we can run this task using the rights of the user who executed the Grunt Launcher or credentials I have gained via other tasks.
Covenant has a large number of tasks that is growing every day with the Open Source Community involvement that cannot be covered in a single blog post. This project will very likely have an exciting future and is certainly worth having a look!
Lee is a Technical Product Manager at STEALTHbits technologies. When Lee is not building Cybersecurity Software in his day-job, He is active in managing and presenting at user groups, meetups, and conferences around the world. Lee is focused on Automation, Security, Monitoring, and IoT.