Skip to content

Commit

Permalink
Update Ethernet/IP contrib and tests
Browse files Browse the repository at this point in the history
Add Hart and CompoNet to Codespell ignore settings
  • Loading branch information
claire-lex committed Aug 3, 2023
1 parent 8e116af commit 0c19169
Show file tree
Hide file tree
Showing 3 changed files with 254 additions and 167 deletions.
2 changes: 2 additions & 0 deletions .config/codespell_ignore.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ ba
byteorder
cace
cas
componet
cros
delt
doas
Expand All @@ -14,6 +15,7 @@ ether
eventtypes
fo
gost
hart
iff
inout
microsof
Expand Down
289 changes: 188 additions & 101 deletions scapy/contrib/enipTCP.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# This file is part of Scapy
# See https://scapy.net/ for more information
# Copyright (C) 2019 Jose Diogo Monteiro <jdlopes@student.dei.uc.pt>
# Updated (C) 2023 Claire Vacherot <clairelex@pm.me>

# scapy.contrib.description = EtherNet/IP
# scapy.contrib.status = loads
Expand All @@ -18,10 +19,11 @@
from scapy.layers.inet import TCP
from scapy.fields import LEShortField, LEShortEnumField, LEIntEnumField, \
LEIntField, LELongField, FieldLenField, PacketListField, ByteField, \
PacketField, MultipleTypeField, StrLenField, StrFixedLenField, \
XLEIntField, XLEStrLenField
StrLenField, StrFixedLenField, XLEIntField, XLEStrLenField, \
LEFieldLenField, ShortField, IPField, LongField, XLEShortField

_commandIdList = {
0x0001: "UnknownCommand",
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
Expand All @@ -43,21 +45,80 @@
105: "unsupported_prot_rev"
}

_itemID = {
_typeIdList = {
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"
0x000c: "CIP Identity",
0x0086: "CIP Security Information",
0x0087: "EtherNet/IP Capability",
0x0088: "EtherNet/IP Usage",
0x00a1: "Connected Address Item",
0x00B1: "Connected Data Item",
0x00B2: "Unconnected Data Item",
0x0100: "List Services Response",
0x8000: "Socket Address Info O->T",
0x8001: "Socket Address Info T->O",
0x8002: "Sequenced Address Item",
0x8003: "Unconnected Message over UDP"
}

_deviceTypeList = {
0x0000: "Generic Device (deprecated)",
0x0002: "AC Drive",
0x0003: "Motor Overload",
0x0004: "Limit Switch",
0x0005: "Inductive Proximity Switch",
0x0006: "Photoelectric Sensor",
0x0007: "General Purpose Discrete I/O",
0x0009: "Resolver",
0x000C: "Communications Adapter",
0x000E: "Programmable Logic Controller",
0x0010: "Position Controller",
0x0013: "DC Drive",
0x0015: "Contactor",
0x0016: "Motor Starter",
0x0017: "Soft Start",
0x0018: "Human-Machine Interface",
0x001A: "Mass Flow Controller",
0x001B: "Pneumatic Valve",
0x001C: "Vacuum Pressure Gauge",
0x001D: "Process Control Value",
0x001E: "Residual Gas Analyzer",
0x001F: "DC Power Generator",
0x0020: "RF Power Generator",
0x0021: "Turbomolecular Vacuum Pump",
0x0022: "Encoder",
0x0023: "Safety Discrete I/O Device",
0x0024: "Fluid Flow Controller",
0x0025: "CIP Motion Drive",
0x0026: "CompoNet Repeater",
0x0027: "Mass Flow Controller, Enhanced",
0x0028: "CIP Modbus Device",
0x0029: "CIP Modbus Translator",
0x002A: "Safety Analog I/O Device",
0x002B: "Generic Device (keyable)",
0x002C: "Managed Ethernet Switch",
0x002D: "CIP Motion Safety Drive Device",
0x002E: "Safety Drive Device",
0x002F: "CIP Motion Encoder",
0x0030: "CIP Motion Converter",
0x0031: "CIP Motion I/O",
0x0032: "ControlNet Physical Layer Component",
0x0033: "Circuit Breaker",
0x0034: "HART Device",
0x0035: "CIP-HART Translator",
0x00C8: "Embedded Component",
}

_interfaceList = {
0x00: "CIP"
}


class ItemData(Packet):
"""Common Packet Format"""
name = "Item Data"
fields_desc = [
LEShortEnumField("typeId", 0, _itemID),
LEShortEnumField("typeId", 0, _typeIdList),
LEShortField("length", 0),
XLEStrLenField("data", "", length_from=lambda pkt: pkt.length),
]
Expand All @@ -66,97 +127,105 @@ 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),
]
# Unknown command (0x0001)


