Covertly Sending Messages Using DNS

How to hide short messages using the power of non-recursive DNS queries or any other of your favorite Internet caching mechanisms. —

Let’s say you want to send short messages that are very covert and not persistent. You could, of course use Twitter, sending strange cryptographic tweets, but that may be too obvious for your tastes, plus they can more easily be traced back to an IP/Person if just one slip-up is made. Somehow using DNS would be an awesome way to do this because:

  • It can be accessed by anyone
  • Messages expire over known intervals
  • No accounts are necessary
  • There are huge amounts of data to go through and junk requests that will look just like yours if someone wanted to find you out
  • Google has a very nice public DNS you can use for free at 8.8.8.8 or 8.8.4.4

Specifics

The two types of queries we’ll be using here are standard and non-recursive DNS queries.

Standard queries are the ones that your computer normally does to change a hostname in to an IP address it can contact, to do this from a command line you’d type:

$ dig @8.8.8.8 www.google.com

To which the DNS server responds:

; <<>> DiG 9.8.1-P1 <<>> @8.8.8.8 www.google.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5459
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;www.google.com.			IN	A

;; ANSWER SECTION:
www.google.com.		300	IN	A	74.125.225.179
www.google.com.		300	IN	A	74.125.225.180
www.google.com.		300	IN	A	74.125.225.176
www.google.com.		300	IN	A	74.125.225.177
www.google.com.		300	IN	A	74.125.225.178

;; Query time: 53 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Apr 12 10:18:31 2013
;; MSG SIZE  rcvd: 112

This shows you all of the numerical IP addresses you can reach google.com at, if you copy and paste the first one in to your browser, you’ll see Google’s home page.

That’s all well and good, but how would you actually store information using DNS? For this you’ll need to generate a domain name that doesn’t exist, we’ll be using www.testdnsflagsetting.com for the purposes of this article:

Now do a dig on it:

$ dig @8.8.8.8 www.testdnsflagsetting.com +norecurse

The response looks different this time, note that ANSWER: 0 and AUTHORITY: 0, this means the request is not authoratative (8.8.8.8 doesn’t know this informatoin for sure), and there was no answer.

This is because we passed in the +norecurse flag to dig, asking the server to just reply with what was it had stored rather than asking higher up in the chain of command:

; <<>> DiG 9.8.1-P1 <<>> @8.8.8.8 www.testdnsflagsetting.com +norecurse
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 23556
;; flags: qr ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;www.testdnsflagsetting.com.	IN	A

;; Query time: 57 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Apr 12 10:04:10 2013
;; MSG SIZE  rcvd: 44

Now we’ll do the same thing, but without the +norecursive:

$ dig @8.8.8.8 www.testdnsflagsetting.com

; <<>> DiG 9.8.1-P1 <<>> @8.8.8.8 www.testdnsflagsetting.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 22889
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0

;; QUESTION SECTION:
;www.testdnsflagsetting.com.	IN	A

;; AUTHORITY SECTION:
com.			900	IN	SOA	a.gtld-servers.net. nstld.verisign-grs.com. 1365782647 1800 900 604800 86400

;; Query time: 82 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Apr 12 10:04:21 2013
;; MSG SIZE  rcvd: 117

Note that the AUTHORITY: 1 now is true, meaning that 8.8.8.8 checked with the server that knows about all .com addresses to see if ours existed. There is also a new section, note the number 900 in it, that means this response is going to be saved in Google’s DNS (8.8.8.8) for 900 seconds.

If we go back and do the +norecurse version again, we now get the cached response, but with 890, meaning there are only 890 seconds left until the cache is cleared:

$ dig @8.8.8.8 www.testdnsflagsetting.com +norecurse

; <<>> DiG 9.8.1-P1 <<>> @8.8.8.8 www.testdnsflagsetting.com +norecurse
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 14832
;; flags: qr ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0

;; QUESTION SECTION:
;www.testdnsflagsetting.com.	IN	A

;; AUTHORITY SECTION:
com.			890	IN	SOA	a.gtld-servers.net. nstld.verisign-grs.com. 1365782647 1800 900 604800 86400

;; Query time: 39 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Apr 12 10:04:31 2013
;; MSG SIZE  rcvd: 117

Put these together now, and say you want to send a true or a false to someone on the other side of the globe, you both just have to agree on a time to check, and a domain, then if you want to say true you do a recursive query, and the other person will get back an authoritative record when they do a non-recursive; if you don’t do the query, they will not and they’ll know you sent false

Sending a Whole Message

Now say you want to send a whole message! Convert the text to ASCII, and break it down to bits, use the first byte to tell how long the message will be (up to 255 characters).

Say you just wanted to send the message Hi, that means you’d send a 2 then Hi, but you can’t use the same domain the whole way through, you’ll have to agree on a bunch of domains, or a way of randomly generating them. Here we’ll just append a number to the end:

Letter:       2        H        i
Binary:   00000010 01001000 01101001
POsition:            111111 11112222
          01234567 89012345 67890123

Your queries would look something like this:

dig www.testdnsflagsetting6.com
dig www.testdnsflagsetting9.com
dig www.testdnsflagsetting12.com
dig www.testdnsflagsetting17.com
dig www.testdnsflagsetting18.com
dig www.testdnsflagsetting20.com
dig www.testdnsflagsetting23.com

You only actually dig for the 1s, because they are represented by true.

Getting The Message Back Out

Now to get the message back out, you just need to do a dig for positions 0-7, and convert that in to the number of letters in the message, multiply that by 8 to get the number of bits you need to check, and check the domains ending with all of those numbers.

First 8

dig www.testdnsflagsetting0.com
dig www.testdnsflagsetting1.com
...
dig www.testdnsflagsetting7.com

This would give us the number 2, meaning there were 2 more sections of 8, then you read on.

A program to do it!

This is a short Python script to do it, you may want to change the DOMAIN_NAME. in case other people are using the script too.

#!/usr/bin/env python2
''' A program to send messages via DNS queries.
'''

import subprocess

DNS_SERVER = '8.8.8.8'
DOMAIN_NAME = "www.testdnsflagsetting{}.com"
NORECURSE_OPT = "+norecurse"

msg = raw_input("Enter a message, or blank to receive: ")

def read_byte(byteno):
	byte = "0b"
	for i in range(byteno * 8, (byteno + 1) * 8):
		output = subprocess.check_output(['dig','@{}'.format(DNS_SERVER), DOMAIN_NAME.format(i), NORECURSE_OPT])
		if ";; AUTHORITY SECTION:" in output:
			byte += '1'
		else:
			byte += '0'

	return int(byte, 2) # converts binary to an int

def write_byte(byteno, byte):
	to_write = bin(byte)[2:].zfill(8) # gets binary representation of a byte
	for loc, b in enumerate(to_write):
		if b == '1':
			i = (byteno * 8) + loc
			subprocess.check_output(['dig','@{}'.format(DNS_SERVER), DOMAIN_NAME.format(i)])
			print "Wrote 1 at: {}".format(i)

if len(msg) == 0:
	message = ""
	for byte in range(1,read_byte(0) + 1): # first byte is length of message
		message += chr(read_byte(byte))

	if len(message) > 0:
		print message
	else:
		print "[No Message]"

else:
	total = len(msg)
	write_byte(0, total)
	for loc, char in enumerate(msg):
		write_byte(loc + 1, ord(char))

	print "Message written"