Unveiling DNSStager: A tool to hide your payload in DNS

Estimated Reading Time: 8 minutes

In the past few weeks, I was working on a new project that could help me to solve an issue during a case I was facing, I needed a tool to help me pulling off my payload through DNS without being noisy or suspicious with the ability to inject this payload to the memory and run it.

And after some work, I’m very glad to release DNSStager.

What is DNSStager?

DNSStager is is an open-source tool used to help Pentesters/RedTeamers to hide their payload in DNS and resolve it based on multiple DNS records such as IPv6  and TXT  and then Inject it into memory and run it for you.

DNSStager will create a fake DNS server for you that will resolve fake addresses of your domain based on AAAA and TXT records, these addresses are presenting a chunk of your payload encoded/encrypted and ready to use by the agent.

DNSStager can generate C or GoLang agent for you, You just need to define some variable for DNSStager and it will handle the whole process for you.

And this figure shows how DNSStager works:

So as we see from the previous figures, DNSStager client.exe will try to resolve N number of subdomains generated by DNSStager and each response of these domains are presenting a number of bytes of your encoded payload.

DNSStager will encode your payload, split it into chunks and make it ready to resolve via your client.exe agent, which could be C or GoLang agent, you can choose that when you start DNSStager and we will talk about that in a bit.

So after retrieving all the payload bytes, the DNSStager agent will inject them into memory and run them directly to execute the shellcode, the good thing here that you can customize the agent and implement your own techniques of process/memory injection to get your payload run after it got pulled through DNS.

DNSStager key features

  • Hide and Resolve your payload in IPv6 records.
  • Hide and Resolve your payload in TXT records.
  • XOR encoder to encode your payload.
  • Base64 encoder to encode your payload (only for TXT records).
  • A pure agent wrote in C with the ability to customize it.
  • A pure agent wrote in GoLang with the ability to customize it.
  • The ability to use sleep between each DNS request.
  • AND MUCH MORE TO COME!

Why use DNSStager?

A best use case for DNSStager is when you need to retrieve your payload through DNS while it’s the only channel available for you to receive data from.

You can use the C or GoLang client to resolve the full payload through DNS and customize the agent to use your own process/memory injection, which means you can fully customize it for your operation and your target.

DNSStager currently supports two DNS records to resolve the full payload which are:

  • IPv6 via AAAA record.
  • TXT record.

And of course much more to come soon!

DNSStager Installation

To install DNSStager you need to clone it first from the official repo using the following command:

git clone https://github.com/mhaskar/DNSStager

And then install all the python requirements of DNSStager using the following command:

pip3 install -r requirements.txt

Please note that you need Python3 in order to run DNSStager.

C Language dependencies

To compile the DNSStager C agent, you need to install ming-w64 using the following command:

apt install mingw-w64

GoLang dependencies

To compile the GoLang agent, you need to have the GoLang compiler v1.16 installed.

And after you install all the dependencies, you are ready now to start DNSStager using the following command:

./dnsstager.py

Please note that you need root privileges to start DNSStager.

You are good to go now!

Make sure to disable systemd-resolved before you run DNSStager.

DNS Setup

To use DNSStager, you need to make your domain point to DNSStager as his name server “DNS Server” in order to resolve and handle any DNS requests coming to your domain.

For example, I’m controlling a domain called mydnsserver.live I created a subdomain called test.mydnsserver.live and made mydnsserver.live the “NS” – Name Server of test.mydnsserver.live after running DNSStager on mydnsserver.live .

So in this case, any request coming to the domain test.mydnsserver.live will be handled by mydnsserver.live which is the DNSStager instance that we are running.

This means if you tried to resolve subdomain.test.mydnsserver.live DNSStager will resolve the value of subdomain.test.mydnsserver.live if it’s existed.

You can change the NS to any subdomain/domain you want, but in my case, I just took the main domain as the NS.

DNSStager Options

We can check DNSStager options using the switch -h like the following:

[email protected]:~/DNSStager# ./dnsstager.py -h
usage: dnsstager.py [-h] [--domain DOMAIN] [--payloads] [--prefix PREFIX] [--payload PAYLOAD] [--output OUTPUT]
                    [--shellcode_path SHELLCODE_PATH] [--xorkey XORKEY] [--sleep SLEEP]

DNSStager main parser

optional arguments:
  -h, --help            show this help message and exit
  --domain DOMAIN       The domain you want to use as staging host
  --payloads            show all payloads
  --prefix PREFIX       Prefix to use as part of your subdomain schema
  --payload PAYLOAD     Payload to use, see --payloads for more details
  --output OUTPUT       Agent output path
  --shellcode_path SHELLCODE_PATH
                        Shellcode file path
  --xorkey XORKEY       XOR key to encode your payload with
  --sleep SLEEP         sleep for N seconds between each DNS request
[email protected]:~/DNSStager# 

  • –domain: you can use this option to select the main domain you will use to handle the DNS requests, in our case, it will be test.mydnsserver.live.
  • — prefix: The prefix you want to use for the subdomain schema For example if your main domain is test.mydnsserver.live you can specify the prefix as “cdn” for example, So the generated domains will be a pattern as the following:
    • cdn0.test.mydnsserver.live
    • cdn1.test.mydnsserver.live
    • cdnN.test.mydnsserver.live

