Implementing a Better DNS Dead Drop

06 Jan 2008, 19:15 PST

dead drop (n): A dead drop or dead letter box, is a location used to secretly pass items between two people, without requiring them to meet.

The Original DNS Dead-Drop

Two years ago, I implemented a DNS-based dead-drop, based on an idea presented by Dan Kaminisky in Attacking Distributed Systems: The DNS Case Study.

Using a recursive, caching name server, coupled with a wildcard zone, it's possible to implement double-blind data transfer. In each DNS query, 7 bits are reserved for a number of flags, one of which is the Recursion Desired (RD) flag. If set to 0, the queried DNS server will not attempt to recurse -- it will only provide answers from its cache.

Combine this with a wildcard zone and it's possible to signal bits (RD on), and read them (RD off). To set a bit to 1 the sender issues a query with the RD bit on. The wildcard zone resolves all requests, including this query. The receiver then issues a query for the same hostname, with the RD bit off. If the bit is 1, the query will return a valid record. If the bit is 0, no record will be returned.

So, it's easy to signal a single bit, but what if you want to share more than 1 bit of data? This requires both sides to compute a list of records -- one record for every bit of data we wish to send. In my implementation, I chose to do this with a pre-shared word list and initialization vector (IV). Given the same word list and IV, both sender and receiver can independently compute an identical mapping of words to bit positions. The sender can then signal the '1' bits, and the receiver can query all bits.

Hiding the Trail: Using TTL to Signal Bits

To avoid suspicion, a good dead-drop mechanism should not appear unusual to an outside observer. The RD flag is unusual, and a signature to detect its use can easily be added to intrusion detection systems. It would be considerably more sneaky to use a signaling mechanism that relied on more normal-appearing DNS queries.

This is where the time-to-live (TTL) value can be used. When returning query results, many recursive DNS servers include a TTL -- the number of seconds before the recursive name server will purge the record from its cache. The TTL begins decrementing as soon as a record is cached. Therefor, newer lookups with have a higher TTL than older lookups. Using this property, it is possible to determine if a record was previously cached, and thus signal bits without relying on the RD flag.

To communicate, the sender and receiver need to pre-share a word list, an initialization vector (IV), the IP of a recursive nameserver, a wildcard domain, and a communications window (time of day). Here's how the protocol works:

Sender:

Receiver:

Download

You can download a copy of NSDK here. It's written in Python, and depends on the dnspython library.

The implementation is a proof-of-concept -- the TTL heuristic is very simple, and you'll certainly see bit errors in longer messages. I enjoyed the "Bourne Identity" books way too much, and this is all meant in fun.

Usage

nsdk.py [dns IP] [wildcard domain] [word list] [iv] <message>

Each bit of your message requires at least one DNS query. I strongly suggest testing this implementation against name servers and zones that you control.

To send a message:

./nsdk.py 10.0.0.1 wildcard.example.com /usr/share/dict/words 42 "The crow flies at midnight"
Message sent successfully!

To receive the message:

./nsdk.py 10.0.0.1 wildcard.example.com /usr/share/dict/words 42
Read 208 bits from DNS server
The Secret Message: The crow flies at midnight