# This file is part of Scapy # See http://www.secdev.org/projects/scapy for more information # Copyright (C) Philippe Biondi # This program is published under a GPLv2 license """ DNS: Domain Name System. """ from __future__ import absolute_import import struct import time from scapy.config import conf from scapy.packet import Packet, bind_layers, NoPayload from scapy.fields import BitEnumField, BitField, ByteEnumField, ByteField, \ ConditionalField, FieldLenField, FlagsField, IntField, \ PacketListField, ShortEnumField, ShortField, StrField, StrFixedLenField, \ StrLenField, MultipleTypeField, UTCTimeField from scapy.compat import orb, raw, chb, bytes_encode from scapy.ansmachine import AnsweringMachine from scapy.sendrecv import sr1 from scapy.layers.inet import IP, DestIPField, IPField, UDP, TCP from scapy.layers.inet6 import DestIP6Field, IP6Field from scapy.error import warning, Scapy_Exception import scapy.modules.six as six from scapy.modules.six.moves import range def dns_get_str(s, pointer=0, pkt=None, _fullpacket=False): """This function decompresses a string s, starting from the given pointer. :param s: the string to decompress :param pointer: first pointer on the string (default: 0) :param pkt: (optional) an InheritOriginDNSStrPacket packet :returns: (decoded_string, end_index, left_string) """ # The _fullpacket parameter is reserved for scapy. It indicates # that the string provided is the full dns packet, and thus # will be the same than pkt._orig_str. The "Cannot decompress" # error will not be prompted if True. max_length = len(s) # The result = the extracted name name = b"" # Will contain the index after the pointer, to be returned after_pointer = None processed_pointers = [] # Used to check for decompression loops # Analyse given pkt if pkt and hasattr(pkt, "_orig_s") and pkt._orig_s: s_full = pkt._orig_s else: s_full = None bytes_left = None while True: if abs(pointer) >= max_length: warning("DNS RR prematured end (ofs=%i, len=%i)" % (pointer, len(s))) break cur = orb(s[pointer]) # get pointer value pointer += 1 # make pointer go forward if cur & 0xc0: # Label pointer if after_pointer is None: # after_pointer points to where the remaining bytes start, # as pointer will follow the jump token after_pointer = pointer + 1 if pointer >= max_length: warning("DNS incomplete jump token at (ofs=%i)" % pointer) break # Follow the pointer pointer = ((cur & ~0xc0) << 8) + orb(s[pointer]) - 12 if pointer in processed_pointers: warning("DNS decompression loop detected") break if not _fullpacket: # Do we have access to the whole packet ? if s_full: # Yes -> use it to continue bytes_left = s[after_pointer:] s = s_full max_length = len(s) _fullpacket = True else: # No -> abort raise Scapy_Exception("DNS message can't be compressed" + "at this point!") processed_pointers.append(pointer) continue elif cur > 0: # Label # cur = length of the string name += s[pointer:pointer + cur] + b"." pointer += cur else: break if after_pointer is not None: # Return the real end index (not the one we followed) pointer = after_pointer if bytes_left is None: bytes_left = s[pointer:] # name, end_index, remaining return name, pointer, bytes_left def dns_encode(x, check_built=False): """Encodes a bytes string into the DNS format :param x: the string :param check_built: detect already-built strings and ignore them :returns: the encoded bytes string """ if not x or x == b".": return b"\x00" if check_built and b"." not in x and ( orb(x[-1]) == 0 or (orb(x[-2]) & 0xc0) == 0xc0 ): # The value has already been processed. Do not process it again return x # Truncate chunks that cannot be encoded (more than 63 bytes..) x = b"".join(chb(len(y)) + y for y in (k[:63] for k in x.split(b"."))) if x[-1:] != b"\x00": x += b"\x00" return x def DNSgetstr(*args, **kwargs): """Legacy function. Deprecated""" warning("DNSgetstr deprecated. Use dns_get_str instead") return dns_get_str(*args, **kwargs) def dns_compress(pkt): """This function compresses a DNS packet according to compression rules. """ if DNS not in pkt: raise Scapy_Exception("Can only compress DNS layers") pkt = pkt.copy() dns_pkt = pkt.getlayer(DNS) build_pkt = raw(dns_pkt) def field_gen(dns_pkt): """Iterates through all DNS strings that can be compressed""" for lay in [dns_pkt.qd, dns_pkt.an, dns_pkt.ns, dns_pkt.ar]: if lay is None: continue current = lay while not isinstance(current, NoPayload): if isinstance(current, InheritOriginDNSStrPacket): for field in current.fields_desc: if isinstance(field, DNSStrField) or \ (isinstance(field, MultipleTypeField) and current.type in [2, 3, 4, 5, 12, 15]): # Get the associated data and store it accordingly # noqa: E501 dat = current.getfieldval(field.name) yield current, field.name, dat current = current.payload def possible_shortens(dat): """Iterates through all possible compression parts in a DNS string""" yield dat for x in range(1, dat.count(b".")): yield dat.split(b".", x)[x] data = {} burned_data = 0 for current, name, dat in field_gen(dns_pkt): for part in possible_shortens(dat): # Encode the data encoded = dns_encode(part, check_built=True) if part not in data: # We have no occurrence of such data, let's store it as a # possible pointer for future strings. # We get the index of the encoded data index = build_pkt.index(encoded) index -= burned_data # The following is used to build correctly the pointer fb_index = ((index >> 8) | 0xc0) sb_index = index - (256 * (fb_index - 0xc0)) pointer = chb(fb_index) + chb(sb_index) data[part] = [(current, name, pointer)] else: # This string already exists, let's mark the current field # with it, so that it gets compressed data[part].append((current, name)) # calculate spared space burned_data += len(encoded) - 2 break # Apply compression rules for ck in data: # compression_key is a DNS string replacements = data[ck] # replacements is the list of all tuples (layer, field name) # where this string was found replace_pointer = replacements.pop(0)[2] # replace_pointer is the packed pointer that should replace # those strings. Note that pop remove it from the list for rep in replacements: # setfieldval edits the value of the field in the layer val = rep[0].getfieldval(rep[1]) assert val.endswith(ck) kept_string = dns_encode(val[:-len(ck)], check_built=True)[:-1] new_val = kept_string + replace_pointer rep[0].setfieldval(rep[1], new_val) try: del(rep[0].rdlen) except AttributeError: pass # End of the compression algorithm # Destroy the previous DNS layer if needed if not isinstance(pkt, DNS) and pkt.getlayer(DNS).underlayer: pkt.getlayer(DNS).underlayer.remove_payload() return pkt / dns_pkt return dns_pkt class InheritOriginDNSStrPacket(Packet): __slots__ = Packet.__slots__ + ["_orig_s", "_orig_p"] def __init__(self, _pkt=None, _orig_s=None, _orig_p=None, *args, **kwargs): self._orig_s = _orig_s self._orig_p = _orig_p Packet.__init__(self, _pkt=_pkt, *args, **kwargs) class DNSStrField(StrLenField): """ Special StrField that handles DNS encoding/decoding. It will also handle DNS decompression. (may be StrLenField if a length_from is passed), """ def h2i(self, pkt, x): if not x: return b"." return x def i2m(self, pkt, x): return dns_encode(x, check_built=True) def i2len(self, pkt, x): return len(self.i2m(pkt, x)) def getfield(self, pkt, s): remain = b"" if self.length_from: remain, s = StrLenField.getfield(self, pkt, s) # Decode the compressed DNS message decoded, _, left = dns_get_str(s, 0, pkt) # returns (remaining, decoded) return left + remain, decoded class DNSRRCountField(ShortField): __slots__ = ["rr"] 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 class DNSRRField(StrField): __slots__ = ["countfld", "passon"] 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_encode(x) def decodeRR(self, name, s, p): ret = s[p:p + 10] # type, cls, ttl, rdlen typ, cls, _, rdlen = struct.unpack("!HHIH", ret) p += 10 cls = DNSRR_DISPATCHER.get(typ, DNSRR) rr = cls(b"\x00" + ret + s[p:p + rdlen], _orig_s=s, _orig_p=p) # Will have changed because of decompression rr.rdlen = None rr.rrname = name p += rdlen return rr, p def getfield(self, pkt, s): if isinstance(s, 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, _ = dns_get_str(s, p, _fullpacket=True) 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): def decodeRR(self, name, s, p): ret = s[p:p + 4] p += 4 rr = DNSQR(b"\x00" + ret, _orig_s=s, _orig_p=p) rr.qname = name return rr, p class DNSTextField(StrLenField): """ Special StrLenField that handles DNS TEXT data (16) """ islist = 1 def m2i(self, pkt, s): ret_s = list() 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 = orb(tmp_s[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))) # noqa: E501 ret_s.append(tmp_s[1:tmp_len]) tmp_s = tmp_s[tmp_len:] return ret_s def any2i(self, pkt, x): if isinstance(x, (str, bytes)): return [x] return x def i2len(self, pkt, x): return len(self.i2m(pkt, x)) def i2m(self, pkt, s): ret_s = b"" for text in s: text = bytes_encode(text) # The initial string must be split into a list of strings # prepended with theirs sizes. while len(text) >= 255: ret_s += b"\xff" + text[:255] text = text[255:] # The remaining string is less than 255 bytes long if len(text): ret_s += struct.pack("!B", len(text)) + text return ret_s class DNS(Packet): name = "DNS" fields_desc = [ ConditionalField(ShortField("length", None), lambda p: isinstance(p.underlayer, TCP)), 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", 1, 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): name = "" if self.qr: type = "Ans" if self.ancount > 0 and isinstance(self.an, DNSRR): name = ' "%s"' % self.an.rdata else: type = "Qry" if self.qdcount > 0 and isinstance(self.qd, DNSQR): name = ' "%s"' % self.qd.qname return 'DNS %s%s ' % (type, name) def post_build(self, pkt, pay): if isinstance(self.underlayer, TCP) and self.length is None: pkt = struct.pack("!H", len(pkt) - 2) + pkt[2:] return pkt + pay def compress(self): """Return the compressed DNS packet (using `dns_compress()`""" return dns_compress(self) def pre_dissect(self, s): """ Check that a valid DNS over TCP message can be decoded """ if isinstance(self.underlayer, TCP): # Compute the length of the DNS packet if len(s) >= 2: dns_len = struct.unpack("!H", s[:2])[0] else: message = "Malformed DNS message: too small!" warning(message) raise Scapy_Exception(message) # Check if the length is valid if dns_len < 14 or len(s) < dns_len: message = "Malformed DNS message: invalid length!" warning(message) raise Scapy_Exception(message) return s # https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4 dnstypes = { 0: "ANY", 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", 19: "X25", 20: "ISDN", 21: "RT", # noqa: E501 22: "NSAP", 23: "NSAP-PTR", 24: "SIG", 25: "KEY", 26: "PX", 27: "GPOS", 28: "AAAA", 29: "LOC", 30: "NXT", 31: "EID", 32: "NIMLOC", 33: "SRV", 34: "ATMA", 35: "NAPTR", 36: "KX", 37: "CERT", 38: "A6", 39: "DNAME", 40: "SINK", 41: "OPT", 42: "APL", 43: "DS", 44: "SSHFP", 45: "IPSECKEY", 46: "RRSIG", 47: "NSEC", 48: "DNSKEY", 49: "DHCID", 50: "NSEC3", 51: "NSEC3PARAM", 52: "TLSA", 53: "SMIMEA", 55: "HIP", 56: "NINFO", 57: "RKEY", # noqa: E501 58: "TALINK", 59: "CDS", 60: "CDNSKEY", 61: "OPENPGPKEY", 62: "CSYNC", 99: "SPF", 100: "UINFO", 101: "UID", 102: "GID", 103: "UNSPEC", 104: "NID", 105: "L32", 106: "L64", 107: "LP", 108: "EUI48", 109: "EUI64", 249: "TKEY", 250: "TSIG", 256: "URI", 257: "CAA", 258: "AVC", 32768: "TA", 32769: "DLV", 65535: "RESERVED" } 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(InheritOriginDNSStrPacket): name = "DNS Question Record" show_indent = 0 fields_desc = [DNSStrField("qname", "www.example.com"), 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"}), # noqa: E501 FieldLenField("optlen", None, "optdata", fmt="H"), StrLenField("optdata", "", length_from=lambda pkt: pkt.optlen)] # noqa: E501 def extract_padding(self, p): return "", p class DNSRROPT(InheritOriginDNSStrPacket): name = "DNS OPT Resource Record" fields_desc = [DNSStrField("rrname", ""), 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)] # noqa: E501 # 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 # noqa: E501 dnssecalgotypes = {0: "Reserved", 1: "RSA/MD5", 2: "Diffie-Hellman", 3: "DSA/SHA-1", # noqa: E501 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", # noqa: E501 252: "Reserved for Indirect Keys", 253: "Private algorithms - domain name", # noqa: E501 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"} # noqa: E501 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 = orb(bitmap[0]) # window number offset = 256 * window_block # offset of the Resource Record bitmap_len = orb(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 orb(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 = [abs(x) for x in sorted(set(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 = sorted(x for x in lst if 256 * wb <= x < 256 * (wb + 1)) if not rrlist: continue # Compute the number of bytes used to store the bitmap if rrlist[-1] == 0: # only one element in the list bytes_count = 1 else: max = rrlist[-1] - 256 * wb bytes_count = int(math.ceil(max // 8)) + 1 # use at least 1 byte if bytes_count > 32: # Don't encode more than 256 bits / values bytes_count = 32 bitmap += struct.pack("BB", wb, bytes_count) # Generate the bitmap # The idea is to remove out of range Resource Records with these steps # 1. rescale to fit into 8 bits # 2. x gives the bit position ; compute the corresponding value # 3. sum everything bitmap += b"".join( struct.pack( b"B", sum(2 ** (7 - (x - 256 * wb) + (tmp * 8)) for x in rrlist if 256 * wb + 8 * tmp <= x < 256 * wb + 8 * tmp + 8), ) for tmp in range(bytes_count) ) return bitmap class RRlistField(StrField): def h2i(self, pkt, x): if isinstance(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] if rrlist else repr(x) class _DNSRRdummy(InheritOriginDNSStrPacket): name = "Dummy class that implements post_build() for Resource Records" def post_build(self, pkt, pay): if self.rdlen is not None: return pkt + pay lrrname = len(self.fields_desc[0].i2m("", self.getfieldval("rrname"))) tmp_len = len(pkt) - lrrname - 10 tmp_pkt = pkt[:lrrname + 8] pkt = struct.pack("!H", tmp_len) + pkt[lrrname + 8 + 2:] return tmp_pkt + pkt + pay class DNSRRMX(_DNSRRdummy): name = "DNS MX Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 6, dnstypes), ShortEnumField("rclass", 1, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), ShortField("preference", 0), DNSStrField("exchange", ""), ] class DNSRRSOA(_DNSRRdummy): name = "DNS SOA Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 6, dnstypes), ShortEnumField("rclass", 1, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), DNSStrField("mname", ""), DNSStrField("rname", ""), 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", ""), 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), UTCTimeField("expiration", 0), UTCTimeField("inception", 0), ShortField("keytag", 0), DNSStrField("signersname", ""), StrField("signature", "") ] class DNSRRNSEC(_DNSRRdummy): name = "DNS NSEC Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 47, dnstypes), ShortEnumField("rclass", 1, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), DNSStrField("nextname", ""), RRlistField("typebitmaps", "") ] class DNSRRDNSKEY(_DNSRRdummy): name = "DNS DNSKEY Resource Record" fields_desc = [DNSStrField("rrname", ""), 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", "") ] class DNSRRDS(_DNSRRdummy): name = "DNS DS Resource Record" fields_desc = [DNSStrField("rrname", ""), 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", "") ] # 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", ""), 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", "", length_from=lambda x: x.saltlength), FieldLenField("hashlength", 0, fmt="!B", length_of="nexthashedownername"), # noqa: E501 StrLenField("nexthashedownername", "", length_from=lambda x: x.hashlength), # noqa: E501 RRlistField("typebitmaps", "") ] class DNSRRNSEC3PARAM(_DNSRRdummy): name = "DNS NSEC3PARAM Resource Record" fields_desc = [DNSStrField("rrname", ""), 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", "", length_from=lambda pkt: pkt.saltlength) # noqa: E501 ] # RFC 2782 - A DNS RR for specifying the location of services (DNS SRV) class DNSRRSRV(_DNSRRdummy): name = "DNS SRV Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 33, dnstypes), ShortEnumField("rclass", 1, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), ShortField("priority", 0), ShortField("weight", 0), ShortField("port", 0), DNSStrField("target", ""), ] # RFC 2845 - Secret Key Transaction Authentication for DNS (TSIG) tsig_algo_sizes = {"HMAC-MD5.SIG-ALG.REG.INT": 16, "hmac-sha1": 20} class TimeSignedField(StrFixedLenField): def __init__(self, name, default): StrFixedLenField.__init__(self, name, default, 6) def _convert_seconds(self, packed_seconds): """Unpack the internal representation.""" seconds = struct.unpack("!H", packed_seconds[:2])[0] seconds += struct.unpack("!I", packed_seconds[2:])[0] return seconds def h2i(self, pkt, seconds): """Convert the number of seconds since 1-Jan-70 UTC to the packed representation.""" if seconds is None: seconds = 0 tmp_short = (seconds >> 32) & 0xFFFF tmp_int = seconds & 0xFFFFFFFF return struct.pack("!HI", tmp_short, tmp_int) def i2h(self, pkt, packed_seconds): """Convert the internal representation to the number of seconds since 1-Jan-70 UTC.""" if packed_seconds is None: return None return self._convert_seconds(packed_seconds) def i2repr(self, pkt, packed_seconds): """Convert the internal representation to a nice one using the RFC format.""" time_struct = time.gmtime(self._convert_seconds(packed_seconds)) return time.strftime("%a %b %d %H:%M:%S %Y", time_struct) class DNSRRTSIG(_DNSRRdummy): name = "DNS TSIG Resource Record" fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 250, dnstypes), ShortEnumField("rclass", 1, dnsclasses), IntField("ttl", 0), ShortField("rdlen", None), DNSStrField("algo_name", "hmac-sha1"), TimeSignedField("time_signed", 0), ShortField("fudge", 0), FieldLenField("mac_len", 20, fmt="!H", length_of="mac_data"), # noqa: E501 StrLenField("mac_data", "", length_from=lambda pkt: pkt.mac_len), # noqa: E501 ShortField("original_id", 0), ShortField("error", 0), FieldLenField("other_len", 0, fmt="!H", length_of="other_data"), # noqa: E501 StrLenField("other_data", "", length_from=lambda pkt: pkt.other_len) # noqa: E501 ] DNSRR_DISPATCHER = { 6: DNSRRSOA, # RFC 1035 15: DNSRRMX, # RFC 1035 33: DNSRRSRV, # RFC 2782 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 250: DNSRRTSIG, # RFC 2845 32769: DNSRRDLV, # RFC 4431 } DNSSEC_CLASSES = tuple(six.itervalues(DNSRR_DISPATCHER)) def isdnssecRR(obj): return isinstance(obj, DNSSEC_CLASSES) class DNSRR(InheritOriginDNSStrPacket): name = "DNS Resource Record" show_indent = 0 fields_desc = [DNSStrField("rrname", ""), ShortEnumField("type", 1, dnstypes), ShortEnumField("rclass", 1, dnsclasses), IntField("ttl", 0), FieldLenField("rdlen", None, length_of="rdata", fmt="H"), MultipleTypeField( [ # A (IPField("rdata", "0.0.0.0"), lambda pkt: pkt.type == 1), # AAAA (IP6Field("rdata", "::"), lambda pkt: pkt.type == 28), # NS, MD, MF, CNAME, PTR (DNSStrField("rdata", "", length_from=lambda pkt: pkt.rdlen), lambda pkt: pkt.type in [2, 3, 4, 5, 12]), # TEXT (DNSTextField("rdata", [], length_from=lambda pkt: pkt.rdlen), lambda pkt: pkt.type == 16), ], StrLenField("rdata", "", length_from=lambda pkt:pkt.rdlen) )] bind_layers(UDP, DNS, dport=5353) bind_layers(UDP, DNS, sport=5353) bind_layers(UDP, DNS, dport=53) bind_layers(UDP, DNS, sport=53) DestIPField.bind_addr(UDP, "224.0.0.251", dport=5353) DestIP6Field.bind_addr(UDP, "ff02::fb", dport=5353) bind_layers(TCP, DNS, dport=53) bind_layers(TCP, 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")], # noqa: E501 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")], # noqa: E501 ns=[DNSRR(rrname=name, type=type, rclass="ANY", ttl=0, rdata="")]), # noqa: E501 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