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"