# MIT License # Copyright (c) 2018 Jose Amores # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # This file is part of Scapy # See http://www.secdev.org/projects/scapy for more information # Copyright (C) Sebastian Baar # This program is published under a GPLv2 license # scapy.contrib.description = Scalable service-Oriented MiddlewarE/IP (SOME/IP) # scapy.contrib.status = loads import ctypes import collections import struct from scapy.layers.inet import TCP, UDP from scapy.layers.inet6 import IP6Field from scapy.compat import raw, orb from scapy.config import conf from scapy.modules.six.moves import range from scapy.packet import Packet, Raw, bind_top_down, bind_bottom_up from scapy.fields import XShortField, BitEnumField, ConditionalField, \ BitField, XBitField, IntField, XByteField, ByteEnumField, \ ShortField, X3BytesField, StrLenField, IPField, FieldLenField, \ PacketListField, XIntField class SOMEIP(Packet): """ SOME/IP Packet.""" PROTOCOL_VERSION = 0x01 INTERFACE_VERSION = 0x01 LEN_OFFSET = 0x08 LEN_OFFSET_TP = 0x0c TYPE_REQUEST = 0x00 TYPE_REQUEST_NO_RET = 0x01 TYPE_NOTIFICATION = 0x02 TYPE_REQUEST_ACK = 0x40 TYPE_REQUEST_NORET_ACK = 0x41 TYPE_NOTIFICATION_ACK = 0x42 TYPE_RESPONSE = 0x80 TYPE_ERROR = 0x81 TYPE_RESPONSE_ACK = 0xc0 TYPE_ERROR_ACK = 0xc1 TYPE_TP_REQUEST = 0x20 TYPE_TP_REQUEST_NO_RET = 0x21 TYPE_TP_NOTIFICATION = 0x22 TYPE_TP_RESPONSE = 0x23 TYPE_TP_ERROR = 0x24 RET_E_OK = 0x00 RET_E_NOT_OK = 0x01 RET_E_UNKNOWN_SERVICE = 0x02 RET_E_UNKNOWN_METHOD = 0x03 RET_E_NOT_READY = 0x04 RET_E_NOT_REACHABLE = 0x05 RET_E_TIMEOUT = 0x06 RET_E_WRONG_PROTOCOL_V = 0x07 RET_E_WRONG_INTERFACE_V = 0x08 RET_E_MALFORMED_MSG = 0x09 RET_E_WRONG_MESSAGE_TYPE = 0x0a _OVERALL_LEN_NOPAYLOAD = 16 name = "SOME/IP" fields_desc = [ XShortField("srv_id", 0), BitEnumField("sub_id", 0, 1, {0: "METHOD_ID", 1: "EVENT_ID"}), ConditionalField(XBitField("method_id", 0, 15), lambda pkt: pkt.sub_id == 0), ConditionalField(XBitField("event_id", 0, 15), lambda pkt: pkt.sub_id == 1), IntField("len", None), XShortField("client_id", 0), XShortField("session_id", 0), XByteField("proto_ver", PROTOCOL_VERSION), XByteField("iface_ver", INTERFACE_VERSION), ByteEnumField("msg_type", TYPE_REQUEST, { TYPE_REQUEST: "REQUEST", TYPE_REQUEST_NO_RET: "REQUEST_NO_RETURN", TYPE_NOTIFICATION: "NOTIFICATION", TYPE_REQUEST_ACK: "REQUEST_ACK", TYPE_REQUEST_NORET_ACK: "REQUEST_NO_RETURN_ACK", TYPE_NOTIFICATION_ACK: "NOTIFICATION_ACK", TYPE_RESPONSE: "RESPONSE", TYPE_ERROR: "ERROR", TYPE_RESPONSE_ACK: "RESPONSE_ACK", TYPE_ERROR_ACK: "ERROR_ACK", TYPE_TP_REQUEST: "TP_REQUEST", TYPE_TP_REQUEST_NO_RET: "TP_REQUEST_NO_RETURN", TYPE_TP_NOTIFICATION: "TP_NOTIFICATION", TYPE_TP_RESPONSE: "TP_RESPONSE", TYPE_TP_ERROR: "TP_ERROR", }), ByteEnumField("retcode", 0, { RET_E_OK: "E_OK", RET_E_NOT_OK: "E_NOT_OK", RET_E_UNKNOWN_SERVICE: "E_UNKNOWN_SERVICE", RET_E_UNKNOWN_METHOD: "E_UNKNOWN_METHOD", RET_E_NOT_READY: "E_NOT_READY", RET_E_NOT_REACHABLE: "E_NOT_REACHABLE", RET_E_TIMEOUT: "E_TIMEOUT", RET_E_WRONG_PROTOCOL_V: "E_WRONG_PROTOCOL_VERSION", RET_E_WRONG_INTERFACE_V: "E_WRONG_INTERFACE_VERSION", RET_E_MALFORMED_MSG: "E_MALFORMED_MESSAGE", RET_E_WRONG_MESSAGE_TYPE: "E_WRONG_MESSAGE_TYPE", }), ConditionalField(BitField("offset", 0, 28), lambda pkt: SOMEIP._is_tp(pkt)), ConditionalField(BitField("res", 0, 3), lambda pkt: SOMEIP._is_tp(pkt)), ConditionalField(BitField("more_seg", 0, 1), lambda pkt: SOMEIP._is_tp(pkt)) ] def post_build(self, pkt, pay): length = self.len if length is None: if SOMEIP._is_tp(self): length = SOMEIP.LEN_OFFSET_TP + len(pay) else: length = SOMEIP.LEN_OFFSET + len(pay) pkt = pkt[:4] + struct.pack("!I", length) + pkt[8:] return pkt + pay def answers(self, other): if other.__class__ == self.__class__: if self.msg_type in [SOMEIP.TYPE_REQUEST_NO_RET, SOMEIP.TYPE_REQUEST_NORET_ACK, SOMEIP.TYPE_NOTIFICATION, SOMEIP.TYPE_TP_REQUEST_NO_RET, SOMEIP.TYPE_TP_NOTIFICATION]: return 0 return self.payload.answers(other.payload) return 0 @staticmethod def _is_tp(pkt): """Returns true if pkt is using SOMEIP-TP, else returns false.""" tp = [SOMEIP.TYPE_TP_REQUEST, SOMEIP.TYPE_TP_REQUEST_NO_RET, SOMEIP.TYPE_TP_NOTIFICATION, SOMEIP.TYPE_TP_RESPONSE, SOMEIP.TYPE_TP_ERROR] if isinstance(pkt, Packet): return pkt.msg_type in tp else: return pkt[15] in tp def fragment(self, fragsize=1392): """Fragment SOME/IP-TP""" fnb = 0 fl = self lst = list() while fl.underlayer is not None: fnb += 1 fl = fl.underlayer for p in fl: s = raw(p[fnb].payload) nb = (len(s) + fragsize) // fragsize for i in range(nb): q = p.copy() del q[fnb].payload q[fnb].len = SOMEIP.LEN_OFFSET_TP + \ len(s[i * fragsize:(i + 1) * fragsize]) q[fnb].more_seg = 1 if i == nb - 1: q[fnb].more_seg = 0 q[fnb].offset += i * fragsize // 16 r = conf.raw_layer(load=s[i * fragsize:(i + 1) * fragsize]) r.overload_fields = p[fnb].payload.overload_fields.copy() q.add_payload(r) lst.append(q) return lst def _bind_someip_layers(): bind_top_down(UDP, SOMEIP, sport=30490, dport=30490) for i in range(15): bind_bottom_up(UDP, SOMEIP, sport=30490 + i) bind_bottom_up(TCP, SOMEIP, sport=30490 + i) bind_bottom_up(UDP, SOMEIP, dport=30490 + i) bind_bottom_up(TCP, SOMEIP, dport=30490 + i) _bind_someip_layers() class _SDPacketBase(Packet): """ base class to be used among all SD Packet definitions.""" def extract_padding(self, s): return "", s SDENTRY_TYPE_SRV_FINDSERVICE = 0x00 SDENTRY_TYPE_SRV_OFFERSERVICE = 0x01 SDENTRY_TYPE_SRV = (SDENTRY_TYPE_SRV_FINDSERVICE, SDENTRY_TYPE_SRV_OFFERSERVICE) SDENTRY_TYPE_EVTGRP_SUBSCRIBE = 0x06 SDENTRY_TYPE_EVTGRP_SUBSCRIBE_ACK = 0x07 SDENTRY_TYPE_EVTGRP = (SDENTRY_TYPE_EVTGRP_SUBSCRIBE, SDENTRY_TYPE_EVTGRP_SUBSCRIBE_ACK) SDENTRY_OVERALL_LEN = 16 def _MAKE_SDENTRY_COMMON_FIELDS_DESC(type): return [ XByteField("type", type), XByteField("index_1", 0), XByteField("index_2", 0), XBitField("n_opt_1", 0, 4), XBitField("n_opt_2", 0, 4), XShortField("srv_id", 0), XShortField("inst_id", 0), XByteField("major_ver", 0), X3BytesField("ttl", 0) ] class SDEntry_Service(_SDPacketBase): name = "Service Entry" fields_desc = _MAKE_SDENTRY_COMMON_FIELDS_DESC( SDENTRY_TYPE_SRV_FINDSERVICE) fields_desc += [ XIntField("minor_ver", 0) ] class SDEntry_EventGroup(_SDPacketBase): name = "Eventgroup Entry" fields_desc = _MAKE_SDENTRY_COMMON_FIELDS_DESC( SDENTRY_TYPE_EVTGRP_SUBSCRIBE) fields_desc += [ XBitField("res", 0, 12), XBitField("cnt", 0, 4), XShortField("eventgroup_id", 0) ] def _sdentry_class(payload, **kargs): TYPE_PAYLOAD_I = 0 pl_type = orb(payload[TYPE_PAYLOAD_I]) cls = None if pl_type in SDENTRY_TYPE_SRV: cls = SDEntry_Service elif pl_type in SDENTRY_TYPE_EVTGRP: cls = SDEntry_EventGroup return cls(payload, **kargs) def _sdoption_class(payload, **kargs): pl_type = orb(payload[2]) cls = { SDOPTION_CFG_TYPE: SDOption_Config, SDOPTION_LOADBALANCE_TYPE: SDOption_LoadBalance, SDOPTION_IP4_ENDPOINT_TYPE: SDOption_IP4_EndPoint, SDOPTION_IP4_MCAST_TYPE: SDOption_IP4_Multicast, SDOPTION_IP4_SDENDPOINT_TYPE: SDOption_IP4_SD_EndPoint, SDOPTION_IP6_ENDPOINT_TYPE: SDOption_IP6_EndPoint, SDOPTION_IP6_MCAST_TYPE: SDOption_IP6_Multicast, SDOPTION_IP6_SDENDPOINT_TYPE: SDOption_IP6_SD_EndPoint }.get(pl_type, Raw) return cls(payload, **kargs) # SD Option SDOPTION_CFG_TYPE = 0x01 SDOPTION_LOADBALANCE_TYPE = 0x02 SDOPTION_LOADBALANCE_LEN = 0x05 SDOPTION_IP4_ENDPOINT_TYPE = 0x04 SDOPTION_IP4_ENDPOINT_LEN = 0x0009 SDOPTION_IP4_MCAST_TYPE = 0x14 SDOPTION_IP4_MCAST_LEN = 0x0009 SDOPTION_IP4_SDENDPOINT_TYPE = 0x24 SDOPTION_IP4_SDENDPOINT_LEN = 0x0009 SDOPTION_IP6_ENDPOINT_TYPE = 0x06 SDOPTION_IP6_ENDPOINT_LEN = 0x0015 SDOPTION_IP6_MCAST_TYPE = 0x16 SDOPTION_IP6_MCAST_LEN = 0x0015 SDOPTION_IP6_SDENDPOINT_TYPE = 0x26 SDOPTION_IP6_SDENDPOINT_LEN = 0x0015 def _MAKE_COMMON_SDOPTION_FIELDS_DESC(type, length=None): return [ ShortField("len", length), XByteField("type", type), XByteField("res_hdr", 0) ] def _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC(): return [ XByteField("res_tail", 0), ByteEnumField("l4_proto", 0x11, {0x06: "TCP", 0x11: "UDP"}), ShortField("port", 0) ] class SDOption_Config(_SDPacketBase): name = "Config Option" fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC(SDOPTION_CFG_TYPE) + [ StrLenField("cfg_str", "\x00", length_from=lambda pkt: pkt.len - 1) ] def post_build(self, pkt, pay): if self.len is None: length = len(self.cfg_str) + 1 # res_hdr field takes 1 byte pkt = struct.pack("!H", length) + pkt[2:] return pkt + pay @staticmethod def make_string(data): # Build a valid null-terminated configuration string from a dict or a # list with key-value pairs. # # Example: # >>> SDOption_Config.make_string({ "hello": "world" }) # b'\x0bhello=world\x00' # # >>> SDOption_Config.make_string([ # ... ("x", "y"), # ... ("abc", "def"), # ... ("123", "456") # ... ]) # b'\x03x=y\x07abc=def\x07123=456\x00' if isinstance(data, dict): data = data.items() # combine entries data = ("{}={}".format(k, v) for k, v in data) # prepend length data = ("{}{}".format(chr(len(v)), v) for v in data) # concatenate data = "".join(data) data += "\x00" return data.encode("utf8") class SDOption_LoadBalance(_SDPacketBase): name = "LoadBalance Option" fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( SDOPTION_LOADBALANCE_TYPE, SDOPTION_LOADBALANCE_LEN) fields_desc += [ ShortField("priority", 0), ShortField("weight", 0) ] class SDOption_IP4_EndPoint(_SDPacketBase): name = "IP4 EndPoint Option" fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( SDOPTION_IP4_ENDPOINT_TYPE, SDOPTION_IP4_ENDPOINT_LEN) fields_desc += [ IPField("addr", "0.0.0.0"), ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() class SDOption_IP4_Multicast(_SDPacketBase): name = "IP4 Multicast Option" fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( SDOPTION_IP4_MCAST_TYPE, SDOPTION_IP4_MCAST_LEN) fields_desc += [ IPField("addr", "0.0.0.0"), ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() class SDOption_IP4_SD_EndPoint(_SDPacketBase): name = "IP4 SDEndPoint Option" fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( SDOPTION_IP4_SDENDPOINT_TYPE, SDOPTION_IP4_SDENDPOINT_LEN) fields_desc += [ IPField("addr", "0.0.0.0"), ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() class SDOption_IP6_EndPoint(_SDPacketBase): name = "IP6 EndPoint Option" fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( SDOPTION_IP6_ENDPOINT_TYPE, SDOPTION_IP6_ENDPOINT_LEN) fields_desc += [ IP6Field("addr", "::"), ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() class SDOption_IP6_Multicast(_SDPacketBase): name = "IP6 Multicast Option" fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( SDOPTION_IP6_MCAST_TYPE, SDOPTION_IP6_MCAST_LEN) fields_desc += [ IP6Field("addr", "::"), ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() class SDOption_IP6_SD_EndPoint(_SDPacketBase): name = "IP6 SDEndPoint Option" fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( SDOPTION_IP6_SDENDPOINT_TYPE, SDOPTION_IP6_SDENDPOINT_LEN) fields_desc += [ IP6Field("addr", "::"), ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() ## # SD PACKAGE DEFINITION ## class SD(_SDPacketBase): """ SD Packet NOTE : when adding 'entries' or 'options', do not use list.append() method but create a new list e.g. : p = SD() p.option_array = [SDOption_Config(),SDOption_IP6_EndPoint()] """ SOMEIP_MSGID_SRVID = 0xffff SOMEIP_MSGID_SUBID = 0x1 SOMEIP_MSGID_EVENTID = 0x100 SOMEIP_CLIENT_ID = 0x0000 SOMEIP_MINIMUM_SESSION_ID = 0x0001 SOMEIP_PROTO_VER = 0x01 SOMEIP_IFACE_VER = 0x01 SOMEIP_MSG_TYPE = SOMEIP.TYPE_NOTIFICATION SOMEIP_RETCODE = SOMEIP.RET_E_OK _sdFlag = collections.namedtuple('Flag', 'mask offset') FLAGSDEF = { "REBOOT": _sdFlag(mask=0x80, offset=7), "UNICAST": _sdFlag(mask=0x40, offset=6) } name = "SD" fields_desc = [ XByteField("flags", 0), X3BytesField("res", 0), FieldLenField("len_entry_array", None, length_of="entry_array", fmt="!I"), PacketListField("entry_array", None, cls=_sdentry_class, length_from=lambda pkt: pkt.len_entry_array), FieldLenField("len_option_array", None, length_of="option_array", fmt="!I"), PacketListField("option_array", None, cls=_sdoption_class, length_from=lambda pkt: pkt.len_option_array) ] def get_flag(self, name): name = name.upper() if name in self.FLAGSDEF: return ((self.flags & self.FLAGSDEF[name].mask) >> self.FLAGSDEF[name].offset) else: return None def set_flag(self, name, value): name = name.upper() if name in self.FLAGSDEF: self.flags = (self.flags & (ctypes.c_ubyte(~self.FLAGSDEF[name].mask).value)) \ | ((value & 0x01) << self.FLAGSDEF[name].offset) def set_entryArray(self, entry_list): if isinstance(entry_list, list): self.entry_array = entry_list else: self.entry_array = [entry_list] def set_optionArray(self, option_list): if isinstance(option_list, list): self.option_array = option_list else: self.option_array = [option_list] bind_top_down(SOMEIP, SD, srv_id=SD.SOMEIP_MSGID_SRVID, sub_id=SD.SOMEIP_MSGID_SUBID, client_id=SD.SOMEIP_CLIENT_ID, session_id=SD.SOMEIP_MINIMUM_SESSION_ID, event_id=SD.SOMEIP_MSGID_EVENTID, proto_ver=SD.SOMEIP_PROTO_VER, iface_ver=SD.SOMEIP_IFACE_VER, msg_type=SD.SOMEIP_MSG_TYPE, retcode=SD.SOMEIP_RETCODE) bind_bottom_up(SOMEIP, SD, srv_id=SD.SOMEIP_MSGID_SRVID, sub_id=SD.SOMEIP_MSGID_SUBID, event_id=SD.SOMEIP_MSGID_EVENTID, proto_ver=SD.SOMEIP_PROTO_VER, iface_ver=SD.SOMEIP_IFACE_VER, msg_type=SD.SOMEIP_MSG_TYPE, retcode=SD.SOMEIP_RETCODE) # FIXME: Service Discovery messages shall be transported over UDP # (TR_SOMEIP_00248) # FIXME: The port 30490 (UDP and TCP as well) shall be only used for SOME/IP-SD # and not used for applications communicating over SOME/IP # (TR_SOMEIP_00020)