# coding: utf8 # This file is part of Scapy # Scapy is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # any later version. # # Scapy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Scapy. If not, see . # scapy.contrib.description = EtherNet/IP # scapy.contrib.status = loads # Copyright (C) 2019 Jose Diogo Monteiro # Based on https://github.com/scy-phy/scapy-cip-enip # Routines for EtherNet/IP (Industrial Protocol) dissection # EtherNet/IP Home: www.odva.org import struct from scapy.packet import Packet, bind_layers from scapy.layers.inet import TCP from scapy.fields import LEShortField, LEShortEnumField, LEIntEnumField, \ LEIntField, LELongField, FieldLenField, PacketListField, ByteField, \ PacketField, MultipleTypeField, StrLenField, StrFixedLenField, \ XLEIntField, XLEStrLenField _commandIdList = { 0x0004: "ListServices", # Request Struct Don't Have Command Spec Data 0x0063: "ListIdentity", # Request Struct Don't Have Command Spec Data 0x0064: "ListInterfaces", # Request Struct Don't Have Command Spec Data 0x0065: "RegisterSession", # Request Structure = Reply Structure 0x0066: "UnregisterSession", # Don't Have Command Specific Data 0x006f: "SendRRData", # Request Structure = Reply Structure 0x0070: "SendUnitData", # There is no reply 0x0072: "IndicateStatus", 0x0073: "Cancel" } _statusList = { 0: "success", 1: "invalid_cmd", 2: "no_resources", 3: "incorrect_data", 100: "invalid_session", 101: "invalid_length", 105: "unsupported_prot_rev" } _itemID = { 0x0000: "Null Address Item", 0x00a1: "Connection-based Address Item", 0x00b1: "Connected Transport packet Data Item", 0x00b2: "Unconnected message Data Item", 0x8000: "Sockaddr Info, originator-to-target Data Item", 0x8001: "Sockaddr Info, target-to-originator Data Item" } class ItemData(Packet): """Common Packet Format""" name = "Item Data" fields_desc = [ LEShortEnumField("typeId", 0, _itemID), LEShortField("length", 0), XLEStrLenField("data", "", length_from=lambda pkt: pkt.length), ] def extract_padding(self, s): return '', s class EncapsulatedPacket(Packet): """Encapsulated Packet""" name = "Encapsulated Packet" fields_desc = [LEShortField("itemCount", 2), PacketListField( "item", None, ItemData, count_from=lambda pkt: pkt.itemCount), ] class BaseSendPacket(Packet): """ Abstract Class""" fields_desc = [ LEIntField("interfaceHandle", 0), LEShortField("timeout", 0), PacketField("encapsulatedPacket", None, EncapsulatedPacket), ] class CommandSpecificData(Packet): """Command Specific Data Field Default""" pass class ENIPSendUnitData(BaseSendPacket): """Send Unit Data Command Field""" name = "ENIPSendUnitData" class ENIPSendRRData(BaseSendPacket): """Send RR Data Command Field""" name = "ENIPSendRRData" class ENIPListInterfacesReplyItems(Packet): """List Interfaces Items Field""" name = "ENIPListInterfacesReplyItems" fields_desc = [ LEIntField("itemTypeCode", 0), FieldLenField("itemLength", 0, length_of="itemData"), StrLenField("itemData", "", length_from=lambda pkt: pkt.itemLength), ] class ENIPListInterfacesReply(Packet): """List Interfaces Command Field""" name = "ENIPListInterfacesReply" fields_desc = [ FieldLenField("itemCount", 0, count_of="identityItems"), PacketField("identityItems", 0, ENIPListInterfacesReplyItems), ] class ENIPListIdentityReplyItems(Packet): """List Identity Items Field""" name = "ENIPListIdentityReplyItems" fields_desc = [ LEIntField("itemTypeCode", 0), FieldLenField("itemLength", 0, length_of="itemData"), StrLenField("itemData", "", length_from=lambda pkt: pkt.item_length), ] class ENIPListIdentityReply(Packet): """List Identity Command Field""" name = "ENIPListIdentityReply" fields_desc = [ FieldLenField("itemCount", 0, count_of="identityItems"), PacketField("identityItems", None, ENIPListIdentityReplyItems), ] class ENIPListServicesReplyItems(Packet): """List Services Items Field""" name = "ENIPListServicesReplyItems" fields_desc = [ LEIntField("itemTypeCode", 0), LEIntField("itemLength", 0), ByteField("version", 1), ByteField("flag", 0), StrFixedLenField("serviceName", None, 16 * 4), ] class ENIPListServicesReply(Packet): """List Services Command Field""" name = "ENIPListServicesReply" fields_desc = [ FieldLenField("itemCount", 0, count_of="identityItems"), PacketField("targetItems", None, ENIPListServicesReplyItems), ] class ENIPRegisterSession(CommandSpecificData): """Register Session Command Field""" name = "ENIPRegisterSession" fields_desc = [ LEShortField("protocolVersion", 1), LEShortField("options", 0) ] class ENIPTCP(Packet): """Ethernet/IP packet over TCP""" name = "ENIPTCP" fields_desc = [ LEShortEnumField("commandId", None, _commandIdList), LEShortField("length", 0), XLEIntField("session", 0), LEIntEnumField("status", None, _statusList), LELongField("senderContext", 0), LEIntField("options", 0), MultipleTypeField( [ # List Services Reply (PacketField("commandSpecificData", ENIPListServicesReply, ENIPListServicesReply), lambda pkt: pkt.commandId == 0x4), # List Identity Reply (PacketField("commandSpecificData", ENIPListIdentityReply, ENIPListIdentityReply), lambda pkt: pkt.commandId == 0x63), # List Interfaces Reply (PacketField("commandSpecificData", ENIPListInterfacesReply, ENIPListInterfacesReply), lambda pkt: pkt.commandId == 0x64), # Register Session (PacketField("commandSpecificData", ENIPRegisterSession, ENIPRegisterSession), lambda pkt: pkt.commandId == 0x65), # Send RR Data (PacketField("commandSpecificData", ENIPSendRRData, ENIPSendRRData), lambda pkt: pkt.commandId == 0x6f), # Send Unit Data (PacketField("commandSpecificData", ENIPSendUnitData, ENIPSendUnitData), lambda pkt: pkt.commandId == 0x70), ], PacketField( "commandSpecificData", None, CommandSpecificData) # By default ), ] def post_build(self, pkt, pay): if self.length is None and pay: pkt = pkt[:2] + struct.pack("