esp32_bluetooth_classic_sni.../libs/scapy/layers/dns.py
Matheus Eduardo Garbelini 86890704fd initial commit
todo: add documentation & wireshark dissector
2021-08-31 19:51:03 +08:00

990 lines
36 KiB
Python
Executable file

# This file is part of Scapy
# See http://www.secdev.org/projects/scapy for more information
# Copyright (C) Philippe Biondi <phil@secdev.org>
# 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