In a recent post, I alluded to having given a little bit of thought to ways in which clandestine communications could be achieved.
Having given a little more thought to the idea, I was unable to resist the temptation to build a small proof of concept - if only to see whether there were any obstacles that I hadn't considered.
This post is the documentation for DNSChat - a small proof of concept enabling PGP encrypted text chat using DNS Queries as a transport mechanism
Recent claims by the UK Prime Minister set my brain working - I could see the fallacy in the claim that we could ever ensure there were no secret means of communication on the Internet, but how easy would it be to come up with a 'new' means of communicating?
The base idea of sending arbitrary data via DNS isn't something I can claim credit for, the concept of exfiltrating data via DNS isn't particularly new, but having searched around it doesn't look like DNS has been (mis)used for real-time chat before.
The aim of the DNSCHAT project was simple - design and build a small utility to enable communication via DNS
You can find the script on GitHub. It's pretty rough around the edges (translation - written pretty quickly and sorely in need of a tidy), but fully functional
On a Debian based distro, you should be able to install these with
apt-get install python-gnupg python-scapy gnupg
The current version of the script uses AES-256 with a symmetric key (the user is prompted to enter a passphrase when they first start the script)
There are (to my mind at least) two ways in which you can use the script
You each enter each other's IP or hostname as the remote resolver, and the DNS queries are sent direct. The downside of this is that an adversary monitoring either end can see the IP of each of the parties
In Proximity to a real Nameserver
An alternate route is to sit on the network path of a real nameserver (or on that server itself). The client can then either be configured just as you would in a Direct communication, or by configuring the appropriate search domain, queries can be sent via any Open Resolver (such as Google's 18.104.22.168)
In principle, an adversary would then have to do a little more work to find out the IP address of both ends of the chat - but there are a few gotcha's:
- If the open resolver chosen uses certain EDNS extensions, the original source IP may be disclosed anyway
- The open resolver may also be logging queries
The script will prompt for any values which are required, but haven't been supplied on the command line, with the usage being as follows
./dnschat.py -h/--help Print this text -r/--resolver= DNS Resolver to use (e.g. --resolver=22.214.171.124) -c/--char-limit= The maximum number of characters to use per query (max: 63) -i/--id= Numeric ID to use -d/--domain= The domain to query (e.g. --domain=example.com) -v/--debug Use debug mode
If a resolver hasn't been specified, whichever one is configured at OS level will be used. You can exit the script with Ctrl-C.
Because the script uses Scapy's sniff, you must run as root
The script works entirely with DNS Queries - it doesn't care what the response is.
The data is transmitted, along with some very basic control data by embedding into various labels within the hostname being queried - it'll look something like the following
The structure is obviously quite identifiable to anyone looking at the contents of queries (see Known Limitations below), but the makeup of the domain name is as follows
- Sn Sender ID - an arbitrary numerical figure used to identify the user (can be set manually, otherwise changes between sessions)
- Sid Sequence ID - A numerical identifier for the current sequence of messages, increases incrementally
- N Sequence Number - Used to identify message ordering within each despatch of messages
- TN Total number of messages in the current despatch
- msgstring hex encoded ciphertext
- endpointDomain The search domain specified (for example with -d)
The chat interface is incredibly simplistic (though using something like Urwid to nicen it up shouldn't actually be that difficult):
./dnschat.py Enter the domain to query: example.com Enter Symmetric passphrase to use for this session: Enter a Message: Hello World 14:15:28 [You]: Hello World Enter a Message: 14:15:36 [User 89]: Hi Right Back
There are a few, though some were expected when designing.
It's quite slow
Not painfully so, but message delivery can take some time - especially if your queries are going via a slow resolver. The script currently waits to ensure it gets a response from the DNS server (even if it's an NXDOMAIN) as a very primitive means of identifying whether the fragment has been delivered
Traffic is identifiable
If DNS traffic is being monitored, then the structure of the names being queried is quite distinctive - this could probably be mitigated reasonably easily with a little thought
Error Trapping is a bit Casual
Although the script does trap exceptions, occasionally it misses one and you have to kill it to stop the capture thread
There are a number of improvements which could be made if I was intending to finish building (I'm not)
- Support for Keypairs as well as/instead of symmetric encryption
- Making the query names less identifiable
- Introducing a delay between DNS requests
- Auto-negotiation of a session encryption key
- Implementing Perfect Forward Secrecy
- Implementing Participant Isolation within the 'room'
- File transfer capability (on the face of it, trivial to implement - it's essentially exfiltration via DNS)
Obvious Opsec Mistakes
There are a number of OpSec mistakes that could easily be made - one of the biggest would be to use a custom resolver on a system that generates a lot of DNS requests anyway. To any observer, it'd look odd if 99% of DNS queries went to the ISPs DNS and all the queries from dnschat went to 126.96.36.199
The usual care would also need to be taken with respect to key exchange etc.
DNS Isn't the Only Transport available
Somewhat jokingly, I started to think about the possibility of swapping the DNS requests out for another mode of transport. The requirement was simply that the alternate transport be possible, with no requirement for efficiency.
Some of the idea's are incredibly inefficient, but would also be difficult to detect without very close inspection of packets (though simple traffic analysis may still be enough to trigger that further analysis)
I'm not planning to build any of those, but it's quite entertaining following that line of thought