Update 04/14: A friend pointed me to dnssnarf, a project that looks like it was written at a DojoSec meeting by Christopher McBee and then updated a bit later on by Grant Stavely. It uses Scapy (which I hear is really neat if you haven’t played with it). Check Grant’s blog post about dnssnarf out.
So, here is another quickie in case anyone needs it out there in the Intertubes. Say you have a .pcap file, or many .pcap files, and you want to mine the DNS responses out of them so you can build up a passive DNS database and track malicious resolutions to build a list of ban-able IP addresses. This script aims to parse a given .pcap file (tcpdump/wireshark libpcap format) and returns the results of the query types you have interest in.
This script is built around dpkt, a tool by Dug Song, and the contents are heavily inspired by the tutorials present at Jon Oberheide’s site (also a developer of dpkt). Honestly, most of the time writing this was spent understanding how dpkt handled its internal data structures and how to get to the data. The documentation on dpkt is not the most mature, but the source is pretty readable, if you keep the references I mention in the comments at hand. Also, this script was only tested with Python 2.6 and dpkt 1.7 on Linux, it was confirmed to not work on Windows as dpkt appears to have some serious problems with Windows at the moment.
#!/usr/bin/env python import dpkt, socket, sys if len(sys.argv) < 2 or len(sys.argv) > 2: print "Usage:\n", sys.argv, "filename.pcap" sys.exit() f = open(sys.argv) pcap = dpkt.pcap.Reader(f) for ts, buf in pcap: # make sure we are dealing with IP traffic # ref: http://www.iana.org/assignments/ethernet-numbers try: eth = dpkt.ethernet.Ethernet(buf) except: continue if eth.type != 2048: continue # make sure we are dealing with UDP # ref: http://www.iana.org/assignments/protocol-numbers/ try: ip = eth.data except: continue if ip.p != 17: continue # filter on UDP assigned ports for DNS # ref: http://www.iana.org/assignments/port-numbers try: udp = ip.data except: continue if udp.sport != 53 and udp.dport != 53: continue # make the dns object out of the udp data and check for it being a RR (answer) # and for opcode QUERY (I know, counter-intuitive) try: dns = dpkt.dns.DNS(udp.data) except: continue if dns.qr != dpkt.dns.DNS_R: continue if dns.opcode != dpkt.dns.DNS_QUERY: continue if dns.rcode != dpkt.dns.DNS_RCODE_NOERR: continue if len(dns.an) < 1: continue # now we're going to process and spit out responses based on record type # ref: http://en.wikipedia.org/wiki/List_of_DNS_record_types for answer in dns.an: if answer.type == 5: print "CNAME request", answer.name, "\tresponse", answer.cname elif answer.type == 1: print "A request", answer.name, "\tresponse", socket.inet_ntoa(answer.rdata) elif answer.type == 12: print "PTR request", answer.name, "\tresponse", answer.ptrname