Covenant [1] is an open source .NET command and control framework to support Red Team operations, similar in many ways to the well-known Cobalt Strike threat emulation software. Covenant is an ASP.NET Core, cross-platform application that includes a web-based interface that allows for multi-user collaboration. It has two main agents/payloads:
Grunt
, which is written in .NET Framework (Windows)Brute
, which is written in .NET Core (cross-platform)Regarding its main agent, the Grunt, which we will be focusing on in this post, it has multiple capabilities to make the use of offensive .NET tradecraft easier. This includes interesting features such as dynamic C# compilation or inline C# execution, and numerous tasks for local or domain enumeration, lateral movement facilities, persistence and more.
At NCC Group have been using and enjoying this framework for some time now, and we agreed that it would be a good idea to contribute our two cents to this fantastic project through contribution of a number of features we missed while using it.
This post explains additions we made to the Covenant and SharpSploit [2] (Covenant’s library) projects along with some small proof of concept examples.
Our additions to the project were:
Event Tracing for Windows (ETW) [3] provides application programmers the ability to start and stop event tracing sessions, instrument an application to provide trace events, and consume trace events.
Even if ETW was created for debugging and performance monitoring and not as a security feature, blue teamers had started using ETW to detect malicious execution of .NET assemblies within a process, among other things. In-memory execution of .NET assemblies is a well-known technique nowadays leveraged by attackers during post-exploitation phases (using functions such as Beacon’s execute-assembly).
Adam Chester [4] explains clearly how it is possible to disable the ETW monitoring feature by patching the ntdll.dll
function EtwEventWrite
in his post “Hiding your .NET – ETW” [5].
A new PatchEtw
function was added to SharpSploit (https://github.com/cobbr/SharpSploit/pull/63), that patches EtwEventWrite
to block its logging capabilities (as explained in Adam Chester’s blog post). This implementation has been adapted from the code published on Adam’s blog post, and from Mythic’s [6] implementation for 64-bit processes.
The following screenshot shows the assemblies loaded by an arbitrary Grunt (without obfuscation) when executing an in-memory instance of SharpHound [7].
When using the new DisableETW
Covenant Task, no information is given about .NET assemblies within the process. This can be seen below.
SharpSploit had the ability to dump processes via the Win32 function MiniDumpWriteDump
[8] (e.g. https://github.com/cobbr/SharpSploit/blob/ec3e7999502c51e481915fd1f4b028d8c0ff3d5c/SharpSploit/Enumeration/Host.cs#L262). However, Covenant only leveraged this function in its SafetyKatz
Task, where the memory of lsass.exe is dumped and the resulting file is parsed on the spot with Mimikatz to extract credentials.
Sometimes it is preferable to simply dump the memory of the process and perform the parsing offline, to avoid possible IOCs that may be generated by the execution of Mimikatz on the target system. For this reason, we thought it was a good idea to make a Task that simply dumped processes with the functionality that was already implemented in SharpSploit. This Task is now available in Covenant under the name MiniDumpWriteDump
.
On the other hand, thanks to the significant progress that has been made over the years in terms of security solutions (e.g. EDRs), it is widely known that lsass.exe is precisely one of the most critical assets that should be protected. One of many ways that exist to circumvent certain checks performed by these security solutions is to create a Snapshot (Win32 PssCaptureSnapshot
[9]) of lsass.exe and dump its memory instead of the original process. This technique is very well explained by B4rtik (@b4rtik) and Matteo Malvica (@uf0o) in the post “Evading WinDefender ATP credential-theft: a hit after a hit-and-miss start” [10]
The previous technique has been implemented in Covenant under the name SnapshotDump
.
A new CreateProcessSnapDump
function was implemented in SharpSploit. This function Creates a snapshot of the targeted process using PssCaptureSnapshot
(Windows 8.1+ and Windows Server 2012+) and dumps it using MiniDumpWriteDump
. The snapshot is cleaned after the dump.
You can view the Pull Request here (https://github.com/cobbr/SharpSploit/pull/65)
Two new tasks were implemented within Covenant:
MiniDumpWriteDump
Win32 API call through PInvoke through SharpSploit.Enumeration.Host.CreateProcessDump
just as the SafetyKatz Task but without any parsing.PssCaptureSnapshot
(Windows 8.1+ and Windows Server 2012+) and dumps it using MiniDumpWriteDump
. The snapshot is cleaned after the dump. Done through the new SharpSploit.Enumeration.Host.CreateProcessSnapDump
function.You can view the Pull Request here (https://github.com/cobbr/Covenant/pull/251)
An example of these Tasks is shown below.
The resulting files can be parsed with Pypykatz [11] (or Mimikatz [12]) as shown below.
Process Injection [13] is one of the most common techniques used by adversaries to evade process-based defences as well as elevate privileges. Process injection is a method of executing arbitrary code in the address space of a live process.
While SharpSploit had a great deal of process injection facilities thanks to the great work of TheWover (@TheRealWover) and Ruben Boonen (@fuzzysec), Covenant only offered limited functionality through the Shellcode
Task. For this reason we decided to create a number of process injection tasks that would fully leverage SharpSploit offerings, as well as adding some interesting features to the mix such as Parent Process spoofing [14] (PPID Spoofing).
SharpSploit signatures, structs and functions for CreateProcess
[15] were introduced to SharpSploit in order to support features such as PPID spoofing or creating processes with certain characteristics or flags. This can be seen in the following Pull Request here (https://github.com/cobbr/SharpSploit/pull/64).
Based on the above addition, we created three new Tasks within Covenant:
SectionMapAlloc
allocation technique to copy the payload into the user-specified PID and executes it using any of the available options in the RemoteThreadCreate
execution technique (NtCreateThreadEx
, RtlCreateUserThread
, CreateRemoteThread
).SectionMapAlloc
allocation technique to copy the payload into the current process and executes it using a delegate.SectionMapAlloc
allocation technique to copy the payload. The payload is then executed using any of the available options in the RemoteThreadCreate
execution technique (NtCreateThreadEx
, RtlCreateUserThread
, CreateRemoteThread
). Supports Parent Process Spoofing (PPID) and the BlockDLLs [16] attribute.These tasks can be seen in the following Pull Request here (https://github.com/cobbr/Covenant/pull/253).
An example is shown below where a new Grunt is executed within an iexplorer.exe process with the BlockDLLs attribute:
Based on Ryan Cobb’s review of the Pull Request, he finally decided to implement a more generic process injection Task without any new additions to SharpSploit. As a result, the aforementioned Task does not support PPID spoofing or process creations flags.
Adversaries may use execution guardrails [17] to constrain execution or actions based on adversary supplied and environment specific conditions that are expected to be present on the target. Guardrails ensure that a payload only executes against an intended target and reduces collateral damage from an adversary’s campaign.
Covenant did not have any method to set up guardrails on the generated payloads. Having the ability to configure such features in the framework helps operators save time and focus on other aspects of an assessment. For this reason we felt it was interesting to implement a simple yet effective way to perform environment-variable-based checks on the victim before executing the agent.
Environment variable checks were implemented within Covenant for the creation of Grunt HTTP Stagers. A new form in the interface allows users to specify a number of variable values to be checked before executing the actual Grunt agent. The desired environment variables can be specified when creating a new HTTP launcher in the following format:enVar1=value1;envVar2=value2;envVar3=value3....
If this form is empty, the Stager will just run without checking anything.
This can be seen in the Pull Request here (https://github.com/cobbr/Covenant/pull/248)
An example is shown below:
The way we implemented this has an obvious caveat: all the logic is hardcoded in the Stager, so anyone will be able to see who is the actual target. In addition, this implementation does not protect in any way the final payload, as anyone could bypass these checks easily and just jump to the stages and actual code.
As we are using a Stager, we could leverage one of the stages to perform a server-side check of the variables so their values are not hardcoded in the actual Stager. If this check results in false, the server does not continue with further stages. Perhaps for a future addition!
New tasks, little fixes and improvements.
The Internal Monologue [18] attack is a good alternative for obtaining credentials from users within a system without touching lsass.exe.
The following PR (https://github.com/cobbr/Covenant/pull/250) adds the Internal Monologue repository as a Covenant submodule and a Task to execute the attack:
We noticed there was scope for extending and improving Active Directory domain enumeration functions.
The following PR (https://github.com/cobbr/SharpSploit/pull/66) fixes an issue with the logic for parsing Properties for the GetDomainComputer
function in SharpSploit.
This second PR (https://github.com/cobbr/Covenant/pull/254) extends the following tasks to take full advantage of the functionality provided by SharpSploit (e.g. LDAP filters, UAC filters….)
Covenant did not allow choosing an OutputKind type within its interface when generating Binary Launchers. This led to the need to change this setting manually in the source code, so that such launchers would not display a window when executed.
This PR (https://github.com/cobbr/Covenant/pull/249 ) creates an OutputKind form for the Binary Launcher. The default OutputKind is WindowsApplication (hidden window), so you don’t have to choose it every time you generate a new Grunt.
[1] https://github.com/cobbr/Covenant
[2] https://github.com/cobbr/SharpSploit
[3] https://docs.microsoft.com/en-us/windows/win32/etw/about-event-tracing
[5] https://blog.xpnsec.com/hiding-your-dotnet-etw/
[6] https://github.com/its-a-feature/Mythic
[7] https://github.com/BloodHoundAD/SharpHound3
[10] https://www.matteomalvica.com/blog/2019/12/02/win-defender-atp-cred-bypass/
[11] https://github.com/skelsec/pypykatz
[12] https://github.com/gentilkiwi/mimikatz/
[13] https://attack.mitre.org/techniques/T1055/
[14] https://www.youtube.com/watch?v=DOe7WTuJ1Ac
[16] https://blog.xpnsec.com/protecting-your-malware/