# 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 """ Global variables and functions for handling external data sets. """ import calendar import os import re from scapy.dadict import DADict from scapy.consts import FREEBSD, NETBSD, OPENBSD, WINDOWS from scapy.error import log_loading from scapy.compat import plain_str import scapy.modules.six as six ############ # Consts # ############ ETHER_ANY = b"\x00" * 6 ETHER_BROADCAST = b"\xff" * 6 # From bits/socket.h SOL_PACKET = 263 # From asm/socket.h SO_ATTACH_FILTER = 26 SO_TIMESTAMPNS = 35 # SO_TIMESTAMPNS_OLD: not 2038 safe ETH_P_ALL = 3 ETH_P_IP = 0x800 ETH_P_ARP = 0x806 ETH_P_IPV6 = 0x86dd ETH_P_MACSEC = 0x88e5 # From net/if_arp.h ARPHDR_ETHER = 1 ARPHDR_METRICOM = 23 ARPHDR_PPP = 512 ARPHDR_LOOPBACK = 772 ARPHDR_TUN = 65534 # From pcap/dlt.h DLT_NULL = 0 DLT_EN10MB = 1 DLT_EN3MB = 2 DLT_AX25 = 3 DLT_PRONET = 4 DLT_CHAOS = 5 DLT_IEEE802 = 6 DLT_ARCNET = 7 DLT_SLIP = 8 DLT_PPP = 9 DLT_FDDI = 10 if OPENBSD: DLT_RAW = 14 else: DLT_RAW = 12 DLT_RAW_ALT = 101 # At least in Argus if FREEBSD or NETBSD: DLT_SLIP_BSDOS = 13 DLT_PPP_BSDOS = 14 else: DLT_SLIP_BSDOS = 15 DLT_PPP_BSDOS = 16 if FREEBSD: DLT_PFSYNC = 121 else: DLT_PFSYNC = 18 DLT_HHDLC = 121 DLT_ATM_CLIP = 19 DLT_PPP_SERIAL = 50 DLT_PPP_ETHER = 51 DLT_SYMANTEC_FIREWALL = 99 DLT_C_HDLC = 104 DLT_IEEE802_11 = 105 DLT_FRELAY = 107 if OPENBSD: DLT_LOOP = 12 DLT_ENC = 13 else: DLT_LOOP = 108 DLT_ENC = 109 DLT_LINUX_SLL = 113 DLT_LTALK = 114 DLT_PFLOG = 117 DLT_PRISM_HEADER = 119 DLT_AIRONET_HEADER = 120 DLT_IP_OVER_FC = 122 DLT_IEEE802_11_RADIO = 127 DLT_ARCNET_LINUX = 129 DLT_LINUX_IRDA = 144 DLT_IEEE802_11_RADIO_AVS = 163 DLT_LINUX_LAPD = 177 DLT_BLUETOOTH_HCI_H4 = 187 DLT_USB_LINUX = 189 DLT_PPI = 192 DLT_IEEE802_15_4_WITHFCS = 195 DLT_BLUETOOTH_HCI_H4_WITH_PHDR = 201 DLT_AX25_KISS = 202 DLT_PPP_WITH_DIR = 204 DLT_FC_2 = 224 DLT_CAN_SOCKETCAN = 227 DLT_IPV4 = 228 DLT_IPV6 = 229 DLT_IEEE802_15_4_NOFCS = 230 DLT_USBPCAP = 249 DLT_NETLINK = 253 DLT_USB_DARWIN = 266 DLT_BLUETOOTH_LE_LL = 251 DLT_BLUETOOTH_LE_LL_WITH_PHDR = 256 DLT_VSOCK = 271 DLT_ETHERNET_MPACKET = 274 DLT_BLUETOOTH_ESPRESSIF = 289 # From net/ipv6.h on Linux (+ Additions) IPV6_ADDR_UNICAST = 0x01 IPV6_ADDR_MULTICAST = 0x02 IPV6_ADDR_CAST_MASK = 0x0F IPV6_ADDR_LOOPBACK = 0x10 IPV6_ADDR_GLOBAL = 0x00 IPV6_ADDR_LINKLOCAL = 0x20 IPV6_ADDR_SITELOCAL = 0x40 # deprecated since Sept. 2004 by RFC 3879 IPV6_ADDR_SCOPE_MASK = 0xF0 # IPV6_ADDR_COMPATv4 = 0x80 # deprecated; i.e. ::/96 # IPV6_ADDR_MAPPED = 0x1000 # i.e.; ::ffff:0.0.0.0/96 IPV6_ADDR_6TO4 = 0x0100 # Added to have more specific info (should be 0x0101 ?) # noqa: E501 IPV6_ADDR_UNSPECIFIED = 0x10000 # from if_arp.h ARPHRD_ETHER = 1 ARPHRD_EETHER = 2 ARPHRD_AX25 = 3 ARPHRD_PRONET = 4 ARPHRD_CHAOS = 5 ARPHRD_IEEE802 = 6 ARPHRD_ARCNET = 7 ARPHRD_DLCI = 15 ARPHRD_ATM = 19 ARPHRD_METRICOM = 23 ARPHRD_SLIP = 256 ARPHRD_CSLIP = 257 ARPHRD_SLIP6 = 258 ARPHRD_CSLIP6 = 259 ARPHRD_ADAPT = 264 ARPHRD_CAN = 280 ARPHRD_PPP = 512 ARPHRD_CISCO = 513 ARPHRD_RAWHDLC = 518 ARPHRD_TUNNEL = 768 ARPHRD_FRAD = 770 ARPHRD_LOOPBACK = 772 ARPHRD_LOCALTLK = 773 ARPHRD_FDDI = 774 ARPHRD_SIT = 776 ARPHRD_FCPP = 784 ARPHRD_FCAL = 785 ARPHRD_FCPL = 786 ARPHRD_FCFABRIC = 787 ARPHRD_IRDA = 783 ARPHRD_IEEE802_TR = 800 ARPHRD_IEEE80211 = 801 ARPHRD_IEEE80211_PRISM = 802 ARPHRD_IEEE80211_RADIOTAP = 803 ARPHRD_IEEE802154 = 804 ARPHRD_NETLINK = 824 ARPHRD_VSOCKMON = 826 # from pcap/pcap-linux.c ARPHRD_LAPD = 8445 # from pcap/pcap-linux.c ARPHRD_NONE = 0xFFFE ARPHRD_TO_DLT = { # netlink -> datalink ARPHRD_ETHER: DLT_EN10MB, ARPHRD_METRICOM: DLT_EN10MB, ARPHRD_LOOPBACK: DLT_EN10MB, ARPHRD_EETHER: DLT_EN3MB, ARPHRD_AX25: DLT_AX25_KISS, ARPHRD_PRONET: DLT_PRONET, ARPHRD_CHAOS: DLT_CHAOS, ARPHRD_CAN: DLT_LINUX_SLL, ARPHRD_IEEE802_TR: DLT_IEEE802, ARPHRD_IEEE802: DLT_IEEE802, ARPHRD_ARCNET: DLT_ARCNET_LINUX, ARPHRD_FDDI: DLT_FDDI, ARPHRD_ATM: -1, ARPHRD_IEEE80211: DLT_IEEE802_11, ARPHRD_IEEE80211_PRISM: DLT_PRISM_HEADER, ARPHRD_IEEE80211_RADIOTAP: DLT_IEEE802_11_RADIO, ARPHRD_PPP: DLT_RAW, ARPHRD_CISCO: DLT_C_HDLC, ARPHRD_SIT: DLT_RAW, ARPHRD_CSLIP: DLT_RAW, ARPHRD_SLIP6: DLT_RAW, ARPHRD_CSLIP6: DLT_RAW, ARPHRD_ADAPT: DLT_RAW, ARPHRD_SLIP: DLT_RAW, ARPHRD_RAWHDLC: DLT_RAW, ARPHRD_DLCI: DLT_RAW, ARPHRD_FRAD: DLT_FRELAY, ARPHRD_LOCALTLK: DLT_LTALK, 18: DLT_IP_OVER_FC, ARPHRD_FCPP: DLT_FC_2, ARPHRD_FCAL: DLT_FC_2, ARPHRD_FCPL: DLT_FC_2, ARPHRD_FCFABRIC: DLT_FC_2, ARPHRD_IRDA: DLT_LINUX_IRDA, ARPHRD_LAPD: DLT_LINUX_LAPD, ARPHRD_NONE: DLT_RAW, ARPHRD_IEEE802154: DLT_IEEE802_15_4_NOFCS, ARPHRD_NETLINK: DLT_NETLINK, ARPHRD_VSOCKMON: DLT_VSOCK, } # Constants for PPI header types. PPI_DOT11COMMON = 2 PPI_DOT11NMAC = 3 PPI_DOT11NMACPHY = 4 PPI_SPECTRUM_MAP = 5 PPI_PROCESS_INFO = 6 PPI_CAPTURE_INFO = 7 PPI_AGGREGATION = 8 PPI_DOT3 = 9 PPI_GPS = 30002 PPI_VECTOR = 30003 PPI_SENSOR = 30004 PPI_ANTENNA = 30005 PPI_BTLE = 30006 # Human-readable type names for PPI header types. PPI_TYPES = { PPI_DOT11COMMON: 'dot11-common', PPI_DOT11NMAC: 'dot11-nmac', PPI_DOT11NMACPHY: 'dot11-nmacphy', PPI_SPECTRUM_MAP: 'spectrum-map', PPI_PROCESS_INFO: 'process-info', PPI_CAPTURE_INFO: 'capture-info', PPI_AGGREGATION: 'aggregation', PPI_DOT3: 'dot3', PPI_GPS: 'gps', PPI_VECTOR: 'vector', PPI_SENSOR: 'sensor', PPI_ANTENNA: 'antenna', PPI_BTLE: 'btle', } # On windows, epoch is 01/02/1970 at 00:00 EPOCH = calendar.timegm((1970, 1, 2, 0, 0, 0, 3, 1, 0)) - 86400 MTU = 0xffff # a.k.a give me all you have # In fact, IANA enterprise-numbers file available at # http://www.iana.org/assignments/enterprise-numbers # is simply huge (more than 2Mo and 600Ko in bz2). I'll # add only most common vendors, and encountered values. # -- arno IANA_ENTERPRISE_NUMBERS = { 9: "ciscoSystems", 35: "Nortel Networks", 43: "3Com", 311: "Microsoft", 2636: "Juniper Networks, Inc.", 4526: "Netgear", 5771: "Cisco Systems, Inc.", 5842: "Cisco Systems", 11129: "Google, Inc", 16885: "Nortel Networks", } def load_protocols(filename, _fallback=None, _integer_base=10): """"Parse /etc/protocols and return values as a dictionary.""" spaces = re.compile(b"[ \t]+|\n") dct = DADict(_name=filename) def _process_data(fdesc): for line in fdesc: try: shrp = line.find(b"#") if shrp >= 0: line = line[:shrp] line = line.strip() if not line: continue lt = tuple(re.split(spaces, line)) if len(lt) < 2 or not lt[0]: continue dct[lt[0]] = int(lt[1], _integer_base) except Exception as e: log_loading.info( "Couldn't parse file [%s]: line [%r] (%s)", filename, line, e, ) try: if not filename: raise IOError with open(filename, "rb") as fdesc: _process_data(fdesc) except IOError: if _fallback: _process_data(_fallback.split(b"\n")) else: log_loading.info("Can't open %s file", filename) return dct def load_ethertypes(filename): """"Parse /etc/ethertypes and return values as a dictionary. If unavailable, use the copy bundled with Scapy.""" from scapy.libs.ethertypes import DATA return load_protocols(filename, _fallback=DATA, _integer_base=16) def load_services(filename): spaces = re.compile(b"[ \t]+|\n") tdct = DADict(_name="%s-tcp" % filename) udct = DADict(_name="%s-udp" % filename) try: with open(filename, "rb") as fdesc: for line in fdesc: try: shrp = line.find(b"#") if shrp >= 0: line = line[:shrp] line = line.strip() if not line: continue lt = tuple(re.split(spaces, line)) if len(lt) < 2 or not lt[0]: continue if lt[1].endswith(b"/tcp"): tdct[lt[0]] = int(lt[1].split(b'/')[0]) elif lt[1].endswith(b"/udp"): udct[lt[0]] = int(lt[1].split(b'/')[0]) except Exception as e: log_loading.warning( "Couldn't parse file [%s]: line [%r] (%s)", filename, line, e, ) except IOError: log_loading.info("Can't open /etc/services file") return tdct, udct class ManufDA(DADict): def fixname(self, val): return plain_str(val) def __dir__(self): return ["lookup", "reverse_lookup"] def _get_manuf_couple(self, mac): oui = ":".join(mac.split(":")[:3]).upper() return self.__dict__.get(oui, (mac, mac)) def _get_manuf(self, mac): return self._get_manuf_couple(mac)[1] def _get_short_manuf(self, mac): return self._get_manuf_couple(mac)[0] def _resolve_MAC(self, mac): oui = ":".join(mac.split(":")[:3]).upper() if oui in self: return ":".join([self[oui][0]] + mac.split(":")[3:]) return mac def lookup(self, mac): """Find OUI name matching to a MAC""" oui = ":".join(mac.split(":")[:3]).upper() return self[oui] def reverse_lookup(self, name, case_sensitive=False): """ Find all MACs registered to a OUI :param name: the OUI name :param case_sensitive: default to False :returns: a dict of mac:tuples (Name, Extended Name) """ if case_sensitive: filtr = lambda x, l: any(x == z for z in l) else: name = name.lower() filtr = lambda x, l: any(x == z.lower() for z in l) return {k: v for k, v in six.iteritems(self.__dict__) if filtr(name, v)} def load_manuf(filename): """ Loads manuf file from Wireshark. :param filename: the file to load the manuf file from :returns: a ManufDA filled object """ manufdb = ManufDA(_name=filename) with open(filename, "rb") as fdesc: for line in fdesc: try: line = line.strip() if not line or line.startswith(b"#"): continue parts = line.split(None, 2) oui, shrt = parts[:2] lng = parts[2].lstrip(b"#").strip() if len(parts) > 2 else "" lng = lng or shrt manufdb[oui] = plain_str(shrt), plain_str(lng) except Exception: log_loading.warning("Couldn't parse one line from [%s] [%r]", filename, line, exc_info=True) return manufdb def select_path(directories, filename): """Find filename among several directories""" for directory in directories: path = os.path.join(directory, filename) if os.path.exists(path): return path if WINDOWS: IP_PROTOS = load_protocols(os.environ["SystemRoot"] + "\\system32\\drivers\\etc\\protocol") # noqa: E501 TCP_SERVICES, UDP_SERVICES = load_services( os.environ["SystemRoot"] + "\\system32\\drivers\\etc\\services") # noqa: E501 # Default values, will be updated by arch.windows ETHER_TYPES = load_ethertypes(None) MANUFDB = ManufDA() else: IP_PROTOS = load_protocols("/etc/protocols") ETHER_TYPES = load_ethertypes("/etc/ethertypes") TCP_SERVICES, UDP_SERVICES = load_services("/etc/services") MANUFDB = ManufDA() manuf_path = select_path( ['/usr', '/usr/local', '/opt', '/opt/wireshark', '/Applications/Wireshark.app/Contents/Resources'], "share/wireshark/manuf" ) if manuf_path: try: MANUFDB = load_manuf(manuf_path) except (IOError, OSError): log_loading.warning("Cannot read wireshark manuf database") ##################### # knowledge bases # ##################### class KnowledgeBase: def __init__(self, filename): self.filename = filename self.base = None def lazy_init(self): self.base = "" def reload(self, filename=None): if filename is not None: self.filename = filename oldbase = self.base self.base = None self.lazy_init() if self.base is None: self.base = oldbase def get_base(self): if self.base is None: self.lazy_init() return self.base