PGP Encrypted Text Chat Via DNS

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

 

Background

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

 

Codebase

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

 

Dependancies

  • Python
  • GnuPG
  • python-gnupg
  • Scapy

On a Debian based distro, you should be able to install these with

apt-get install python-gnupg python-scapy gnupg

 

Crypto

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)

 

Deployment

There are (to my mind at least) two ways in which you can use the script

Direct Communications

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 8.8.8.8)

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

 

Script Usage

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=8.8.8.8)
	-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

 

Generated Traffic

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

59.368.0.4.8c0d040903024eb3799b822cb02760d26201d2f6c3b8d97202a0a0f8c59cf8b.example.com

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}.{Sid}.{N}.{TN}.{msgstring}.endpointDomain

Where

  • 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)

 

Output

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

 

Known Limitations

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

 

Obvious Improvements

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 8.8.8.8

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