class CommandSpecificData(Packet):
"""Command Specific Data Field Default"""
class ENIPUnknownCommand(Packet):
"""Unknown Command reply"""
name = "ENIPUnknownCommand"
pass


class ENIPSendUnitData(BaseSendPacket):
"""Send Unit Data Command Field"""
name = "ENIPSendUnitData"


class ENIPSendRRData(BaseSendPacket):
"""Send RR Data Command Field"""
name = "ENIPSendRRData"
# List services (0x0004)


class ENIPListInterfacesReplyItems(Packet):
"""List Interfaces Items Field"""
name = "ENIPListInterfacesReplyItems"
class ENIPListServicesItem(Packet):
"""List Services Item Field"""
name = "ENIPListServicesItem"
fields_desc = [
LEIntField("itemTypeCode", 0),
FieldLenField("itemLength", 0, length_of="itemData"),
StrLenField("itemData", "", length_from=lambda pkt: pkt.itemLength),
LEShortEnumField("itemTypeCode", 0, _typeIdList),
LEFieldLenField("itemLength", 0),
LEShortField("protocolVersion", 0),
XLEShortField("flag", 0), # TODO: detail with BitFields
StrFixedLenField("serviceName", None, 16),
]


class ENIPListInterfacesReply(Packet):
"""List Interfaces Command Field"""
name = "ENIPListInterfacesReply"
class ENIPListServices(Packet):
"""List Services Command Field"""
name = "ENIPListServices"
fields_desc = [
FieldLenField("itemCount", 0, count_of="identityItems"),
PacketField("identityItems", 0, ENIPListInterfacesReplyItems),
FieldLenField("itemCount", 0, count_of="items"),
PacketListField("items", None, ENIPListServicesItem),
]


class ENIPListIdentityReplyItems(Packet):
"""List Identity Items Field"""
name = "ENIPListIdentityReplyItems"
# List identity (0x0063)


class ENIPListIdentityItem(Packet):
"""List Identity Item Fields"""
name = "ENIPListIdentityReplyItem"
fields_desc = [
LEIntField("itemTypeCode", 0),
FieldLenField("itemLength", 0, length_of="itemData"),
StrLenField("itemData", "", length_from=lambda pkt: pkt.item_length),
LEShortEnumField("itemTypeCode", 0, _typeIdList),
LEFieldLenField("itemLength", 0),
LEShortField("protocolVersion", 0),
# Socket address
ShortField("sinFamily", 0),
ShortField("sinPort", 0),
IPField("sinAddress", None),
LongField("sinZero", 0),
# End socket address
LEShortField("vendorId", 0), # Vendor list could be added (long list)
LEShortEnumField("deviceType", 0, _deviceTypeList),
LEShortField("productCode", 0),
ByteField("revisionMajor", 0),
ByteField("revisionMinor", 0),
LEShortField("status", 0),
XLEIntField("serialNumber", 0),
ByteField("productNameLength", 0),
StrLenField("productName", None,
length_from=lambda pkt: pkt.productNameLength),
ByteField("state", 0)
]


class ENIPListIdentityReply(Packet):
"""List Identity Command Field"""
name = "ENIPListIdentityReply"
class ENIPListIdentity(Packet):
"""List identity request and response"""
name = "ENIPListIdentity"
fields_desc = [
FieldLenField("itemCount", 0, count_of="identityItems"),
PacketField("identityItems", None, ENIPListIdentityReplyItems),
FieldLenField("itemCount", 0, count_of="items"),
PacketListField("items", None, ENIPListIdentityItem)
]


