esp32_bluetooth_classic_sni.../libs/scapy/contrib/pnio_rpc.py
Matheus Eduardo Garbelini 86890704fd initial commit
todo: add documentation & wireshark dissector
2021-08-31 19:51:03 +08:00

1010 lines
35 KiB
Python
Executable file

# 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 <http://www.gnu.org/licenses/>.
# Copyright (C) 2016 Gauthier Sebaux
# scapy.contrib.description = ProfinetIO Remote Procedure Call (RPC)
# scapy.contrib.status = loads
"""
PNIO RPC endpoints
"""
import struct
from uuid import UUID
from scapy.packet import Packet, bind_layers
from scapy.config import conf
from scapy.fields import BitField, ByteField, BitEnumField, ConditionalField, \
FieldLenField, FieldListField, IntField, IntEnumField, \
LenField, MACField, PadField, PacketField, PacketListField, \
ShortEnumField, ShortField, StrFixedLenField, StrLenField, \
UUIDField, XByteField, XIntField, XShortEnumField, XShortField
from scapy.contrib.dce_rpc import DceRpc, EndiannessField, DceRpcPayload
from scapy.compat import bytes_hex
from scapy.volatile import RandUUID
# Block Packet
BLOCK_TYPES_ENUM = {
0x0000: "AlarmNotification_High",
0x0001: "AlarmNotification_Low",
0x0008: "IODWriteReqHeader",
0x0009: "IODReadReqHeader",
0x0010: "DiagnosisData",
0x0012: "ExpectedIdentificationData",
0x0013: "RealIdentificationData",
0x0014: "SubsituteValue",
0x0015: "RecordInputDataObjectElement",
0x0016: "RecordOutputDataObjectElement",
0x0018: "ARData",
0x0019: "LogBookData",
0x001a: "APIData",
0x001b: "SRLData",
0x0020: "I&M0",
0x0021: "I&M1",
0x0022: "I&M2",
0x0023: "I&M3",
0x0024: "I&M4",
0x0030: "I&M0FilterDataSubmodule",
0x0031: "I&M0FilterDataModule",
0x0032: "I&M0FilterDataDevice",
0x0101: "ARBlockReq",
0x0102: "IOCRBlockReq",
0x0103: "AlarmCRBlockReq",
0x0104: "ExpectedSubmoduleBlockReq",
0x0105: "PrmServerBlockReq",
0x0106: "MCRBlockReq",
0x0107: "ARRPCBlockReq",
0x0108: "ARVendorBlockReq",
0x0109: "IRInfoBlock",
0x010a: "SRInfoBlock",
0x010b: "ARFSUBlock",
0x0110: "IODBlockReq_connect_end",
0x0111: "IODBlockReq_plug",
0x0112: "IOXBlockReq_connect",
0x0113: "IOXBlockReq_plug",
0x0114: "ReleaseBlockReq",
0x0116: "IOXBlockReq_companion",
0x0117: "IOXBlockReq_rt_class_3",
0x0118: "IODBlockReq_connect_begin",
0x0119: "SubmoduleListBlock",
0x0200: "PDPortDataCheck",
0x0201: "PdevData",
0x0202: "PDPortDataAdjust",
0x0203: "PDSyncData",
0x0204: "IsochronousModeData",
0x0205: "PDIRData",
0x0206: "PDIRGlobalData",
0x0207: "PDIRFrameData",
0x0208: "PDIRBeginEndData",
0x0209: "AdjustDomainBoundary",
0x020a: "SubBlock_check_Peers",
0x020b: "SubBlock_check_LineDelay",
0x020c: "SubBlock_check_MAUType",
0x020e: "AdjustMAUType",
0x020f: "PDPortDataReal",
0x0210: "AdjustMulticastBoundary",
0x0211: "PDInterfaceMrpDataAdjust",
0x0212: "PDInterfaceMrpDataReal",
0x0213: "PDInterfaceMrpDataCheck",
0x0214: "PDPortMrpDataAdjust",
0x0215: "PDPortMrpDataReal",
0x0216: "MrpManagerParams",
0x0217: "MrpClientParams",
0x0219: "MrpRingStateData",
0x021b: "AdjustLinkState",
0x021c: "CheckLinkState",
0x021e: "CheckSyncDifference",
0x021f: "CheckMAUTypeDifference",
0x0220: "PDPortFODataReal",
0x0221: "FiberOpticManufacturerSpecific",
0x0222: "PDPortFODataAdjust",
0x0223: "PDPortFODataCheck",
0x0224: "AdjustPeerToPeerBoundary",
0x0225: "AdjustDCPBoundary",
0x0226: "AdjustPreambleLength",
0x0228: "FiberOpticDiagnosisInfo",
0x022a: "PDIRSubframeData",
0x022b: "SubframeBlock",
0x022d: "PDTimeData",
0x0230: "PDNCDataCheck",
0x0231: "MrpInstanceDataAdjustBlock",
0x0232: "MrpInstanceDataRealBlock",
0x0233: "MrpInstanceDataCheckBlock",
0x0240: "PDInterfaceDataReal",
0x0250: "PDInterfaceAdjust",
0x0251: "PDPortStatistic",
0x0400: "MultipleBlockHeader",
0x0401: "COContainerContent",
0x0500: "RecordDataReadQuery",
0x0600: "FSHelloBlock",
0x0601: "FSParameterBlock",
0x0608: "PDInterfaceFSUDataAdjust",
0x0609: "ARFSUDataAdjust",
0x0700: "AutoConfiguration",
0x0701: "AutoConfigurationCommunication",
0x0702: "AutoConfigurationConfiguration",
0x0703: "AutoConfigurationIsochronous",
0x0A00: "UploadBLOBQuery",
0x0A01: "UploadBLOB",
0x0A02: "NestedDiagnosisInfo",
0x0F00: "MaintenanceItem",
0x0F01: "UploadRecord",
0x0F02: "iParameterItem",
0x0F03: "RetrieveRecord",
0x0F04: "RetrieveAllRecord",
0x8001: "AlarmAckHigh",
0x8002: "AlarmAckLow",
0x8008: "IODWriteResHeader",
0x8009: "IODReadResHeader",
0x8101: "ARBlockRes",
0x8102: "IOCRBlockRes",
0x8103: "AlarmCRBlockRes",
0x8104: "ModuleDiffBlock",
0x8105: "PrmServerBlockRes",
0x8106: "ARServerBlockRes",
0x8107: "ARRPCBlockRes",
0x8108: "ARVendorBlockRes",
0x8110: "IODBlockRes_connect_end",
0x8111: "IODBlockRes_plug",
0x8112: "IOXBlockRes_connect",
0x8113: "IOXBlockRes_plug",
0x8114: "ReleaseBlockRes",
0x8116: "IOXBlockRes_companion",
0x8117: "IOXBlockRes_rt_class_3",
0x8118: "IODBlockRes_connect_begin",
}
# IODWriteReq & IODWriteMultipleReq Packets
IOD_WRITE_REQ_INDEX = {
0x8000: "ExpectedIdentificationData_subslot",
0x8001: "RealIdentificationData_subslot",
0x800a: "Diagnosis_channel_subslot",
0x800b: "Diagnosis_all_subslot",
0x800c: "Diagnosis_Maintenance_subslot",
0x8010: "Maintenance_required_in_channel_subslot",
0x8011: "Maintenance_demanded_in_channel_subslot",
0x8012: "Maintenance_required_in_all_channels_subslot",
0x8013: "Maintenance_demanded_in_all_channels_subslot",
0x801e: "SubstitueValue_subslot",
0x8020: "PDIRSubframeData_subslot",
0x8028: "RecordInputDataObjectElement_subslot",
0x8029: "RecordOutputDataObjectElement_subslot",
0x802a: "PDPortDataReal_subslot",
0x802b: "PDPortDataCheck_subslot",
0x802c: "PDIRData_subslot",
0x802d: "Expected_PDSyncData_subslot",
0x802f: "PDPortDataAdjust_subslot",
0x8030: "IsochronousModeData_subslot",
0x8031: "Expected_PDTimeData_subslot",
0x8050: "PDInterfaceMrpDataReal_subslot",
0x8051: "PDInterfaceMrpDataCheck_subslot",
0x8052: "PDInterfaceMrpDataAdjust_subslot",
0x8053: "PDPortMrpDataAdjust_subslot",
0x8054: "PDPortMrpDataReal_subslot",
0x8060: "PDPortFODataReal_subslot",
0x8061: "PDPortFODataCheck_subslot",
0x8062: "PDPortFODataAdjust_subslot",
0x8070: "PdNCDataCheck_subslot",
0x8071: "PDInterfaceAdjust_subslot",
0x8072: "PDPortStatistic_subslot",
0x8080: "PDInterfaceDataReal_subslot",
0x8090: "Expected_PDInterfaceFSUDataAdjust",
0x80a0: "Energy_saving_profile_record_0",
0x80b0: "CombinedObjectContainer",
0x80c0: "Sequence_events_profile_record_0",
0xaff0: "I&M0",
0xaff1: "I&M1",
0xaff2: "I&M2",
0xaff3: "I&M3",
0xaff4: "I&M4",
0xc000: "Expect edIdentificationData_slot",
0xc001: "RealId entificationData_slot",
0xc00a: "Diagno sis_channel_slot",
0xc00b: "Diagnosis_all_slot",
0xc00c: "Diagnosis_Maintenance_slot",
0xc010: "Maintenance_required_in_channel_slot",
0xc011: "Maintenance_demanded_in_channel_slot",
0xc012: "Maintenance_required_in_all_channels_slot",
0xc013: "Maintenance_demanded_in_all_channels_slot",
0xe000: "ExpectedIdentificationData_AR",
0xe001: "RealIdentificationData_AR",
0xe002: "ModuleDiffBlock_AR",
0xe00a: "Diagnosis_channel_AR",
0xe00b: "Diagnosis_all_AR",
0xe00c: "Diagnosis_Maintenance_AR",
0xe010: "Maintenance_required_in_channel_AR",
0xe011: "Maintenance_demanded_in_channel_AR",
0xe012: "Maintenance_required_in_all_channels_AR",
0xe013: "Maintenance_demanded_in_all_channels_AR",
0xe040: "WriteMultiple",
0xe050: "ARFSUDataAdjust_AR",
0xf000: "RealIdentificationData_API",
0xf00a: "Diagnosis_channel_API",
0xf00b: "Diagnosis_all_API",
0xf00c: "Diagnosis_Maintenance_API",
0xf010: "Maintenance_required_in_channel_API",
0xf011: "Maintenance_demanded_in_channel_API",
0xf012: "Maintenance_required_in_all_channels_API",
0xf013: "Maintenance_demanded_in_all_channels_API",
0xf020: "ARData_API",
0xf80c: "Diagnosis_Maintenance_device",
0xf820: "ARData",
0xf821: "APIData",
0xf830: "LogBookData",
0xf831: "PdevData",
0xf840: "I&M0FilterData",
0xf841: "PDRealData",
0xf842: "PDExpectedData",
0xf850: "AutoConfiguration",
0xf860: "GSD_upload",
0xf861: "Nested_Diagnosis_info",
0xfbff: "Trigger_index_CMSM",
}
# ARBlockReq Packets
AR_TYPE = {
0x0001: "IOCARSingle",
0x0006: "IOSAR",
0x0010: "IOCARSingle_RT_CLASS_3",
0x0020: "IOCARSR",
}
# IOCRBlockReq Packets
IOCR_TYPE = {
0x0001: "InputCR",
0x0002: "OutputCR",
0x0003: "MulticastProviderCR",
0x0004: "MulticastConsumerCR",
}
IOCR_BLOCK_REQ_IOCR_PROPERTIES = {
0x1: "RT_CLASS_1",
0x2: "RT_CLASS_2",
0x3: "RT_CLASS_3",
0x4: "RT_CLASS_UDP",
}
# List of all valid activity UUIDs for the DceRpc layer with PROFINET RPC
# endpoint.
#
# Because these are used in overloaded_fields, it must be a ``UUID``, not a
# string.
RPC_INTERFACE_UUID = {
"UUID_IO_DeviceInterface": UUID("dea00001-6c97-11d1-8271-00a02442df7d"),
"UUID_IO_ControllerInterface":
UUID("dea00002-6c97-11d1-8271-00a02442df7d"),
"UUID_IO_SupervisorInterface":
UUID("dea00003-6c97-11d1-8271-00a02442df7d"),
"UUID_IO_ParameterServerInterface":
UUID("dea00004-6c97-11d1-8271-00a02442df7d"),
}
# Generic Block Packet
class BlockHeader(Packet):
"""Abstract packet to centralize block headers fields"""
fields_desc = [
ShortEnumField("block_type", None, BLOCK_TYPES_ENUM),
ShortField("block_length", None),
ByteField("block_version_high", 1),
ByteField("block_version_low", 0),
]
def __new__(cls, name, bases, dct):
raise NotImplementedError()
class Block(Packet):
"""A generic block packet for PNIO RPC"""
fields_desc = [
BlockHeader,
StrLenField("load", "", length_from=lambda pkt: pkt.block_length - 2),
]
# default block_type
block_type = 0
def post_build(self, p, pay):
# update the block_length if needed
if self.block_length is None:
# block_length and block_type are not part of the length count
length = len(p) - 4
p = p[:2] + struct.pack("!H", length) + p[4:]
return Packet.post_build(self, p, pay)
def extract_padding(self, s):
# all fields after block_length are included in the length and must be
# subtracted from the pdu length l
length = self.payload_length()
return s[:length], s[length:]
def payload_length(self):
""" A function for each block, to determine the length of
the payload """
return 0 # default, no payload
# Specific Block Packets
# IODControlRe{q,s}
class IODControlReq(Block):
"""IODControl request block"""
fields_desc = [
BlockHeader,
StrFixedLenField("padding", "", length=2),
UUIDField("ARUUID", None),
ShortField("SessionKey", 0),
XShortField("AlarmSequenceNumber", 0),
# ControlCommand
BitField("ControlCommand_reserved", 0, 9),
BitField("ControlCommand_PrmBegin", 0, 1),
BitField("ControlCommand_ReadyForRT_CLASS_3", 0, 1),
BitField("ControlCommand_ReadyForCompanion", 0, 1),
BitField("ControlCommand_Done", 0, 1),
BitField("ControlCommand_Release", 0, 1),
BitField("ControlCommand_ApplicationReady", 0, 1),
BitField("ControlCommand_PrmEnd", 0, 1),
XShortField("ControlBlockProperties", 0)
]
def post_build(self, p, pay):
# Try to find the right block type
if self.block_type is None:
if self.ControlCommand_PrmBegin:
p = struct.pack("!H", 0x0118) + p[2:]
elif self.ControlCommand_ReadyForRT_CLASS_3:
p = struct.pack("!H", 0x0117) + p[2:]
elif self.ControlCommand_ReadyForCompanion:
p = struct.pack("!H", 0x0116) + p[2:]
elif self.ControlCommand_Release:
p = struct.pack("!H", 0x0114) + p[2:]
elif self.ControlCommand_ApplicationReady:
if self.AlarmSequenceNumber > 0:
p = struct.pack("!H", 0x0113) + p[2:]
else:
p = struct.pack("!H", 0x0112) + p[2:]
elif self.ControlCommand_PrmEnd:
if self.AlarmSequenceNumber > 0:
p = struct.pack("!H", 0x0111) + p[2:]
else:
p = struct.pack("!H", 0x0110) + p[2:]
return Block.post_build(self, p, pay)
def get_response(self):
"""Generate the response block of this request.
Careful: it only sets the fields which can be set from the request
"""
res = IODControlRes()
for field in ["ARUUID", "SessionKey", "AlarmSequenceNumber"]:
res.setfieldval(field, self.getfieldval(field))
res.block_type = self.block_type + 0x8000
return res
class IODControlRes(Block):
"""IODControl response block"""
fields_desc = [
BlockHeader,
StrFixedLenField("padding", "", length=2),
UUIDField("ARUUID", None),
ShortField("SessionKey", 0),
XShortField("AlarmSequenceNumber", 0),
# ControlCommand
BitField("ControlCommand_reserved", 0, 9),
BitField("ControlCommand_PrmBegin", 0, 1),
BitField("ControlCommand_ReadyForRT_CLASS_3", 0, 1),
BitField("ControlCommand_ReadyForCompanion", 0, 1),
BitField("ControlCommand_Done", 1, 1),
BitField("ControlCommand_Release", 0, 1),
BitField("ControlCommand_ApplicationReady", 0, 1),
BitField("ControlCommand_PrmEnd", 0, 1),
XShortField("ControlBlockProperties", 0)
]
# default block_type value
block_type = 0x8110
# The block_type can be among 0x8110 to 0x8118 except 0x8115
# The right type is however determine by the type of the request
# (same type as the request + 0x8000)
# IODWriteRe{q,s}
class IODWriteReq(Block):
"""IODWrite request block"""
fields_desc = [
BlockHeader,
ShortField("seqNum", 0),
UUIDField("ARUUID", None),
XIntField("API", 0),
XShortField("slotNumber", 0),
XShortField("subslotNumber", 0),
StrFixedLenField("padding", "", length=2),
XShortEnumField("index", 0, IOD_WRITE_REQ_INDEX),
LenField("recordDataLength", None, fmt="I"),
StrFixedLenField("RWPadding", "", length=24),
]
# default block_type value
block_type = 0x0008
def payload_length(self):
return self.recordDataLength
def get_response(self):
"""Generate the response block of this request.
Careful: it only sets the fields which can be set from the request
"""
res = IODWriteRes()
for field in ["seqNum", "ARUUID", "API", "slotNumber",
"subslotNumber", "index"]:
res.setfieldval(field, self.getfieldval(field))
return res
class IODWriteRes(Block):
"""IODWrite response block"""
fields_desc = [
BlockHeader,
ShortField("seqNum", 0),
UUIDField("ARUUID", None),
XIntField("API", 0),
XShortField("slotNumber", 0),
XShortField("subslotNumber", 0),
StrFixedLenField("padding", "", length=2),
XShortEnumField("index", 0, IOD_WRITE_REQ_INDEX),
LenField("recordDataLength", None, fmt="I"),
XShortField("additionalValue1", 0),
XShortField("additionalValue2", 0),
IntEnumField("status", 0, ["OK"]),
StrFixedLenField("RWPadding", "", length=16),
]
# default block_type value
block_type = 0x8008
F_PARAMETERS_BLOCK_ID = [
"No_F_WD_Time2_No_F_iPar_CRC", "No_F_WD_Time2_F_iPar_CRC",
"F_WD_Time2_No_F_iPar_CRC", "F_WD_Time2_F_iPar_CRC",
"reserved_4", "reserved_5", "reserved_6", "reserved_7"
]
class FParametersBlock(Packet):
"""F-Parameters configuration block"""
name = "F-Parameters Block"
fields_desc = [
# F_Prm_Flag1
BitField("F_Prm_Flag1_Reserved_7", 0, 1),
BitField("F_CRC_Seed", 0, 1),
BitEnumField("F_CRC_Length", 0, 2,
["CRC-24", "depreciated", "CRC-32", "reserved"]),
BitEnumField("F_SIL", 2, 2, ["SIL_1", "SIL_2", "SIL_3", "No_SIL"]),
BitField("F_Check_iPar", 0, 1),
BitField("F_Check_SeqNr", 0, 1),
# F_Prm_Flag2
BitEnumField("F_Par_Version", 1, 2,
["V1", "V2", "reserved_2", "reserved_3"]),
BitEnumField("F_Block_ID", 0, 3, F_PARAMETERS_BLOCK_ID),
BitField("F_Prm_Flag2_Reserved", 0, 2),
BitField("F_Passivation", 0, 1),
XShortField("F_Source_Add", 0),
XShortField("F_Dest_Add", 0),
ShortField("F_WD_Time", 0),
ConditionalField(
cond=lambda p: p.getfieldval("F_Block_ID") & 0b110 == 0b010,
fld=ShortField("F_WD_Time_2", 0)),
ConditionalField(
cond=lambda p: p.getfieldval("F_Block_ID") & 0b101 == 0b001,
fld=XIntField("F_iPar_CRC", 0)),
XShortField("F_Par_CRC", 0)
]
overload_fields = {
IODWriteReq: {
"index": 0x100, # commonly used index for F-Parameters block
}
}
bind_layers(IODWriteReq, FParametersBlock, index=0x0100)
bind_layers(FParametersBlock, conf.padding_layer)
# IODWriteMultipleRe{q,s}
class PadFieldWithLen(PadField):
"""PadField which handles the i2len function to include padding"""
def i2len(self, pkt, val):
"""get the length of the field, including the padding length"""
fld_len = self._fld.i2len(pkt, val)
return fld_len + self.padlen(fld_len)
class IODWriteMultipleReq(Block):
"""IODWriteMultiple request"""
fields_desc = [
BlockHeader,
ShortField("seqNum", 0),
UUIDField("ARUUID", None),
XIntField("API", 0xffffffff),
XShortField("slotNumber", 0xffff),
XShortField("subslotNumber", 0xffff),
StrFixedLenField("padding", "", length=2),
XShortEnumField("index", 0, IOD_WRITE_REQ_INDEX),
FieldLenField("recordDataLength", None, fmt="I", length_of="blocks"),
StrFixedLenField("RWPadding", "", length=24),
FieldListField("blocks", [],
PadFieldWithLen(PacketField("", None, IODWriteReq), 4),
length_from=lambda pkt: pkt.recordDataLength)
]
# default values
block_type = 0x0008
index = 0xe040
API = 0xffffffff
slotNumber = 0xffff
subslotNumber = 0xffff
def post_build(self, p, pay):
# patch the update of block_length, as requests field must not be
# included. block_length is always 60
if self.block_length is None:
p = p[:2] + struct.pack("!H", 60) + p[4:]
# Remove the final padding added in requests
fld, val = self.getfield_and_val("blocks")
if fld.i2count(self, val) > 0:
length = len(val[-1])
pad = fld.field.padlen(length)
if pad > 0:
p = p[:-pad]
# also reduce the recordDataLength accordingly
if self.recordDataLength is None:
val = struct.unpack("!I", p[36:40])[0]
val -= pad
p = p[:36] + struct.pack("!I", val) + p[40:]
return Packet.post_build(self, p, pay)
def get_response(self):
"""Generate the response block of this request.
Careful: it only sets the fields which can be set from the request
"""
res = IODWriteMultipleRes()
for field in ["seqNum", "ARUUID", "API", "slotNumber",
"subslotNumber", "index"]:
res.setfieldval(field, self.getfieldval(field))
# append all block response
res_blocks = []
for block in self.getfieldval("blocks"):
res_blocks.append(block.get_response())
res.setfieldval("blocks", res_blocks)
return res
class IODWriteMultipleRes(Block):
"""IODWriteMultiple response"""
fields_desc = [
BlockHeader,
ShortField("seqNum", 0),
UUIDField("ARUUID", None),
XIntField("API", 0xffffffff),
XShortField("slotNumber", 0xffff),
XShortField("subslotNumber", 0xffff),
StrFixedLenField("padding", "", length=2),
XShortEnumField("index", 0, IOD_WRITE_REQ_INDEX),
FieldLenField("recordDataLength", None, fmt="I", length_of="blocks"),
XShortField("additionalValue1", 0),
XShortField("additionalValue2", 0),
IntEnumField("status", 0, ["OK"]),
StrFixedLenField("RWPadding", "", length=16),
FieldListField("blocks", [], PacketField("", None, IODWriteRes),
length_from=lambda pkt: pkt.recordDataLength)
]
# default values
block_type = 0x8008
index = 0xe040
def post_build(self, p, pay):
# patch the update of block_length, as requests field must not be
# included. block_length is always 60
if self.block_length is None:
p = p[:2] + struct.pack("!H", 60) + p[4:]
return Packet.post_build(self, p, pay)
# ARBlockRe{q,s}
class ARBlockReq(Block):
"""Application relationship block request"""
fields_desc = [
BlockHeader,
XShortEnumField("ARType", 1, AR_TYPE),
UUIDField("ARUUID", None),
ShortField("SessionKey", 0),
MACField("CMInitiatorMacAdd", None),
UUIDField("CMInitiatorObjectUUID", None),
# ARProperties
BitField("ARProperties_PullModuleAlarmAllowed", 0, 1),
BitEnumField("ARProperties_StartupMode", 0, 1,
["Legacy", "Advanced"]),
BitField("ARProperties_reserved_3", 0, 6),
BitField("ARProperties_reserved_2", 0, 12),
BitField("ARProperties_AcknowledgeCompanionAR", 0, 1),
BitEnumField("ARProperties_CompanionAR", 0, 2,
["Single_AR", "First_AR", "Companion_AR", "reserved"]),
BitEnumField("ARProperties_DeviceAccess", 0, 1,
["ExpectedSubmodule", "Controlled_by_IO_device_app"]),
BitField("ARProperties_reserved_1", 0, 3),
BitEnumField("ARProperties_ParametrizationServer", 0, 1,
["External_PrmServer", "CM_Initator"]),
BitField("ARProperties_SupervisorTakeoverAllowed", 0, 1),
BitEnumField("ARProperties_State", 1, 3, {1: "Active"}),
ShortField("CMInitiatorActivityTimeoutFactor", 1000),
ShortField("CMInitiatorUDPRTPort", 0x8892),
FieldLenField("StationNameLength", None, fmt="H",
length_of="CMInitiatorStationName"),
StrLenField("CMInitiatorStationName", "",
length_from=lambda pkt: pkt.StationNameLength),
]
# default block_type value
block_type = 0x0101
def get_response(self):
"""Generate the response block of this request.
Careful: it only sets the fields which can be set from the request
"""
res = ARBlockRes()
for field in ["ARType", "ARUUID", "SessionKey"]:
res.setfieldval(field, self.getfieldval(field))
return res
class ARBlockRes(Block):
"""Application relationship block response"""
fields_desc = [
BlockHeader,
XShortEnumField("ARType", 1, AR_TYPE),
UUIDField("ARUUID", None),
ShortField("SessionKey", 0),
MACField("CMResponderMacAdd", None),
ShortField("CMResponderUDPRTPort", 0x8892),
]
# default block_type value
block_type = 0x8101
# IOCRBlockRe{q,s}
class IOCRAPIObject(Packet):
"""API item descriptor used in API description of IOCR blocks"""
name = "API item"
fields_desc = [
XShortField("SlotNumber", 0),
XShortField("SubslotNumber", 0),
ShortField("FrameOffset", 0),
]
def extract_padding(self, s):
return None, s # No extra payload
class IOCRAPI(Packet):
"""API description used in IOCR block"""
name = "API"
fields_desc = [
XIntField("API", 0),
FieldLenField("NumberOfIODataObjects", None,
count_of="IODataObjects"),
PacketListField("IODataObjects", [], IOCRAPIObject,
count_from=lambda p: p.NumberOfIODataObjects),
FieldLenField("NumberOfIOCS", None,
count_of="IOCSs"),
PacketListField("IOCSs", [], IOCRAPIObject,
count_from=lambda p: p.NumberOfIOCS),
]
def extract_padding(self, s):
return None, s # No extra payload
class IOCRBlockReq(Block):
"""IO Connection Relationship block request"""
fields_desc = [
BlockHeader,
XShortEnumField("IOCRType", 1, IOCR_TYPE),
XShortField("IOCRReference", 1),
XShortField("LT", 0x8892),
# IOCRProperties
BitField("IOCRProperties_reserved3", 0, 8),
BitField("IOCRProperties_reserved2", 0, 11),
BitField("IOCRProperties_reserved1", 0, 9),
BitEnumField("IOCRProperties_RTClass", 0, 4,
IOCR_BLOCK_REQ_IOCR_PROPERTIES),
ShortField("DataLength", 40),
XShortField("FrameID", 0x8000),
ShortField("SendClockFactor", 32),
ShortField("ReductionRatio", 32),
ShortField("Phase", 1),
ShortField("Sequence", 0),
XIntField("FrameSendOffset", 0xffffffff),
ShortField("WatchdogFactor", 10),
ShortField("DataHoldFactor", 10),
# IOCRTagHeader
BitEnumField("IOCRTagHeader_IOUserPriority", 6, 3,
{6: "IOCRPriority"}),
BitField("IOCRTagHeader_reserved", 0, 1),
BitField("IOCRTagHeader_IOCRVLANID", 0, 12),
MACField("IOCRMulticastMACAdd", None),
FieldLenField("NumberOfAPIs", None, fmt="H", count_of="APIs"),
PacketListField("APIs", [], IOCRAPI,
count_from=lambda p: p.NumberOfAPIs)
]
# default block_type value
block_type = 0x0102
def get_response(self):
"""Generate the response block of this request.
Careful: it only sets the fields which can be set from the request
"""
res = IOCRBlockRes()
for field in ["IOCRType", "IOCRReference", "FrameID"]:
res.setfieldval(field, self.getfieldval(field))
return res
class IOCRBlockRes(Block):
"""IO Connection Relationship block response"""
fields_desc = [
BlockHeader,
XShortEnumField("IOCRType", 1, IOCR_TYPE),
XShortField("IOCRReference", 1),
XShortField("FrameID", 0x8000),
]
# default block_type value
block_type = 0x8102
# ExpectedSubmoduleBlockReq
class ExpectedSubmoduleDataDescription(Packet):
"""Description of the data of a submodule"""
name = "Data Description"
fields_desc = [
XShortEnumField("DataDescription", 0, {1: "Input", 2: "Output"}),
ShortField("SubmoduleDataLength", 0),
ByteField("LengthIOCS", 0),
ByteField("LengthIOPS", 0),
]
def extract_padding(self, s):
return None, s # No extra payload
class ExpectedSubmodule(Packet):
"""Description of a submodule in an API of an expected submodule"""
name = "Submodule"
fields_desc = [
XShortField("SubslotNumber", 0),
XIntField("SubmoduleIdentNumber", 0),
# Submodule Properties
XByteField("SubmoduleProperties_reserved_2", 0),
BitField("SubmoduleProperties_reserved_1", 0, 2),
BitField("SubmoduleProperties_DiscardIOXS", 0, 1),
BitField("SubmoduleProperties_ReduceOutputSubmoduleDataLength", 0, 1),
BitField("SubmoduleProperties_ReduceInputSubmoduleDataLength", 0, 1),
BitField("SubmoduleProperties_SharedInput", 0, 1),
BitEnumField("SubmoduleProperties_Type", 0, 2,
["NO_IO", "INPUT", "OUTPUT", "INPUT_OUTPUT"]),
PacketListField(
"DataDescription", [], ExpectedSubmoduleDataDescription,
count_from=lambda p: 2 if p.SubmoduleProperties_Type == 3 else 1
),
]
def extract_padding(self, s):
return None, s # No extra payload
class ExpectedSubmoduleAPI(Packet):
"""Description of an API in the expected submodules blocks"""
name = "API"
fields_desc = [
XIntField("API", 0),
XShortField("SlotNumber", 0),
XIntField("ModuleIdentNumber", 0),
XShortField("ModuleProperties", 0),
FieldLenField("NumberOfSubmodules", None, fmt="H",
count_of="Submodules"),
PacketListField("Submodules", [], ExpectedSubmodule,
count_from=lambda p: p.NumberOfSubmodules),
]
def extract_padding(self, s):
return None, s # No extra payload
class ExpectedSubmoduleBlockReq(Block):
"""Expected submodule block request"""
fields_desc = [
BlockHeader,
FieldLenField("NumberOfAPIs", None, fmt="H", count_of="APIs"),
PacketListField("APIs", [], ExpectedSubmoduleAPI,
count_from=lambda p: p.NumberOfAPIs)
]
# default block_type value
block_type = 0x0104
def get_response(self):
"""Generate the response block of this request.
Careful: it only sets the fields which can be set from the request
"""
return None # no response associated (should be modulediffblock)
# PROFINET IO DCE/RPC PDU
PNIO_RPC_BLOCK_ASSOCIATION = {
# requests
"0101": ARBlockReq,
"0102": IOCRBlockReq,
"0104": ExpectedSubmoduleBlockReq,
"0110": IODControlReq,
"0111": IODControlReq,
"0112": IODControlReq,
"0113": IODControlReq,
"0114": IODControlReq,
"0116": IODControlReq,
"0117": IODControlReq,
"0118": IODControlReq,
# responses
"8101": ARBlockRes,
"8102": IOCRBlockRes,
"8110": IODControlRes,
"8111": IODControlRes,
"8112": IODControlRes,
"8113": IODControlRes,
"8114": IODControlRes,
"8116": IODControlRes,
"8117": IODControlRes,
"8118": IODControlRes,
}
def _guess_block_class(_pkt, *args, **kargs):
cls = Block # Default block type
# Special cases
if _pkt[:2] == b'\x00\x08': # IODWriteReq
if _pkt[34:36] == b'\xe0@': # IODWriteMultipleReq
cls = IODWriteMultipleReq
else:
cls = IODWriteReq
elif _pkt[:2] == b'\x80\x08': # IODWriteRes
if _pkt[34:36] == b'\xe0@': # IODWriteMultipleRes
cls = IODWriteMultipleRes
else:
cls = IODWriteRes
# Common cases
else:
btype = bytes_hex(_pkt[:2]).decode("utf8")
if btype in PNIO_RPC_BLOCK_ASSOCIATION:
cls = PNIO_RPC_BLOCK_ASSOCIATION[btype]
return cls(_pkt, *args, **kargs)
def dce_rpc_endianess(pkt):
"""determine the symbol for the endianness of a the DCE/RPC"""
try:
endianness = pkt.underlayer.endianness
except AttributeError:
# handle the case where a PNIO class is
# built without its DCE-RPC under-layer
# i.e there is no endianness indication
return "!"
if endianness == 0: # big endian
return ">"
elif endianness == 1: # little endian
return "<"
else:
return "!"
class NDRData(Packet):
"""Base NDRData to centralize some fields. It can't be instantiated"""
fields_desc = [
EndiannessField(
FieldLenField("args_length", None, fmt="I", length_of="blocks"),
endianess_from=dce_rpc_endianess),
EndiannessField(
FieldLenField("max_count", None, fmt="I", length_of="blocks"),
endianess_from=dce_rpc_endianess),
EndiannessField(
IntField("offset", 0),
endianess_from=dce_rpc_endianess),
EndiannessField(
FieldLenField("actual_count", None, fmt="I", length_of="blocks"),
endianess_from=dce_rpc_endianess),
PacketListField("blocks", [], _guess_block_class,
length_from=lambda p: p.args_length)
]
def __new__(cls, name, bases, dct):
raise NotImplementedError()
class PNIOServiceReqPDU(Packet):
"""PNIO PDU for RPC Request"""
fields_desc = [
EndiannessField(
FieldLenField("args_max", None, fmt="I", length_of="blocks"),
endianess_from=dce_rpc_endianess),
NDRData,
]
overload_fields = {
DceRpc: {
# random object_uuid in the appropriate range
"object_uuid": RandUUID("dea00000-6c97-11d1-8271-******"),
# interface uuid to send to a device
"interface_uuid": RPC_INTERFACE_UUID["UUID_IO_DeviceInterface"],
# Request DCE/RPC type
"type": 0,
},
}
@classmethod
def can_handle(cls, pkt, rpc):
"""heuristic guess_payload_class"""
# type = 0 => request
if rpc.getfieldval("type") == 0 and \
str(rpc.object_uuid).startswith("dea00000-6c97-11d1-8271-"):
return True
return False
DceRpcPayload.register_possible_payload(PNIOServiceReqPDU)
class PNIOServiceResPDU(Packet):
"""PNIO PDU for RPC Response"""
fields_desc = [
EndiannessField(IntEnumField("status", 0, ["OK"]),
endianess_from=dce_rpc_endianess),
NDRData,
]
overload_fields = {
DceRpc: {
# random object_uuid in the appropriate range
"object_uuid": RandUUID("dea00000-6c97-11d1-8271-******"),
# interface uuid to send to a host
"interface_uuid": RPC_INTERFACE_UUID[
"UUID_IO_ControllerInterface"],
# Request DCE/RPC type
"type": 2,
},
}
@classmethod
def can_handle(cls, pkt, rpc):
"""heuristic guess_payload_class"""
# type = 2 => response
if rpc.getfieldval("type") == 2 and \
str(rpc.object_uuid).startswith("dea00000-6c97-11d1-8271-"):
return True
return False
DceRpcPayload.register_possible_payload(PNIOServiceResPDU)