## This file is part of Scapy ## See http://www.secdev.org/projects/scapy for more informations ## Copyright (C) Philippe Biondi ## This program is published under a GPLv2 license """ DNS: Domain Name System. """ import socket,struct from scapy.packet import * from scapy.fields import * from scapy.ansmachine import * from scapy.layers.inet import IP, UDP from scapy.utils import str2bytes class DNSStrField(StrField): def h2i(self, pkt, x): if type(x) == str: x = x.encode('ascii') if x == b"": return b"." return x def i2m(self, pkt, x): if type(x) == str: x = x.encode('ascii') if x == b".": return b"\x00" x = [k[:63] for k in x.split(b".")] # Truncate chunks that cannot be encoded (more than 63 bytes..) x = map(lambda y: bytes([len(y)]) + y, x) x = b"".join(x) if x[-1] != 0: x += b"\x00" return x def getfield(self, pkt, s): n = b"" #if ord(s[0]) == 0: if (s[0]) == 0: return s[1:], b"." while 1: #l = ord(s[0]) l = (s[0]) s = s[1:] if not l: break if l & 0xc0: raise Scapy_Exception("DNS message can't be compressed at this point!") else: n += s[:l]+b"." s = s[l:] return s, n class DNSRRCountField(ShortField): holds_packets=1 def __init__(self, name, default, rr): ShortField.__init__(self, name, default) self.rr = rr def _countRR(self, pkt): x = getattr(pkt,self.rr) i = 0 while isinstance(x, DNSRR) or isinstance(x, DNSQR) or isdnssecRR(x): x = x.payload i += 1 return i def i2m(self, pkt, x): if x is None: x = self._countRR(pkt) return x def i2h(self, pkt, x): if x is None: x = self._countRR(pkt) return x def DNSgetstr(s,p): name = b"" q = 0 jpath = [p] while 1: if p >= len(s): warning("DNS RR prematured end (ofs=%i, len=%i)"%(p,len(s))) break #l = ord(s[p]) l = s[p] p += 1 if l & 0xc0: if not q: q = p+1 if p >= len(s): warning("DNS incomplete jump token at (ofs=%i)" % p) break p = ((l & 0x3f) << 8) + s[p] - 12 if p in jpath: warning("DNS decompression loop detected") break jpath.append(p) continue elif l > 0: name += s[p:p+l]+b"." p += l continue break if q: p = q return name,p class DNSRRField(StrField): holds_packets=1 def __init__(self, name, countfld, passon=1): StrField.__init__(self, name, None) self.countfld = countfld self.passon = passon def i2m(self, pkt, x): if x is None: return b"" return bytes(x) def decodeRR(self, name, s, p): ret = s[p:p+10] type,cls,ttl,rdlen = struct.unpack("!HHIH", ret) p += 10 rr = DNSRR(b"\x00"+ret+s[p:p+rdlen]) if type in [2, 3, 4, 5]: rr.rdata = DNSgetstr(s,p)[0] del(rr.rdlen) elif type in dnsRRdispatcher.keys(): rr = dnsRRdispatcher[type](b"\x00"+ret+s[p:p+rdlen]) else: del(rr.rdlen) p += rdlen rr.rrname = name return rr,p def getfield(self, pkt, s): if type(s) is tuple : s,p = s else: p = 0 ret = None c = getattr(pkt, self.countfld) if c > len(s): warning("wrong value: DNS.%s=%i" % (self.countfld,c)) return s,b"" while c: c -= 1 name,p = DNSgetstr(s,p) rr,p = self.decodeRR(name, s, p) if ret is None: ret = rr else: ret.add_payload(rr) if self.passon: return (s,p),ret else: return s[p:],ret class DNSQRField(DNSRRField): holds_packets=1 def decodeRR(self, name, s, p): ret = s[p:p+4] p += 4 rr = DNSQR(b"\x00"+ret) rr.qname = name return rr,p class RDataField(StrLenField): def m2i(self, pkt, s): family = None if pkt.type == 1: # A family = socket.AF_INET elif pkt.type == 12: # PTR s = DNSgetstr(s, 0)[0] elif pkt.type == 16: # TXT ret_s = b"" tmp_s = s # RDATA contains a list of strings, each are prepended with # a byte containing the size of the following string. while tmp_s: tmp_len = struct.unpack("!B", bytes([tmp_s[0]]))[0] + 1 if tmp_len > len(tmp_s): warning("DNS RR TXT prematured end of character-string (size=%i, remaining bytes=%i)" % (tmp_len, len(tmp_s))) ret_s += tmp_s[1:tmp_len] tmp_s = tmp_s[tmp_len:] s = ret_s elif pkt.type == 28: # AAAA family = socket.AF_INET6 if family is not None: s = inet_ntop(family, s) return s def i2m(self, pkt, s): if pkt.type == 1: # A if s: if type(s) is bytes: s = s.decode('ascii') s = inet_aton(s) elif pkt.type in [2,3,4,5]: # NS, MD, MF, CNAME s = b"".join(map(lambda x: bytes([len(x)]) + x, s.split(b"."))) #if ord(s[-1]): if s[-1]: s += b"\x00" elif pkt.type == 16: # TXT if s: ret_s = b"" # The initial string must be splitted into a list of strings # prepended with theirs sizes. while len(s) >= 255: ret_s += b"\xff" + s[:255] s = s[255:] # The remaining string is less than 255 bytes long if len(s): ret_s += struct.pack("!B", len(s)) + s s = ret_s elif pkt.type == 28: # AAAA if s: s = inet_pton(socket.AF_INET6, s) return s class RDLenField(Field): def __init__(self, name): Field.__init__(self, name, None, "H") def i2m(self, pkt, x): if x is None: rdataf = pkt.get_field("rdata") x = len(rdataf.i2m(pkt, pkt.rdata)) return x def i2h(self, pkt, x): if x is None: rdataf = pkt.get_field("rdata") x = len(rdataf.i2m(pkt, pkt.rdata)) return x class DNS(Packet): name = "DNS" fields_desc = [ ShortField("id", 0), BitField("qr", 0, 1), BitEnumField("opcode", 0, 4, {0:"QUERY",1:"IQUERY",2:"STATUS"}), BitField("aa", 0, 1), BitField("tc", 0, 1), BitField("rd", 0, 1), BitField("ra", 0, 1), BitField("z", 0, 1), # AD and CD bits are defined in RFC 2535 BitField("ad", 0, 1), # Authentic Data BitField("cd", 0, 1), # Checking Disabled BitEnumField("rcode", 0, 4, {0:"ok", 1:"format-error", 2:"server-failure", 3:"name-error", 4:"not-implemented", 5:"refused"}), DNSRRCountField("qdcount", None, "qd"), DNSRRCountField("ancount", None, "an"), DNSRRCountField("nscount", None, "ns"), DNSRRCountField("arcount", None, "ar"), DNSQRField("qd", "qdcount"), DNSRRField("an", "ancount"), DNSRRField("ns", "nscount"), DNSRRField("ar", "arcount",0) ] def answers(self, other): return (isinstance(other, DNS) and self.id == other.id and self.qr == 1 and other.qr == 0) def mysummary(self): type = ["Qry","Ans"][self.qr] name = "" if self.qr: type = "Ans" if self.ancount > 0 and isinstance(self.an, DNSRR): name = ' "%s"' % self.an.getstrval("rdata") else: type = "Qry" if self.qdcount > 0 and isinstance(self.qd, DNSQR): name = ' "%s"' % self.qd.getstrval("qname") return 'DNS %s%s ' % (type, name) dnstypes = { 0:"ANY", 255:"ALL", 1:"A", 2:"NS", 3:"MD", 4:"MF", 5:"CNAME", 6:"SOA", 7: "MB", 8:"MG", 9:"MR",10:"NULL",11:"WKS",12:"PTR",13:"HINFO",14:"MINFO",15:"MX",16:"TXT", 17:"RP",18:"AFSDB",28:"AAAA", 33:"SRV",38:"A6",39:"DNAME", 41:"OPT", 43:"DS", 46:"RRSIG", 47:"NSEC", 48:"DNSKEY", 50: "NSEC3", 51: "NSEC3PARAM", 32769:"DLV" } dnsqtypes = {251:"IXFR",252:"AXFR",253:"MAILB",254:"MAILA",255:"ALL"} dnsqtypes.update(dnstypes) dnsclasses = {1: 'IN', 2: 'CS', 3: 'CH', 4: 'HS', 255: 'ANY'} class DNSQR(Packet): name = "DNS Question Record" show_indent=0 fields_desc = [ DNSStrField("qname",b""), ShortEnumField("qtype", 1, dnsqtypes), ShortEnumField("qclass", 1, dnsclasses) ] # RFC 2671 - Extension Mechanisms for DNS (EDNS0) class EDNS0TLV(Packet): name = "DNS EDNS0 TLV" fields_desc = [ ShortEnumField("optcode", 0, { 0: "Reserved", 1: "LLQ", 2: "UL", 3: "NSID", 4: "Reserved", 5: "PING" }), FieldLenField("optlen", None, "optdata", fmt="H"), StrLenField("optdata", b"", length_from=lambda pkt: pkt.optlen) ] def extract_padding(self, p): return b"", p class DNSRROPT(Packet): name = "DNS OPT Resource Record" fields_desc = [ DNSStrField("rrname",b""), ShortEnumField("type", 41, dnstypes), ShortField("rclass", 4096), ByteField("extrcode", 0), ByteField("version", 0), # version 0 means EDNS0 BitEnumField("z", 32768, 16, { 32768: "D0" }), # D0 means DNSSEC OK from RFC 3225 FieldLenField("rdlen", None, length_of="rdata", fmt="H"), PacketListField("rdata", [], EDNS0TLV, length_from=lambda pkt: pkt.rdlen) ] # RFC 4034 - Resource Records for the DNS Security Extensions # 09/2013 from http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml dnssecalgotypes = { 0:"Reserved", 1:"RSA/MD5", 2:"Diffie-Hellman", 3:"DSA/SHA-1", 4:"Reserved", 5:"RSA/SHA-1", 6:"DSA-NSEC3-SHA1", 7:"RSASHA1-NSEC3-SHA1", 8:"RSA/SHA-256", 9:"Reserved", 10:"RSA/SHA-512", 11:"Reserved", 12:"GOST R 34.10-2001", 13:"ECDSA Curve P-256 with SHA-256", 14: "ECDSA Curve P-384 with SHA-384", 252:"Reserved for Indirect Keys", 253:"Private algorithms - domain name", 254:"Private algorithms - OID", 255:"Reserved" } # 09/2013 from http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml dnssecdigesttypes = { 0:"Reserved", 1:"SHA-1", 2:"SHA-256", 3:"GOST R 34.11-94", 4:"SHA-384" } class TimeField(IntField): def any2i(self, pkt, x): if type(x) == str: import time, calendar t = time.strptime(x, "%Y%m%d%H%M%S") return int(calendar.timegm(t)) return x def i2repr(self, pkt, x): import time x = self.i2h(pkt, x) t = time.strftime("%Y%m%d%H%M%S", time.gmtime(x)) return "%s (%d)" % (t ,x) def bitmap2RRlist(bitmap): """ Decode the 'Type Bit Maps' field of the NSEC Resource Record into an integer list. """ # RFC 4034, 4.1.2. The Type Bit Maps Field RRlist = [] while bitmap: if len(bitmap) < 2: warning("bitmap too short (%i)" % len(bitmap)) return #window_block = ord(bitmap[0]) # window number window_block = (bitmap[0]) # window number offset = 256*window_block # offset of the Ressource Record #bitmap_len = ord(bitmap[0]) # length of the bitmap in bytes bitmap_len = (bitmap[1]) # length of the bitmap in bytes if bitmap_len <= 0 or bitmap_len > 32: warning("bitmap length is no valid (%i)" % bitmap_len) return tmp_bitmap = bitmap[2:2+bitmap_len] # Let's compare each bit of tmp_bitmap and compute the real RR value for b in range(len(tmp_bitmap)): v = 128 for i in range(8): #if ord(tmp_bitmap[b]) & v: if (tmp_bitmap[b]) & v: # each of the RR is encoded as a bit RRlist += [ offset + b*8 + i ] v = v >> 1 # Next block if any bitmap = bitmap[2+bitmap_len:] return RRlist def RRlist2bitmap(lst): """ Encode a list of integers representing Resource Records to a bitmap field used in the NSEC Resource Record. """ # RFC 4034, 4.1.2. The Type Bit Maps Field import math bitmap = b"" lst = list(set(lst)) lst.sort() #lst = filter(lambda x: x <= 65535, lst) #lst = map(lambda x: abs(x), lst) lst = [ abs(x) for x in lst if x<= 65535 ] # number of window blocks max_window_blocks = int(math.ceil(lst[-1] / 256.)) min_window_blocks = int(math.floor(lst[0] / 256.)) if min_window_blocks == max_window_blocks: max_window_blocks += 1 for wb in range(min_window_blocks, max_window_blocks+1): # First, filter out RR not encoded in the current window block # i.e. keep everything between 256*wb <= 256*(wb+1) #rrlist = filter(lambda x: 256*wb <= x and x < 256*(wb+1), lst) rrlist = [ x for x in lst if 256*wb <= x and x < 256*(wb+1) ] rrlist.sort() if rrlist == []: continue # Compute the number of bytes used to store the bitmap if rrlist[-1] == 0: # only one element in the list bs = 1 else: max = rrlist[-1] - 256*wb #bs = int(math.ceil(max / 8)) + 1 # use at least 1 byte bs = int(max // 8) + 1 # use at least 1 byte if bs > 32: # Don't encode more than 256 bits / values bs = 32 bitmap += struct.pack("B", wb) bitmap += struct.pack("B", bs) # Generate the bitmap for tmp in range(bs): v = 0 # Remove out of range Ressource Records #tmp_rrlist = filter(lambda x: 256*wb+8*tmp <= x and x < 256*wb+8*tmp+8, rrlist) tmp_rrlist = [ x for x in rrlist if 256*wb+8*tmp <= x and x < 256*wb+8*tmp+8 ] if not tmp_rrlist == []: # 1. rescale to fit into 8 bits tmp_rrlist = map(lambda x: (x-256*wb)-(tmp*8), tmp_rrlist) # 2. x gives the bit position ; compute the corresponding value tmp_rrlist = map(lambda x: 2**(7-x) , tmp_rrlist) # 3. sum everything #v = reduce(lambda x,y: x+y, tmp_rrlist) v = sum(tmp_rrlist) bitmap += struct.pack("B", v) return bitmap class RRlistField(StrField): def h2i(self, pkt, x): if type(x) == list: return RRlist2bitmap(x) return x def i2repr(self, pkt, x): x = self.i2h(pkt, x) rrlist = bitmap2RRlist(x) return [ dnstypes.get(rr, rr) for rr in rrlist ] class _DNSRRdummy(Packet): name = "Dummy class that implements post_build() for Ressource Records" def post_build(self, pkt, pay): if not self.rdlen == None: return pkt lrrname = len(self.fields_desc[0].i2m(b"", self.getfieldval("rrname"))) l = len(pkt) - lrrname - 10 pkt = pkt[:lrrname+8] + struct.pack("!H", l) + pkt[lrrname+8+2:] return pkt class DNSRRSOA(_DNSRRdummy): name = "DNS SOA Resource Record" fields_desc = [ DNSStrField("rrname",b""), ShortEnumField("type", 6, dnstypes), ShortEnumField("rclass", 1, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), DNSStrField("mname", b""), DNSStrField("rname", b""), IntField("serial", 0), IntField("refresh", 0), IntField("retry", 0), IntField("expire", 0), IntField("minimum", 0) ] class DNSRRRSIG(_DNSRRdummy): name = "DNS RRSIG Resource Record" fields_desc = [ DNSStrField("rrname",b""), ShortEnumField("type", 46, dnstypes), ShortEnumField("rclass", 1, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), ShortEnumField("typecovered", 1, dnstypes), ByteEnumField("algorithm", 5, dnssecalgotypes), ByteField("labels", 0), IntField("originalttl", 0), TimeField("expiration", 0), TimeField("inception", 0), ShortField("keytag", 0), DNSStrField("signersname", b""), StrField("signature", b"") ] class DNSRRNSEC(_DNSRRdummy): name = "DNS NSEC Resource Record" fields_desc = [ DNSStrField("rrname",b""), ShortEnumField("type", 47, dnstypes), ShortEnumField("rclass", 1, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), DNSStrField("nextname", b""), RRlistField("typebitmaps", b"") ] class DNSRRDNSKEY(_DNSRRdummy): name = "DNS DNSKEY Resource Record" fields_desc = [ DNSStrField("rrname",b""), ShortEnumField("type", 48, dnstypes), ShortEnumField("rclass", 1, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), FlagsField("flags", 256, 16, "S???????Z???????"), # S: Secure Entry Point # Z: Zone Key ByteField("protocol", 3), ByteEnumField("algorithm", 5, dnssecalgotypes), StrField("publickey", b"") ] class DNSRRDS(_DNSRRdummy): name = "DNS DS Resource Record" fields_desc = [ DNSStrField("rrname",b""), ShortEnumField("type", 43, dnstypes), ShortEnumField("rclass", 1, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), ShortField("keytag", 0), ByteEnumField("algorithm", 5, dnssecalgotypes), ByteEnumField("digesttype", 5, dnssecdigesttypes), StrField("digest", b"") ] # RFC 5074 - DNSSEC Lookaside Validation (DLV) class DNSRRDLV(DNSRRDS): name = "DNS DLV Resource Record" def __init__(self, *args, **kargs): DNSRRDS.__init__(self, *args, **kargs) if not kargs.get('type', 0): self.type = 32769 # RFC 5155 - DNS Security (DNSSEC) Hashed Authenticated Denial of Existence class DNSRRNSEC3(_DNSRRdummy): name = "DNS NSEC3 Resource Record" fields_desc = [ DNSStrField("rrname",b""), ShortEnumField("type", 50, dnstypes), ShortEnumField("rclass", 1, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), ByteField("hashalg", 0), BitEnumField("flags", 0, 8, {1:"Opt-Out"}), ShortField("iterations", 0), FieldLenField("saltlength", 0, fmt="!B", length_of="salt"), StrLenField("salt", b"", length_from=lambda x: x.saltlength), FieldLenField("hashlength", 0, fmt="!B", length_of="nexthashedownername"), StrLenField("nexthashedownername", b"", length_from=lambda x: x.hashlength), RRlistField("typebitmaps", b"") ] class DNSRRNSEC3PARAM(_DNSRRdummy): name = "DNS NSEC3PARAM Resource Record" fields_desc = [ DNSStrField("rrname",b""), ShortEnumField("type", 51, dnstypes), ShortEnumField("rclass", 1, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), ByteField("hashalg", 0), ByteField("flags", 0), ShortField("iterations", 0), FieldLenField("saltlength", 0, fmt="!B", length_of="salt"), StrLenField("salt", b"", length_from=lambda pkt: pkt.saltlength) ] dnssecclasses = [ DNSRROPT, DNSRRRSIG, DNSRRDLV, DNSRRDNSKEY, DNSRRNSEC, DNSRRDS, DNSRRNSEC3, DNSRRNSEC3PARAM ] def isdnssecRR(obj): list = [ isinstance (obj, cls) for cls in dnssecclasses ] ret = False for i in list: ret = ret or i return ret dnsRRdispatcher = { #6: DNSRRSOA, 41: DNSRROPT, # RFC 1671 43: DNSRRDS, # RFC 4034 46: DNSRRRSIG, # RFC 4034 47: DNSRRNSEC, # RFC 4034 48: DNSRRDNSKEY, # RFC 4034 50: DNSRRNSEC3, # RFC 5155 51: DNSRRNSEC3PARAM, # RFC 5155 32769: DNSRRDLV # RFC 4431 } class DNSRR(Packet): name = "DNS Resource Record" show_indent=0 fields_desc = [ DNSStrField("rrname",""), ShortEnumField("type", 1, dnstypes), ShortEnumField("rclass", 1, dnsclasses), IntField("ttl", 0), RDLenField("rdlen"), RDataField("rdata", "", length_from=lambda pkt:pkt.rdlen) ] bind_layers( UDP, DNS, dport=53) bind_layers( UDP, DNS, sport=53) @conf.commands.register def dyndns_add(nameserver, name, rdata, type="A", ttl=10): """Send a DNS add message to a nameserver for "name" to have a new "rdata" dyndns_add(nameserver, name, rdata, type="A", ttl=10) -> result code (0=ok) example: dyndns_add("ns1.toto.com", "dyn.toto.com", "127.0.0.1") RFC2136 """ zone = name[name.find(".")+1:] r=sr1(IP(dst=nameserver)/UDP()/DNS(opcode=5, qd=[DNSQR(qname=zone, qtype="SOA")], ns=[DNSRR(rrname=name, type="A", ttl=ttl, rdata=rdata)]), verbose=0, timeout=5) if r and r.haslayer(DNS): return r.getlayer(DNS).rcode else: return -1 @conf.commands.register def dyndns_del(nameserver, name, type="ALL", ttl=10): """Send a DNS delete message to a nameserver for "name" dyndns_del(nameserver, name, type="ANY", ttl=10) -> result code (0=ok) example: dyndns_del("ns1.toto.com", "dyn.toto.com") RFC2136 """ zone = name[name.find(".")+1:] r=sr1(IP(dst=nameserver)/UDP()/DNS(opcode=5, qd=[DNSQR(qname=zone, qtype="SOA")], ns=[DNSRR(rrname=name, type=type, rclass="ANY", ttl=0, rdata=b"")]), verbose=0, timeout=5) if r and r.haslayer(DNS): return r.getlayer(DNS).rcode else: return -1 class DNS_am(AnsweringMachine): function_name="dns_spoof" filter = "udp port 53" def parse_options(self, joker="192.168.1.1", match=None): if match is None: self.match = {} else: self.match = match self.joker=joker def is_request(self, req): return req.haslayer(DNS) and req.getlayer(DNS).qr == 0 def make_reply(self, req): ip = req.getlayer(IP) dns = req.getlayer(DNS) resp = IP(dst=ip.src, src=ip.dst)/UDP(dport=ip.sport,sport=ip.dport) rdata = self.match.get(dns.qd.qname, self.joker) resp /= DNS(id=dns.id, qr=1, qd=dns.qd, an=DNSRR(rrname=dns.qd.qname, ttl=10, rdata=rdata)) return resp