Where N is auto-generated number represents the number of chunks of your payload.

  • –payload: the DNSStager payload “agent” you want to generate based on the technique, programming language, and architecture.
  • –output: Output path to save DNSStager executable payload “agent”.
  • –shellcode_path: Your raw/bin shellcode path.
  • –xorkey: XOR key to encode the payload with.
  • –sleep: Used to sleep for N seconds between each DNS request.

DNSStager payloads

You can check DNSStager payload using --payloads the option to get:

The structure of DNSStager payloads is:

arch/language/method

The method is the DNS record that you want DNSStager to translate your payload to.

And as mentioned, there are two modes currently to work with:

  • IPv6 (AAAA).
  • TXT.

DNSStager payload encoders

DNSStager encrypt your payload using XOR encoder/encrypter in case you are using IPv6 to represent your payload, and base64 in case you are using TXT to represent your payload.

If you are using TXT mode, the payload will be encoded using XOR too before being encoded using base64.

You can specify the xor key to encrypt your payload by using the --xorkey option.

And as OPSEC consideration, DNSStager will check if you didn’t choose the XOR key and ask you to enter the XOR key or procced without it like the following:

Extra encoders will be added in the future.

DNSStager Requests Sleep

You can specify the number of seconds to sleep between each DNS request, which will make the DNS query for these records less noise.

You can do that using the --sleep option followed by the number of seconds you want to sleep.

DNSStager usage example

Now let’s use DNSStager to compromise Windows Server 2019 with fully updated Windows Defender.

I will generate Cobalt Strike payload and save it to payload.bin file like the following:

Feel free to change the payload as you want.

And now I will upload this payload to my DNSStager instance:

All ready to run DNSStager now, my domain is test.mydnsserver.live and I will use cdn as my prefix and the rest of the options shown in this screenshot:

I encoded my payload with XOR key 0x20 with no sleep between the requests.

Lets test if everything is working well by using the following dig command to query for AAAA record for the domain cdn0.test.mydnsserver.live.

As we can see, the red box contains the first 16 bytes of our payload XOR encoded with 0x20.

To verify that, let’s read the first 16 bytes from payload.bin and XOR them with 0x20 to get the following:

As we can see, we got the same in the DNS response value after reading the first 16 bytes from the shellcode after encoding each byte with 0x20.

Now we are ready to send agent.exe to our target and see if we will get a beacon back to our Cobalt Strike.

Let’s open this file in Windows Server 2019 and see what we will get:

Perfect! we can see that we got a beacon back from DNSStager after pulling the full shellcode through DNS, encode it, and run it from memory.

And this GIF shows the process in action:

Observation

DNSStager agent will send a number of DNS in order to pull the full payload, of course, if you are using IPv6 the number of requests will be bigger than TXT because you are limited to 16 bytes per request only.

Now to see the traffic in action, let’s open Wireshark on our Windows Server 2019 and see what is going there!

A total of 59 DNS AAAA requests sent to pull the full payload, we can add some sleep between each request to make it less noisy!

And don’t forget that the process is depending on the size of the payload again, larger shellcodes mean more requests to send from the agent.

DNSStager Agent Customizing

You can modify the process injection technique you want to use for both GoLang and C agents, you can see the source code for both codes in the templates folder inside DNSStager main folder.

The C agent currently is doing a simple jump to the shellcode after pulling it from DNS, of course, you can customize that as you wish after editing the C agent template templates/client-ipv6-generic.c main function which has the following code:

int main(){

// Get Shellcode Address

LPVOID ShellcodeAddress = GetShellCodeAddress();

// Write your injection technique here
// And use ShellcodeAddress as your shellcode pointer

// Jump to shellcode -  Replace it with your technique 
goto *ShellcodeAddress;
}

In line number 95 you will have the address of the ready to use shellcode saved in a variable called ShellcodeAddress and then we use simple goto to jump to this address.

You can use ShellcodeAddress with your process/memory injection technique as you want now by editing this function.

For the GoLang agent, we are using the CreateFiber technique which is adopted from Ne0nd0g.

You can also customize it as you want by using the function retreiveShellcodeAsHex in both template files:

  • templates/client-ipv6-generic.go
  • templates/client-txt-generic.go

We will discuss that more in another article with more examples later on, so stay tuned for it 😉

Credits

I want to thank @TeslaPuls3 who wrote the GoLang agent for DNSStager and helped a lot during project testing.

Also want to thank @bb_hacks for the great support during writing/testing the tool.

Final Words

DNSStager is still under development project and this is the beta release of it, I tried to finish the main functions and make it stable as much as I can, of course, more work and fun ideas to come soon within the stable version but until then, please feel free to report any issue by opening a new issue.

You can get the latest version of DNSStager from the official Github repository.

Sponsoring this project

You can be a proud sponsor of DNSStager using Github sponsorship using this link.