class ENIPListServicesReplyItems(Packet):
"""List Services Items Field"""
name = "ENIPListServicesReplyItems"
# List Interfaces (0x0064)


class ENIPListInterfacesItem(Packet):
"""List Interfaces Item Fields"""
name = "ENIPListInterfacesItem"
fields_desc = [
LEIntField("itemTypeCode", 0),
LEIntField("itemLength", 0),
ByteField("version", 1),
ByteField("flag", 0),
StrFixedLenField("serviceName", None, 16 * 4),
LEShortEnumField("itemTypeCode", 0, _typeIdList),
FieldLenField("itemLength", 0, length_of="itemData"),
# TODO: Could be detailed
StrLenField("itemData", "", length_from=lambda pkt: pkt.itemLength),
]


class ENIPListServicesReply(Packet):
"""List Services Command Field"""
name = "ENIPListServicesReply"
class ENIPListInterfaces(Packet):
"""List Interfaces Command Field"""
name = "ENIPListInterfaces"
fields_desc = [
FieldLenField("itemCount", 0, count_of="identityItems"),
PacketField("targetItems", None, ENIPListServicesReplyItems),
FieldLenField("itemCount", 0, count_of="items"),
PacketListField("items", None, ENIPListInterfacesItem),
]


class ENIPRegisterSession(CommandSpecificData):
# Register Session (0x0065)


class ENIPRegisterSession(Packet):
"""Register Session Command Field"""
name = "ENIPRegisterSession"
fields_desc = [
Expand All @@ -165,6 +234,47 @@ class ENIPRegisterSession(CommandSpecificData):
]


# Unregister Session (0x0066) -- Requires further testing


class ENIPUnregisterSession(Packet):
"""Unregister Session Command Field"""
name = "ENIPUnregisterSession"
pass


# Send RR Data (0x006f)


class ENIPSendRRData(Packet):
"""Send RR Data Command Field"""
name = "ENIPSendRRData"
fields_desc = [
LEIntEnumField("interface", 0, _interfaceList),
LEShortField("timeout", 0xff),
LEFieldLenField("itemCount", 0, count_of="items"),
PacketListField("items", None, ItemData)
# TODO: Send RR Data is usually followed by a CIP packet
]


# Send Unit Data (0x006f)


class ENIPSendUnitData(Packet):
"""Send Unit Data Command Field"""
name = "ENIPSendUnitData"
fields_desc = [
LEIntEnumField("interface", 0, _interfaceList),
LEShortField("timeout", 0xff),
LEFieldLenField("itemCount", 0, count_of="items"),
PacketListField("items", None, ItemData)
]


# Main Ethernet/IP packet structure with header


class ENIPTCP(Packet):
"""Ethernet/IP packet over TCP"""
name = "ENIPTCP"
Expand All @@ -175,38 +285,6 @@ class ENIPTCP(Packet):
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):
Expand All @@ -217,3 +295,12 @@ def post_build(self, pkt, pay):

bind_layers(TCP, ENIPTCP, dport=44818)
bind_layers(TCP, ENIPTCP, sport=44818)

bind_layers(ENIPTCP, ENIPUnknownCommand, commandId=0x0001)
bind_layers(ENIPTCP, ENIPListServices, commandId=0x0004)
bind_layers(ENIPTCP, ENIPListIdentity, commandId=0x0063)
bind_layers(ENIPTCP, ENIPListInterfaces, commandId=0x0064)
bind_layers(ENIPTCP, ENIPRegisterSession, commandId=0x0065)
bind_layers(ENIPTCP, ENIPUnregisterSession, commandId=0x0066)
bind_layers(ENIPTCP, ENIPSendRRData, commandId=0x006f)
bind_layers(ENIPTCP, ENIPSendUnitData, commandId=0x0070)
Loading

0 comments on commit 0c19169

Please sign in to comment.