diff --git a/scapy/contrib/dce_rpc.py b/scapy/contrib/dce_rpc.py index 86ede267536..61aa179e12f 100644 --- a/scapy/contrib/dce_rpc.py +++ b/scapy/contrib/dce_rpc.py @@ -23,13 +23,10 @@ """ # TODO: namespace locally used fields -import re -import struct from scapy.packet import Packet, Raw, bind_layers -from scapy.fields import Field, BitEnumField, ByteEnumField, ByteField, \ - FlagsField, IntField, LenField, ShortField, XByteField, XShortField -from scapy.volatile import RandField, RandNum, RandInt, RandShort, RandByte -import scapy.modules.six +from scapy.fields import BitEnumField, ByteEnumField, ByteField, \ + FlagsField, IntField, LenField, ShortField, UUIDField, XByteField, \ + XShortField # Fields @@ -45,8 +42,13 @@ def set_endianess(self, pkt): """Add the endianness to the format""" end = self.endianess_from(pkt) if isinstance(end, str) and len(end) > 0: - # fld.fmt should always start with a order specifier, cf field init - self.fld.fmt = end[0] + self.fld.fmt[1:] + if isinstance(self.fld, UUIDField): + self.fld.uuid_fmt = (UUIDField.FORMAT_LE if end == '<' + else UUIDField.FORMAT_BE) + else: + # fld.fmt should always start with a order specifier, cf field + # init + self.fld.fmt = end[0] + self.fld.fmt[1:] def getfield(self, pkt, buf): """retrieve the field with endianness""" @@ -62,88 +64,6 @@ def __getattr__(self, attr): return getattr(self.fld, attr) -class UUIDField(Field): - """UUID Field""" - __slots__ = ["reg"] - - def __init__(self, name, default): - # create and compile the regex used to extract the uuid values from str - reg = r"^\s*{0}-{1}-{1}-{2}{2}-{2}{2}{2}{2}{2}{2}\s*$".format( - "([0-9a-f]{8})", "([0-9a-f]{4})", "([0-9a-f]{2})" - ) - self.reg = re.compile(reg, re.I) - - Field.__init__(self, name, default, fmt="I2H8B") - - def i2m(self, pkt, val): - if val is None: - return (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - - if isinstance(val, bytearray) or isinstance(val, str): # py3 concern - # use the regex to extract the values - match = self.reg.match(val) - if match: - # we return a tuple of values after parsing them to integer - return tuple([int(i, 16) for i in match.groups()]) - else: - return (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - else: - return val - - def m2i(self, pkt, val): - return ("%08x-%04x-%04x-%02x%02x-" + "%02x" * 6) % val - - def any2i(self, pkt, val): - if isinstance(val, bytearray) and len(val) == 16: - return self.getfield(pkt, val)[1] - elif isinstance(val, bytearray): - return val.lower() - return val - - def addfield(self, pkt, s, val): - return s + struct.pack(self.fmt, *self.i2m(pkt, val)) - - def getfield(self, pkt, s): - return s[16:], self.m2i(pkt, struct.unpack(self.fmt, s[:16])) - - @staticmethod - def randval(): - return RandUUID() - - -class RandUUID(RandField): - """generate a random UUID""" - def __init__(self, template="*-*-*-**-******"): - base = "([0-9a-f]{{{0}}}|\\*|[0-9a-f]{{{0}}}:[0-9a-f]{{{0}}})" - reg = re.compile( - r"^\s*{0}-{1}-{1}-{2}{2}-{2}{2}{2}{2}{2}{2}\s*$".format( - base.format(8), base.format(4), base.format(2) - ), - re.I - ) - - tmp = reg.match(template) - if tmp: - template = tmp.groups() - else: - template = ["*"] * 11 - - rnd_f = [RandInt] + [RandShort] * 2 + [RandByte] * 8 - self.uuid = () - for i in scapy.modules.six.moves.range(11): - if template[i] == "*": - val = rnd_f[i]() - elif ":" in template[i]: - mini, maxi = template[i].split(":") - val = RandNum(int(mini, 16), int(maxi, 16)) - else: - val = int(template[i], 16) - self.uuid += (val,) - - def _fix(self): - return ("%08x-%04x-%04x-%02x%02x-" + "%02x" * 6) % self.uuid - - # DCE/RPC Packet DCE_RPC_TYPE = ["request", "ping", "response", "fault", "working", "no_call", "reject", "acknowledge", "connectionless_cancel", "frag_ack", diff --git a/scapy/contrib/dce_rpc.uts b/scapy/contrib/dce_rpc.uts index 85a95ba7963..dd2065cfb4b 100644 --- a/scapy/contrib/dce_rpc.uts +++ b/scapy/contrib/dce_rpc.uts @@ -4,6 +4,7 @@ = Import the DCE/RPC layer import re from scapy.contrib.dce_rpc import * +from uuid import UUID + Check EndiannessField @@ -40,46 +41,24 @@ f.getfield(None, '0102030405') == (b'', '0102030405') f = EndiannessField(StrField('f', 0), lambda p: '>') f.addfield(None, b'01', '02030405') == b'0102030405' += Little Endian UUIDField getfield +* The endianness of a UUIDField should be apply by block on each block in +* parenthesis '(01234567)-(89ab)-(cdef)-(01)(23)-(45)(67)(89)(ab)(cd)(ef)' +f = EndiannessField(UUIDField('f', None), lambda p: '<') +f.getfield(None, hex_bytes('0123456789abcdef0123456789abcdef')) == (b'', UUID('67452301-ab89-efcd-0123-456789abcdef')) -+ Check UUIDField - -= Parsing a human-readable UUID -f = UUIDField('f', '01234567-89ab-cdef-0123-456789abcdef') -f.addfield(None, b'', f.default) == bytearray.fromhex('0123456789abcdef0123456789abcdef') - -= Parsing a machine-encoded UUID -f = UUIDField('f', bytearray.fromhex('0123456789abcdef0123456789abcdef')) -f.addfield(None, b'', f.default) == bytearray.fromhex('0123456789abcdef0123456789abcdef') - -= Parsing a tuple of values -f = UUIDField('f', (0x01234567, 0x89ab, 0xcdef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef)) -f.addfield(None, b'', f.default) == bytearray.fromhex('0123456789abcdef0123456789abcdef') - -= Handle None values -f = UUIDField('f', None) -f.addfield(None, b'', f.default) == bytearray.fromhex('00000000000000000000000000000000') - -= Get a UUID for dissection -f = UUIDField('f', None) -f.getfield(None, bytearray.fromhex('0123456789abcdef0123456789abcdef01')) == (b'\x01', '01234567-89ab-cdef-0123-456789abcdef') - -= Verify little endianness of UUIDField -* The endianness of a UUIDField should be apply by block on each block in parenthesis '(01234567)-(89ab)-(cdef)-(01)(23)-(45)(67)(89)(ab)(cd)(ef)' += Little Endian UUIDField addfield f = EndiannessField(UUIDField('f', '01234567-89ab-cdef-0123-456789abcdef'), lambda p: '<') -f.addfield(None, b'', f.default) == bytearray.fromhex('67452301ab89efcd0123456789abcdef') - -= Verify RandUUID -re.match(r'[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}', str(RandUUID()), re.I) is not None - -= Verify RandUUID with a static part -* RandUUID template can contain static part such a 01234567-89ab-*-01*-*****ef -re.match(r'01234567-89ab-[0-9a-f]{4}-01[0-9a-f]{2}-[0-9a-f]{10}ef', str(RandUUID('01234567-89ab-*-01*-*****ef')), re.I) is not None +f.addfield(None, b'', f.default) == hex_bytes('67452301ab89efcd0123456789abcdef') -= Verify RandUUID with a range part -* RandUUID template can contain a part with a range of values such a 01234567-89ab-*-01*-****c0:c9ef -re.match(r'01234567-89ab-[0-9a-f]{4}-01[0-9a-f]{2}-[0-9a-f]{8}c[0-9]ef', str(RandUUID('01234567-89ab-*-01*-****c0:c9ef')), re.I) is not None += Big Endian UUIDField getfield +f = EndiannessField(UUIDField('f', None), lambda p: '>') +f.getfield(None, hex_bytes('0123456789abcdef0123456789abcdef')) == (b'', UUID('01234567-89ab-cdef-0123456789abcdef')) += Big Endian UUIDField addfield +f = EndiannessField(UUIDField('f', '01234567-89ab-cdef-0123-456789abcdef'), lambda p: '>') +f.addfield(None, b'', f.default) == hex_bytes('0123456789abcdef0123456789abcdef') + Check DCE/RPC layer diff --git a/scapy/contrib/opc_da.py b/scapy/contrib/opc_da.py index db9f9f81d17..887d0d99a22 100644 --- a/scapy/contrib/opc_da.py +++ b/scapy/contrib/opc_da.py @@ -36,16 +36,13 @@ Using the website: https://msdn.microsoft.com/en-us/library/cc226801.aspx """ -import uuid - -from scapy.compat import plain_str from scapy.config import conf from scapy.fields import Field, ByteField, ShortField, LEShortField, \ IntField, LEIntField, LongField, LELongField, StrField, StrLenField, \ StrFixedLenField, BitEnumField, ByteEnumField, ShortEnumField, \ LEShortEnumField, IntEnumField, LEIntEnumField, FieldLenField, \ LEFieldLenField, PacketField, PacketListField, PacketLenField, \ - ConditionalField, FlagsField + ConditionalField, FlagsField, UUIDField from scapy.packet import Packet # Defined values @@ -271,58 +268,6 @@ def extract_padding(self, p): return b"", p -class PUUID(Field): - __slots__ = ["endianType"] - - def __init__(self, name, default, endianType): - _repr = {0: 'bigEndian', 1: 'littleEndian'} - Field.__init__(self, name, default, "16s") - self.endianType = _repr[endianType] - - def h2i(self, pkt, x): - """ Description: transform a string uuid in uuid type object """ - self.default = uuid.UUID(plain_str(x or "")) - return self.default - - def i2h(self, pkt, x): - """ Description: transform a type uuid object in string uuid """ - x = str(self.default) - return x - - def m2i(self, pkt, x): - """ Description: transform a byte string uuid in uuid type object """ - if self.endianType == 'bigEndian': - self.default = uuid.UUID(bytes=x) - elif self.endianType == 'littleEndian': - self.default = uuid.UUID(bytes_le=x) - return self.default - - def i2m(self, pkt, x): - """ Description: transform a uuid type object in a byte string """ - if self.endianType == 'bigEndian': - x = self.default.bytes - elif self.endianType == 'littleEndian': - x = self.default.bytes_le - return x - - def i2repr(self, pkt, x): - return str(self.default) - - def getfield(self, pkt, s): - """ Extract an internal value from a string """ - self.default = self.m2i(pkt, s[:self.sz]) - return s[self.sz:], self.default - - def addfield(self, pkt, s, val): - """ Add an internal value to a string """ - return s + self.i2m(pkt, val) - - def any2i(self, pkt, x): - """ Try to understand the most input values possible and make an - internal value from them """ - return self.h2i(pkt, x) - - class LenStringPacket(Packet): name = "len string packet" fields_desc = [ @@ -358,7 +303,8 @@ def extract_padding(self, p): class SyntaxId(Packet): name = "syntax Id" fields_desc = [ - PUUID('interfaceUUID', str('0001' * 8), 0), + UUIDField('interfaceUUID', str('0001' * 8), + uuid_fmt=UUIDField.FORMAT_BE), ShortField('versionMajor', 0), ShortField('versionMinor', 0), ] @@ -370,7 +316,8 @@ def extract_padding(self, p): class SyntaxIdLE(Packet): name = "syntax Id" fields_desc = [ - PUUID('interfaceUUID', str('0001' * 8), 1), + UUIDField('interfaceUUID', str('0001' * 8), + uuid_fmt=UUIDField.FORMAT_LE), LEShortField('versionMajor', 0), LEShortField('versionMinor', 0), ] @@ -471,7 +418,7 @@ class STDOBJREF(Packet): LEIntField('cPublicRefs', 0), LELongField('OXID', 0), LELongField('OID', 0), - PacketField('IPID', None, PUUID), + PacketField('IPID', None, UUIDField), ] @@ -524,7 +471,7 @@ class OBJREF_HANDLER(Packet): name = "objetref stanDard" fields_desc = [ PacketField('std', None, STDOBJREF), - PUUID('clsid', str('0001' * 8), 0), + UUIDField('clsid', str('0001' * 8), uuid_fmt=UUIDField.FORMAT_BE), PacketField('saResAddr', None, DualStringArray), ] @@ -533,7 +480,7 @@ class OBJREF_HANDLERLE(Packet): name = "objetref stanDard" fields_desc = [ PacketField('std', None, STDOBJREF), - PUUID('clsid', str('0001' * 8), 1), + UUIDField('clsid', str('0001' * 8), uuid_fmt=UUIDField.FORMAT_LE), PacketField('saResAddr', None, DualStringArrayLE), ] @@ -541,7 +488,7 @@ class OBJREF_HANDLERLE(Packet): class OBJREF_CUSTOM(Packet): name = "objetref stanDard" fields_desc = [ - PUUID('clsid', str('0001' * 8), 0), + UUIDField('clsid', str('0001' * 8), uuid_fmt=UUIDField.FORMAT_BE), IntField('cbExtension', 0), IntField('reserved', 0), ] @@ -550,7 +497,7 @@ class OBJREF_CUSTOM(Packet): class OBJREF_CUSTOMLE(Packet): name = "objetref stanDard" fields_desc = [ - PUUID('clsid', str('0001' * 8), 1), + UUIDField('clsid', str('0001' * 8), uuid_fmt=UUIDField.FORMAT_LE), LEIntField('cbExtension', 0), LEIntField('reserved', 0), ] @@ -946,7 +893,7 @@ class RequestSubDataLE(Packet): LEShortField('versionMinor', 0), LEIntField('flags', 0), LEIntField('reserved', 0), - PUUID('subUuid', str('0001' * 8), 1), + UUIDField('subUuid', str('0001' * 8), uuid_fmt=UUIDField.FORMAT_LE), StrField('subdata', ''), ] @@ -960,7 +907,7 @@ class OpcDaRequest(Packet): IntField('allocHint', 0), ShortField('contextId', 0), ShortField('opNum', 0), - PUUID('uuid', str('0001' * 8), 0), + UUIDField('uuid', str('0001' * 8), uuid_fmt=UUIDField.FORMAT_BE), PacketLenField('subData', None, RequestSubData, length_from=lambda pkt:pkt.allocHint), PacketField('authentication', None, AuthentificationProtocol), @@ -976,7 +923,7 @@ class OpcDaRequestLE(Packet): LEIntField('allocHint', 0), LEShortField('contextId', 0), LEShortField('opNum', 0), - PUUID('uuid', str('0001' * 8), 1), + UUIDField('uuid', str('0001' * 8), uuid_fmt=UUIDField.FORMAT_LE), PacketLenField('subData', None, RequestSubDataLE, length_from=lambda pkt:pkt.allocHint), PacketField('authentication', None, AuthentificationProtocol), diff --git a/scapy/contrib/pnio_rpc.py b/scapy/contrib/pnio_rpc.py index f7fc3c9a053..d71468645be 100644 --- a/scapy/contrib/pnio_rpc.py +++ b/scapy/contrib/pnio_rpc.py @@ -22,16 +22,18 @@ """ import struct +from uuid import UUID + from scapy.packet import Packet, bind_layers from scapy.config import conf -from scapy.fields import BitField, ByteField, BitEnumField, \ +from scapy.fields import BitField, ByteField, BitEnumField, ConditionalField, \ FieldLenField, FieldListField, IntField, IntEnumField, \ LenField, MACField, PadField, PacketField, PacketListField, \ ShortEnumField, ShortField, StrFixedLenField, StrLenField, \ - XByteField, XIntField, XShortEnumField, XShortField, ConditionalField -from scapy.contrib.dce_rpc import DceRpc, EndiannessField, DceRpcPayload, \ - UUIDField, RandUUID + UUIDField, XByteField, XIntField, XShortEnumField, XShortField +from scapy.contrib.dce_rpc import DceRpc, EndiannessField, DceRpcPayload from scapy.compat import bytes_hex +from scapy.volatile import RandUUID # Block Packet BLOCK_TYPES_ENUM = { @@ -275,14 +277,19 @@ } -# List of all valid activity uuid for the DceRpc -# layer with PROFINET RPC endpoint +# List of all valid activity UUIDs for the DceRpc layer with PROFINET RPC +# endpoint. +# +# Because these are used in overloaded_fields, it must be a ``UUID``, not a +# string. RPC_INTERFACE_UUID = { - "UUID_IO_DeviceInterface": "dea00001-6c97-11d1-8271-00a02442df7d", - "UUID_IO_ControllerInterface": "dea00002-6c97-11d1-8271-00a02442df7d", - "UUID_IO_SupervisorInterface": "dea00003-6c97-11d1-8271-00a02442df7d", + "UUID_IO_DeviceInterface": UUID("dea00001-6c97-11d1-8271-00a02442df7d"), + "UUID_IO_ControllerInterface": + UUID("dea00002-6c97-11d1-8271-00a02442df7d"), + "UUID_IO_SupervisorInterface": + UUID("dea00003-6c97-11d1-8271-00a02442df7d"), "UUID_IO_ParameterServerInterface": - "dea00004-6c97-11d1-8271-00a02442df7d", + UUID("dea00004-6c97-11d1-8271-00a02442df7d"), } @@ -963,7 +970,7 @@ def can_handle(cls, pkt, rpc): """heuristical guess_payload_class""" # type = 0 => request if rpc.getfieldval("type") == 0 and \ - rpc.object_uuid.startswith("dea00000-6c97-11d1-8271-"): + str(rpc.object_uuid).startswith("dea00000-6c97-11d1-8271-"): return True return False @@ -995,7 +1002,7 @@ def can_handle(cls, pkt, rpc): """heuristical guess_payload_class""" # type = 2 => response if rpc.getfieldval("type") == 2 and \ - rpc.object_uuid.startswith("dea00000-6c97-11d1-8271-"): + str(rpc.object_uuid).startswith("dea00000-6c97-11d1-8271-"): return True return False diff --git a/scapy/contrib/pnio_rpc.uts b/scapy/contrib/pnio_rpc.uts index d38c428e65e..3f87d3406ea 100644 --- a/scapy/contrib/pnio_rpc.uts +++ b/scapy/contrib/pnio_rpc.uts @@ -5,6 +5,10 @@ from scapy.contrib.dce_rpc import * from scapy.contrib.pnio_rpc import * += Check that we have UUIDs + +for v in RPC_INTERFACE_UUID.values(): + assert(isinstance(v, UUID)) + Check Block diff --git a/scapy/fields.py b/scapy/fields.py index 431d88b8952..26ea68a1d6a 100644 --- a/scapy/fields.py +++ b/scapy/fields.py @@ -1,6 +1,8 @@ +# -*- mode: python3; indent-tabs-mode: nil; tab-width: 4 -*- # This file is part of Scapy # See http://www.secdev.org/projects/scapy for more information # Copyright (C) Philippe Biondi +# Copyright (C) Michael Farrell # This program is published under a GPLv2 license """ @@ -15,13 +17,14 @@ import struct import time from types import MethodType +from uuid import UUID from scapy.config import conf from scapy.dadict import DADict from scapy.volatile import RandBin, RandByte, RandEnumKeys, RandInt, \ RandIP, RandIP6, RandLong, RandMAC, RandNum, RandShort, RandSInt, \ - RandSByte, RandTermString, VolatileValue + RandSByte, RandTermString, RandUUID, VolatileValue from scapy.data import EPOCH from scapy.error import log_runtime, Scapy_Exception from scapy.compat import bytes_hex, chb, orb, plain_str, raw, bytes_encode @@ -2273,3 +2276,129 @@ def randval(self): max_value = int(max(barrier1, barrier2)) return RandNum(min_value, max_value) + + +class UUIDField(Field): + """Field for UUID storage, wrapping Python's uuid.UUID type. + + The internal storage format of this field is ``uuid.UUID`` from the Python + standard library. + + There are three formats (``uuid_fmt``) for this field type:: + + * ``FORMAT_BE`` (default): the UUID is six fields in big-endian byte order, + per RFC 4122. + + This format is used by DHCPv6 (RFC 6355) and most network protocols. + + * ``FORMAT_LE``: the UUID is six fields, with ``time_low``, ``time_mid`` + and ``time_high_version`` in little-endian byte order. This _doesn't_ + change the arrangement of the fields from RFC 4122. + + This format is used by Microsoft's COM/OLE libraries. + + * ``FORMAT_REV``: the UUID is a single 128-bit integer in little-endian + byte order. This _changes the arrangement_ of the fields. + + This format is used by Bluetooth Low Energy. + + Note: You should use the constants here. + + The "human encoding" of this field supports a number of different input + formats, and wraps Python's ``uuid.UUID`` library appropriately:: + + * Given a bytearray, bytes or str of 16 bytes, this class decodes UUIDs in + wire format. + + * Given a bytearray, bytes or str of other lengths, this delegates to + ``uuid.UUID`` the Python standard library. This supports a number of + different encoding options -- see the Python standard library + documentation for more details. + + * Given an int or long, presumed to be a 128-bit integer to pass to + ``uuid.UUID``. + + * Given a tuple: + + * Tuples of 11 integers are treated as having the last 6 integers forming + the ``node`` field, and are merged before being passed as a tuple of 6 + integers to ``uuid.UUID``. + + * Otherwise, the tuple is passed as the ``fields`` parameter to + ``uuid.UUID`` directly without modification. + + ``uuid.UUID`` expects a tuple of 6 integers. + + Other types (such as ``uuid.UUID``) are passed through. + """ + + __slots__ = ["uuid_fmt"] + + FORMAT_BE = 0 + FORMAT_LE = 1 + FORMAT_REV = 2 + + # Change this when we get new formats + FORMATS = (FORMAT_BE, FORMAT_LE, FORMAT_REV) + + def __init__(self, name, default, uuid_fmt=FORMAT_BE): + self.uuid_fmt = uuid_fmt + self._check_uuid_fmt() + Field.__init__(self, name, default, "16s") + + def _check_uuid_fmt(self): + """Checks .uuid_fmt, and raises an exception if it is not valid.""" + if self.uuid_fmt not in UUIDField.FORMATS: + raise FieldValueRangeException( + "Unsupported uuid_fmt ({})".format(self.uuid_fmt)) + + def i2m(self, pkt, x): + self._check_uuid_fmt() + if x is None: + return b'\0' * 16 + if self.uuid_fmt == UUIDField.FORMAT_BE: + return x.bytes + elif self.uuid_fmt == UUIDField.FORMAT_LE: + return x.bytes_le + elif self.uuid_fmt == UUIDField.FORMAT_REV: + return x.bytes[::-1] + + def m2i(self, pkt, x): + self._check_uuid_fmt() + if self.uuid_fmt == UUIDField.FORMAT_BE: + return UUID(bytes=x) + elif self.uuid_fmt == UUIDField.FORMAT_LE: + return UUID(bytes_le=x) + elif self.uuid_fmt == UUIDField.FORMAT_REV: + return UUID(bytes=x[::-1]) + + def any2i(self, pkt, x): + # Python's uuid doesn't handle bytearray, so convert to an immutable + # type first. + if isinstance(x, bytearray): + x = bytes(x) + + if isinstance(x, six.integer_types): + x = UUID(int=x) + elif isinstance(x, tuple): + if len(x) == 11: + # For compatibility with dce_rpc: this packs into a tuple where + # elements 7..10 are the 48-bit node ID. + node = 0 + for i in x[5:]: + node = (node << 8) | i + + x = (x[0], x[1], x[2], x[3], x[4], node) + + x = UUID(fields=x) + elif isinstance(x, (six.binary_type, six.text_type)): + if len(x) == 16: + # Raw bytes + x = self.m2i(pkt, x) + else: + x = UUID(plain_str(x)) + return x + + @staticmethod + def randval(): + return RandUUID() diff --git a/scapy/layers/bluetooth.py b/scapy/layers/bluetooth.py index 575693c1a09..96311d83c7f 100644 --- a/scapy/layers/bluetooth.py +++ b/scapy/layers/bluetooth.py @@ -14,7 +14,6 @@ import struct from select import select from ctypes import sizeof -from uuid import UUID from scapy.config import conf from scapy.data import DLT_BLUETOOTH_HCI_H4, DLT_BLUETOOTH_HCI_H4_WITH_PHDR @@ -22,7 +21,7 @@ from scapy.fields import ByteEnumField, ByteField, Field, FieldLenField, \ FieldListField, FlagsField, IntField, LEShortEnumField, LEShortField, \ LenField, PacketListField, SignedByteField, StrField, StrFixedLenField, \ - StrLenField, XByteField, BitField, XLELongField, PadField + StrLenField, XByteField, BitField, XLELongField, PadField, UUIDField from scapy.supersocket import SuperSocket from scapy.sendrecv import sndrcv from scapy.data import MTU @@ -31,7 +30,6 @@ from scapy.utils import lhex, mac2str, str2mac from scapy.volatile import RandMAC from scapy.modules import six -from scapy.compat import plain_str ########## @@ -70,61 +68,6 @@ def randval(self): return RandMAC() -class BTUUID128Field(Field): - """Field for Bluetooth UUID storage, wrapping Python's uuid.UUID type. - - Bluetooth stores UUIDs as a 128-bit little-endian integer. This differs - from most other encodings, which only change the endianness of the first - three component integers (time_low, time_mid, time_high_version), but - otherwise emit the fields in a consistent order. - - The internal storage format of this field is ``uuid.UUID`` from the Python - standard library. - - The "human encoding" of this field supports a number of different input - formats, and wraps Python's ``uuid.UUID`` library appropriately: - - * Given a bytearray or str of 16 bytes, this class decodes UUIDs in - Bluetooth wire format. - - * Given a bytearray or str of other lengths, this delegates to - ``uuid.UUID`` the Python standard library. This supports a number of - different encoding options -- see the Python standard library - documentation for more details. - - * Given an int or long, presumed to be a 128-bit integer to pass to - ``uuid.UUID``. - - * Given a tuple, this is presumed to be a tuple of 6 integers to pass to - ``uuid.UUID``. - - Other types (such as ``uuid.UUID``) are passed through. - """ - def __init__(self, name, default): - Field.__init__(self, name, default, "16s") - - def i2m(self, pkt, x): - if x is None: - return bytes(16) - return x.bytes[::-1] - - def m2i(self, pkt, x): - return UUID(bytes=x[::-1]) - - def any2i(self, pkt, x): - if isinstance(x, six.integer_types): - x = UUID(int=x) - elif isinstance(x, tuple): - x = UUID(fields=x) - elif isinstance(x, (six.binary_type, six.text_type)): - if len(x) == 16: - # Raw bytes - x = self.m2i(pkt, x) - else: - x = UUID(plain_str(x)) - return x - - ########## # Layers # ########## @@ -644,7 +587,8 @@ class EIR_IncompleteList16BitServiceUUIDs(EIR_CompleteList16BitServiceUUIDs): class EIR_CompleteList128BitServiceUUIDs(EIR_Element): name = "Complete list of 128-bit service UUIDs" fields_desc = [ - FieldListField("svc_uuids", None, BTUUID128Field("uuid", None), + FieldListField("svc_uuids", None, + UUIDField("uuid", None, uuid_fmt=UUIDField.FORMAT_REV), length_from=EIR_Element.length_from) ] diff --git a/scapy/layers/dhcp6.py b/scapy/layers/dhcp6.py index 7487d4c9a9e..cd0d7f29774 100644 --- a/scapy/layers/dhcp6.py +++ b/scapy/layers/dhcp6.py @@ -25,7 +25,7 @@ FlagsField, IntEnumField, IntField, MACField, PacketField, \ PacketListField, ShortEnumField, ShortField, StrField, StrFixedLenField, \ StrLenField, UTCTimeField, X3BytesField, XIntField, XShortEnumField, \ - PacketLenField + PacketLenField, UUIDField from scapy.layers.inet import UDP from scapy.layers.inet6 import DomainNameListField, IP6Field, IP6ListField, \ IPv6 @@ -285,7 +285,7 @@ class DUID_LL(Packet): # sect 9.4 RFC 3315 class DUID_UUID(Packet): # RFC 6355 name = "DUID - Based on UUID" fields_desc = [ShortEnumField("type", 4, duidtypes), - StrFixedLenField("uuid", "", 16)] + UUIDField("uuid", None, uuid_fmt=UUIDField.FORMAT_BE)] duid_cls = {1: "DUID_LLT", diff --git a/scapy/volatile.py b/scapy/volatile.py index d1afcceb5ec..7710359afa5 100644 --- a/scapy/volatile.py +++ b/scapy/volatile.py @@ -1,6 +1,8 @@ # This file is part of Scapy # See http://www.secdev.org/projects/scapy for more information # Copyright (C) Philippe Biondi +# Copyright (C) Michael Farrell +# Copyright (C) Gauthier Sebaux # This program is published under a GPLv2 license """ @@ -11,6 +13,9 @@ import random import time import math +import re +import uuid + from scapy.base_classes import Net from scapy.compat import bytes_encode, chb, plain_str from scapy.utils import corrupt_bits, corrupt_bytes @@ -783,6 +788,130 @@ def _fix(self): r = random.choice(self._pool) return r._fix() + +class RandUUID(RandField): + """Generates a random UUID. + + By default, this generates a RFC 4122 version 4 UUID (totally random). + + See Python's ``uuid`` module documentation for more information. + + Args: + template (optional): A template to build the UUID from. Not valid with + any other option. + node (optional): A 48-bit Host ID. Only valid for version 1 (where it + is optional). + clock_seq (optional): An integer of up to 14-bits for the sequence + number. Only valid for version 1 (where it is + optional). + namespace: A namespace identifier, which is also a UUID. Required for + versions 3 and 5, must be omitted otherwise. + name: string, required for versions 3 and 5, must be omitted otherwise. + version: Version of UUID to use (1, 3, 4 or 5). If omitted, attempts to + guess which version to generate, defaulting to version 4 + (totally random). + + Raises: + ValueError: on invalid constructor arguments + """ + # This was originally scapy.contrib.dce_rpc.RandUUID. + + _BASE = "([0-9a-f]{{{0}}}|\\*|[0-9a-f]{{{0}}}:[0-9a-f]{{{0}}})" + _REG = re.compile( + r"^{0}-?{1}-?{1}-?{2}{2}-?{2}{2}{2}{2}{2}{2}$".format( + _BASE.format(8), _BASE.format(4), _BASE.format(2) + ), + re.I + ) + VERSIONS = (1, 3, 4, 5) + + def __init__(self, template=None, node=None, clock_seq=None, + namespace=None, name=None, version=None): + self.uuid_template = None + self.node = None + self.clock_seq = None + self.namespace = None + self.node = None + self.version = None + + if template: + if node or clock_seq or namespace or name or version: + raise ValueError("UUID template must be the only parameter, " + "if specified") + tmp = RandUUID._REG.match(template) + if tmp: + template = tmp.groups() + else: + # Invalid template + raise ValueError("UUID template is invalid") + + rnd_f = [RandInt] + [RandShort] * 2 + [RandByte] * 8 + uuid_template = [] + for i, t in enumerate(template): + if t == "*": + val = rnd_f[i]() + elif ":" in t: + mini, maxi = t.split(":") + val = RandNum(int(mini, 16), int(maxi, 16)) + else: + val = int(t, 16) + uuid_template.append(val) + + self.uuid_template = tuple(uuid_template) + else: + if version: + if version not in RandUUID.VERSIONS: + raise ValueError("version is not supported") + else: + self.version = version + else: + # No version specified, try to guess... + # This could be wrong, and cause an error later! + if node or clock_seq: + self.version = 1 + elif namespace and name: + self.version = 5 + else: + # Don't know, random! + self.version = 4 + + # We have a version, now do things... + if self.version == 1: + if namespace or name: + raise ValueError("namespace and name may not be used with " + "version 1") + self.node = node + self.clock_seq = clock_seq + elif self.version in (3, 5): + if node or clock_seq: + raise ValueError("node and clock_seq may not be used with " + "version {}".format(self.version)) + + self.namespace = namespace + self.name = name + elif self.version == 4: + if namespace or name or node or clock_seq: + raise ValueError("node, clock_seq, node and clock_seq may " + "not be used with version 4. If you " + "did not specify version, you need to " + "specify it explicitly.") + + def _fix(self): + if self.uuid_template: + return uuid.UUID(("%08x%04x%04x" + ("%02x" * 8)) + % self.uuid_template) + elif self.version == 1: + return uuid.uuid1(self.node, self.clock_seq) + elif self.version == 3: + return uuid.uuid3(self.namespace, self.name) + elif self.version == 4: + return uuid.uuid4() + elif self.version == 5: + return uuid.uuid5(self.namespace, self.name) + else: + raise ValueError("Unhandled version") + + # Automatic timestamp diff --git a/test/regression.uts b/test/regression.uts index 689137e1095..5794c8f32c1 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -6,6 +6,14 @@ ############ + Information on Scapy += Setup +def expect_exception(e, c): + try: + c() + return False + except e: + return True + = Get conf ~ conf command * Dump the current configuration @@ -4376,6 +4384,29 @@ a=DUID_LL(b'\x00\x03\x00\x01\xff\xff\xff\xff\xff\xff') a.hwtype == 1 and a.lladdr == "ff:ff:ff:ff:ff:ff" +############ +############ ++ Test DHCP6 DUID_UUID + += DUID_UUID basic instantiation +a=DUID_UUID() + += DUID_UUID basic build +raw(DUID_UUID()) == b"\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + += DUID_UUID build with specific values +raw(DUID_UUID(uuid="272adcca-138c-4e8d-b3f4-634e953128cf")) == \ + b"\x00\x04'*\xdc\xca\x13\x8cN\x8d\xb3\xf4cN\x951(\xcf" + += DUID_UUID basic dissection +a=DUID_UUID(raw(DUID_UUID())) +a.type == 4 and str(a.uuid) == "00000000-0000-0000-0000-000000000000" + += DUID_UUID with specific values +a=DUID_UUID(b"\x00\x04'*\xdc\xca\x13\x8cN\x8d\xb3\xf4cN\x951(\xcf") +a.type == 4 and str(a.uuid) == "272adcca-138c-4e8d-b3f4-634e953128cf" + + ############ ############ + Test DHCP6 Opt Unknown @@ -9937,8 +9968,8 @@ len(r4.routes) == len_r4 = RandomEnumeration -re = RandomEnumeration(0, 7, seed=0x2807, forever=False) -[x for x in re] == ([3, 4, 2, 5, 1, 6, 0, 7] if six.PY2 else [5, 0, 2, 7, 6, 3, 1, 4]) +ren = RandomEnumeration(0, 7, seed=0x2807, forever=False) +[x for x in ren] == ([3, 4, 2, 5, 1, 6, 0, 7] if six.PY2 else [5, 0, 2, 7, 6, 3, 1, 4]) = RandIP6 @@ -9979,8 +10010,8 @@ assert(ro == ("1.2.3.11" if six.PY2 else "1.2.3.12")) = RandRegExp random.seed(0x2807) -re = RandRegExp("[g-v]* @? [0-9]{3} . (g|v)") -bytes(re) == ('vmuvr @ 906 \x9e g' if six.PY2 else b'irrtv @ 517 \xc2\xb8 v') +rex = RandRegExp("[g-v]* @? [0-9]{3} . (g|v)") +bytes(rex) == ('vmuvr @ 906 \x9e g' if six.PY2 else b'irrtv @ 517 \xc2\xb8 v') = Corrupted(Bytes|Bits) @@ -11011,6 +11042,166 @@ assert p.inner.f_name == b"scapy" p = TestPacket() assert p.inner.f_name == b"test" ++ UUIDField + += Parsing a human-readable UUID +f = UUIDField('f', '01234567-89ab-cdef-0123-456789abcdef') +f.addfield(None, b'', f.default) == hex_bytes('0123456789abcdef0123456789abcdef') + += Parsing a machine-encoded UUID +f = UUIDField('f', bytearray.fromhex('0123456789abcdef0123456789abcdef')) +f.addfield(None, b'', f.default) == hex_bytes('0123456789abcdef0123456789abcdef') + += Parsing a tuple of values +f = UUIDField('f', (0x01234567, 0x89ab, 0xcdef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef)) +f.addfield(None, b'', f.default) == hex_bytes('0123456789abcdef0123456789abcdef') + += Handle None values +f = UUIDField('f', None) +f.addfield(None, b'', f.default) == hex_bytes('00000000000000000000000000000000') + += Get a UUID for dissection +from uuid import UUID +f = UUIDField('f', None) +f.getfield(None, bytearray.fromhex('0123456789abcdef0123456789abcdef01')) == (b'\x01', UUID('01234567-89ab-cdef-0123-456789abcdef')) + += Verify little endian UUIDField +* The endianness of a UUIDField should be apply by block on each block in parenthesis '(01234567)-(89ab)-(cdef)-(01)(23)-(45)(67)(89)(ab)(cd)(ef)' +f = UUIDField('f', '01234567-89ab-cdef-0123-456789abcdef', uuid_fmt=UUIDField.FORMAT_LE) +f.addfield(None, b'', f.default) == hex_bytes('67452301ab89efcd0123456789abcdef') + += Verify reversed UUIDField +* This should reverse the entire value as 128-bits +f = UUIDField('f', '01234567-89ab-cdef-0123-456789abcdef', uuid_fmt=UUIDField.FORMAT_REV) +f.addfield(None, b'', f.default) == hex_bytes('efcdab8967452301efcdab8967452301') + ++ RandUUID + += RandUUID setup + +RANDUUID_TEMPLATE = '01234567-89ab-*-01*-*****ef' +RANDUUID_FIXED = uuid.uuid4() + += RandUUID default behaviour + +u = RandUUID()._fix() +u.version == 4 + += RandUUID incorrect implicit args + +expect_exception(ValueError, lambda: RandUUID(node=0x1234, name="scapy")) +expect_exception(ValueError, lambda: RandUUID(node=0x1234, namespace=uuid.uuid4())) +expect_exception(ValueError, lambda: RandUUID(clock_seq=0x1234, name="scapy")) +expect_exception(ValueError, lambda: RandUUID(clock_seq=0x1234, namespace=uuid.uuid4())) +expect_exception(ValueError, lambda: RandUUID(name="scapy")) +expect_exception(ValueError, lambda: RandUUID(namespace=uuid.uuid4())) + += RandUUID v4 UUID (correct args) + +u = RandUUID(version=4)._fix() +u.version == 4 + +u2 = RandUUID(version=4)._fix() +u2.version == 4 + +str(u) != str(u2) + += RandUUID v4 UUID (incorrect args) + +expect_exception(ValueError, lambda: RandUUID(version=4, template=RANDUUID_TEMPLATE)) +expect_exception(ValueError, lambda: RandUUID(version=4, node=0x1234)) +expect_exception(ValueError, lambda: RandUUID(version=4, clock_seq=0x1234)) +expect_exception(ValueError, lambda: RandUUID(version=4, namespace=uuid.uuid4())) +expect_exception(ValueError, lambda: RandUUID(version=4, name="scapy")) + += RandUUID v1 UUID + +u = RandUUID(version=1)._fix() +u.version == 1 + +u = RandUUID(version=1, node=0x1234)._fix() +u.version == 1 +u.node == 0x1234 + +u = RandUUID(version=1, clock_seq=0x1234)._fix() +u.version == 1 +u.clock_seq == 0x1234 + +u = RandUUID(version=1, node=0x1234, clock_seq=0x1bcd)._fix() +u.version == 1 +u.node == 0x1234 +u.clock_seq == 0x1bcd + += RandUUID v1 UUID (implicit version) + +u = RandUUID(node=0x1234)._fix() +u.version == 1 +u.node == 0x1234 + +u = RandUUID(clock_seq=0x1234)._fix() +u.version == 1 +u.clock_seq == 0x1234 + +u = RandUUID(node=0x1234, clock_seq=0x1bcd)._fix() +u.version == 1 +u.node == 0x1234 +u.clock_seq == 0x1bcd + += RandUUID v1 UUID (incorrect args) + +expect_exception(ValueError, lambda: RandUUID(version=1, template=RANDUUID_TEMPLATE)) +expect_exception(ValueError, lambda: RandUUID(version=1, namespace=uuid.uuid4())) +expect_exception(ValueError, lambda: RandUUID(version=1, name="scapy")) + += RandUUID v5 UUID + +u = RandUUID(version=5, namespace=RANDUUID_FIXED, name="scapy")._fix() +u.version == 5 + +u2 = RandUUID(version=5, namespace=RANDUUID_FIXED, name="scapy")._fix() +u2.version == 5 +u.bytes == u2.bytes + +# implicit v5 +u2 = RandUUID(namespace=RANDUUID_FIXED, name="scapy")._fix() +u.bytes == u2.bytes + += RandUUID v5 UUID (incorrect args) + +expect_exception(ValueError, lambda: RandUUID(version=5, template=RANDUUID_TEMPLATE)) +expect_exception(ValueError, lambda: RandUUID(version=5, node=0x1234)) +expect_exception(ValueError, lambda: RandUUID(version=5, clock_seq=0x1234)) + += RandUUID v3 UUID + +u = RandUUID(version=3, namespace=RANDUUID_FIXED, name="scapy")._fix() +u.version == 3 + +u2 = RandUUID(version=3, namespace=RANDUUID_FIXED, name="scapy")._fix() +u2.version == 3 +u.bytes == u2.bytes + +# implicit v5 +u2 = RandUUID(namespace=RANDUUID_FIXED, name="scapy")._fix() +u.bytes != u2.bytes + += RandUUID v3 UUID (incorrect args) + +expect_exception(ValueError, lambda: RandUUID(version=5, template=RANDUUID_TEMPLATE)) +expect_exception(ValueError, lambda: RandUUID(version=5, node=0x1234)) +expect_exception(ValueError, lambda: RandUUID(version=5, clock_seq=0x1234)) + += RandUUID looks like a UUID with str +re.match(r'[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}', str(RandUUID()), re.I) is not None + += RandUUID with a static part +* RandUUID template can contain static part such a 01234567-89ab-*-01*-*****ef +re.match(r'01234567-89ab-[0-9a-f]{4}-01[0-9a-f]{2}-[0-9a-f]{10}ef', str(RandUUID('01234567-89ab-*-01*-*****ef')), re.I) is not None + += RandUUID with a range part +* RandUUID template can contain a part with a range of values such a 01234567-89ab-*-01*-****c0:c9ef +re.match(r'01234567-89ab-[0-9a-f]{4}-01[0-9a-f]{2}-[0-9a-f]{8}c[0-9]ef', str(RandUUID('01234567-89ab-*-01*-****c0:c9ef')), re.I) is not None + ############ ############ + MPLS tests