class CIP(scapy_all.Packet): name = "CIP" fields_desc = [ scapy_all.BitEnumField("direction", None, 1, { 0: "request", 1: "response" }), utils.XBitEnumField( "service", 0, 7, { 0x01: "Get_Attribute_All", 0x02: "Set_Attribute_All", 0x03: "Get_Attribute_List", 0x04: "Set_Attribute_List", 0x05: "Reset", 0x06: "Start", 0x07: "Stop", 0x08: "Create", 0x09: "Delete", 0x0a: "Multiple_Service_Packet", 0x0d: "Apply_attributes", 0x0e: "Get_Attribute_Single", 0x10: "Set_Attribute_Single", 0x4b: "Execute_PCCC_Service", # PCCC = Programmable Controller Communication Commands 0x4c: "Read_Tag_Service", 0x4d: "Write_Tag_Service", 0x4e: "Read_Modify_Write_Tag_Service", 0x4f: "Read_Other_Tag_Service", # ??? 0x52: "Read_Tag_Fragmented_Service", 0x53: "Write_Tag_Fragmented_Service", 0x54: "Forward_Open?", }), scapy_all.PacketListField("path", [], CIP_Path, count_from=lambda p: 1 if p.direction == 0 else 0), scapy_all.PacketListField("status", [], CIP_ResponseStatus, count_from=lambda p: 1 if p.direction == 1 else 0), ] def post_build(self, p, pay): is_response = (self.direction == 1) if self.direction is None and not self.path: # Transform the packet into a response p = "\x01" + p[1:] is_response = True if is_response: # Add a success status field if there was none if not self.status: p = p[0:1] + b"\0\0\0" + p[1:] return p + pay
class SCION(scapy.Packet): name = 'SCION' fields_desc = [ # Common header scapy.BitField('version', 0, 4), scapy.BitEnumField('dst_type', "ipv4", 6, SCION_ADDR_TYPE), scapy.BitEnumField('src_type', "ipv4", 6, SCION_ADDR_TYPE), scapy.BitField('total_len', None, 16), scapy.BitField('hdr_len', None, 8), scapy.BitField('curr_inf', None, 8), scapy.BitField('curr_hf', None, 8), scapy.BitEnumField('next_hdr', None, 8, scapy.IP_PROTOS), scapy.PacketField('addr', None, SCIONAddr), scapy.PacketListField('path', None, PathSegment, count_from=lambda _: 3), scapy.PacketField('ext', None, SecurityExtension) ] # this is terrible, but apparently that's how scapy works :-/ def post_build(self, pkt, pay): # compute lengths if self.total_len == None: self.total_len = len(pkt) + len(pay) pkt = pkt[:2] + struct.pack('!H', self.total_len) + pkt[4:] if self.hdr_len == None: self.hdr_len = len(pkt) - self.ext.hdr_len if self.hdr_len % 8 != 0: raise ValueError( "SCION packet header length not multiple of 8 bytes!") self.hdr_len = int(self.hdr_len / 8) #must divide by 8 pkt = pkt[:4] + struct.pack('B', self.hdr_len) + pkt[5:] return pkt + pay
class PathSegment(scapy.Packet): name = 'SCION Path segment' fields_desc = [ scapy.BitField('flags', 0x0, 8), UnixTimeField('timestamp', None), ISDField('isd', None), scapy.FieldLenField('nhops', None, count_of='hops', fmt='B'), scapy.PacketListField('hops', None, HopField, count_from=lambda p: p.nhops), ] def extract_padding(self, p): return "", p def post_build(self, pkt, pay): # compute MACs on HFs # this is not a thing that should be done by the client normally :D # => only useful for testing # TODO This is *not* verified against a "real" SCION packet yet! # Somebody should do something! def calculate_mac(current, prev): if prev != None: prev_data = prev[HopField.RANGE_SKIP_FLAGS:HopField.RANGE_END] else: prev_data = b'\0' * (HopField.RANGE_END - HopField.RANGE_SKIP_FLAGS) data = ( struct.pack('!I', self.timestamp) + struct.pack( 'B', current[HopField.FLAGS] & HopField.IMMUTABLE_FLAGS) + current[HopField.RANGE_SKIP_FLAGS:HopField.RANGE_BEFORE_MAC] + prev_data) # print('prev_data: ', len(prev_data), prev_data.encode('hex')) # print('data: ', len(data), data.encode('hex')) assert len(data) == 128 // 8 c = cmac.CMAC(algorithms.AES(HF_MAC_KEY), backend=default_backend()) c.update(data) return c.finalize() for i in range(len(self.hops)): if not self.hops[i].mac: curr_beg = 8 + 8 * i curr_end = 8 + 8 * (i + 1) prev_beg = curr_beg - 8 mac_beg = curr_end - 3 curr = pkt[curr_beg:curr_end] prev = pkt[prev_beg:curr_beg] if i > 0 else None mac = calculate_mac(curr, prev) # print('DEBUG: updating MAC: {} -> {}'.format(struct.pack('!I', self.hops[i].mac).encode('hex'), mac.encode('hex'))) # print('DEBUG: updating MAC -> {}'.format(mac.encode('hex'))) mac_bytes = mac[:3] # take the most significant bits pkt = pkt[:mac_beg] + mac_bytes + pkt[curr_end:] return pkt + pay
class ENIP_ListServices(scapy_all.Packet): name = "ENIP_ListServices" fields_desc = [ utils.LEShortLenField("count", 1, count_of="TargetItems"), scapy_all.PacketListField("TargetItems", [], ENIP_ListServices_TargetItem, count_from=lambda p: p.count), ]
class Pixels(scapy.Packet): name = "Pixels" fields_desc = [ scapy.ByteField("TypeOfUpdate", 0x00), scapy.ByteField("Layer", 0x01), scapy.FieldLenField("NumberOfPixelUpdates", None, count_of="PixelData", fmt='H'), scapy.PacketListField("PixelData", None, Pixel, count_from=lambda pkt: pkt.NumberOfPixelUpdates) ]
class ENIP_CPF(scapy_all.Packet): name = "ENIP_CPF" fields_desc = [ utils.LEShortLenField("count", 2, count_of="items"), # Changed implementation to reflect use of CIP_Item above scapy_all.PacketListField("items", [], CPF_Item, count_from=lambda p: p.count), # Due to potential 'optional' packet components at end in protocol scapy_all.ConditionalField( scapy_all.PacketListField("optional_items", None, CPF_Item, count_from=lambda p: p.count - 2), lambda p: p.count > 2), ] def extract_padding(self, p): return '', p
class ENIP_SendUnitData(scapy_all.Packet): """Data in ENIP header specific to the specified command""" name = "ENIP_SendUnitData" fields_desc = [ scapy_all.LEIntField("interface_handle", 0), scapy_all.LEShortField("timeout", 0), utils.LEShortLenField("count", None, count_of="items"), scapy_all.PacketListField("items", [], ENIP_SendUnitData_Item, count_from=lambda p: p.count), ]
class ENIP_UDP(scapy_all.Packet): """Ethernet/IP packet over UDP""" name = "ENIP_UDP" fields_desc = [ utils.LEShortLenField("count", None, count_of="items"), scapy_all.PacketListField("items", [], enip_cpf.CPF_Item, count_from=lambda p: p.count), ] def extract_padding(self, p): return "", p
class ENIP_ListIdentity(scapy_all.Packet): name = "ENIP_ListIdentity" fields_desc = [ utils.LEShortLenField("count", 1, count_of="IdentityItems"), scapy_all.PacketListField("IdentityItems", [], ENIP_ListIdentity_TargetItem, count_from=lambda p: p.count), ] def extract_padding(self, p): # print self.__class__.__name__ + ": P=" + str(p) return "", p
class ENIP_CPF(scapy_all.Packet): name = "ENIP_CPF" fields_desc = [ utils.LEShortLenField("count", 2, count_of="items"), scapy_all.PacketListField( "items", [CPF_AddressDataItem('', 0, 0), CPF_AddressDataItem('', 0, 0)], CPF_AddressDataItem, count_from=lambda p: p.count), ] def extract_padding(self, p): return '', p
class CIP_ReqForwardOpen(scapy_all.Packet): """Forward Open request""" name = "CIP_ReqForwardOpen" SEGMENT_TYPE = { 0x00: "Port Segment", 0x01: "Logical Segment", 0x02: "Network Segment", 0x03: "Symbolic Segment", 0x04: "Data Segment", 0x05: "Data Type (constructed)", 0x06: "Data Type (elementary)", 0x07: "Reserved for future use", } fields_desc = [ # Updated a few field descriptions to adjust how they are displayed # Altered fields begin with utils. rather than scapy_all. - MED scapy_all.BitField("priority", 0, 4), scapy_all.BitField("tick_time", 0, 4), scapy_all.ByteField("timeout_ticks", 249), utils.XLEIntField("OT_network_connection_id", 0x80000031), utils.XLEIntField("TO_network_connection_id", 0x80fe0030), scapy_all.LEShortField("connection_serial_number", 0x1337), utils.XLEShortField("vendor_id", 0x004d), utils.XLEIntField("originator_serial_number", 0xdeadbeef), scapy_all.ByteField("connection_timeout_multiplier", 0), scapy_all.X3BytesField("reserved", 0), utils.XLEIntField("OT_rpi", 0x007a1200), # 8000 ms scapy_all.PacketField('OT_connection_param', CIP_ConnectionParam(), CIP_ConnectionParam), utils.XLEIntField("TO_rpi", 0x007a1200), scapy_all.PacketField('TO_connection_param', CIP_ConnectionParam(), CIP_ConnectionParam), scapy_all.XByteField("transport_type", 0xa3), # direction server, application object, class 3 # Changed name - MED scapy_all.ByteField("Connection_Path_Size", None), #The number of 16 bit words in the Connection_Path field. # scapy_all.PacketListField("path_segment_items", [], CIP_Path1, length_from=lambda p: 2 * p.Connection_Path_Size), # Modified Implementation - MED scapy_all.PacketListField("path_segment_items", [], CIP_PathPadded, length_from=lambda p: 6), # CIP_PathField("path", None, length_from=lambda p: 2 * p.path_wordsize), ]