PROFINET IO RTC

PROFINET IO是一种工业协议,由不同层组成,例如实时循环(RTC)层,用于交换数据. 但是,该RTC层是有状态的,并且取决于通过另一层发送的配置:PROFINET的DCE / RPC端点. 该配置定义了每个交换的数据必须在RTC data缓冲区中的位置以及该缓冲区的长度. 这样,建立这样的分组比其他协议要复杂一些.

RTC data packet

建立RTC data缓冲区时,要做的第一件事是实例化代表数据的每个Scapy数据包. 它们中的每个都可能需要一些特定的配置,例如其长度. 所有数据包及其配置为:

  • PNIORealTimeRawData :像Raw这样的简单原始数据

    • length :定义数据的长度

  • Profisafe :执行功能安全的PROFIsafe配置文件

    • length :定义整个数据包的长度

    • CRC :定义CRC的长度,为34

  • PNIORealTimeIOxS :IO使用者或提供者状态字节

    • Doesn’t require any configuration

要使用其配置实例化这些数据包之一,必须提供config参数. 它是一个dict() ,其中包含所有必需的配置:

>>> load_contrib('pnio_rtc')
>>> raw(PNIORealTimeRawData(load='AAA', config={'length': 4}))
'AAA\x00'
>>> raw(Profisafe(load='AAA', Control_Status=0x20, CRC=0x424242, config={'length': 8, 'CRC': 3}))
'AAA\x00 BBB'
>>> hexdump(PNIORealTimeIOxS())
0000   80                                                 .

RTC packet

现在可以实例化数据包,可以构建整个RTC包. PNIORealTime包含一个字段data ,该字段data是要添加到缓冲区中的所有数据包的列表,但是,如果没有配置,Scapy将无法对其进行剖析:

>>> load_contrib("pnio_rtc")
>>> p=PNIORealTime(cycleCounter=1024, data=[
... PNIORealTimeIOxS(),
... PNIORealTimeRawData(load='AAA', config={'length':4}) / PNIORealTimeIOxS(),
... Profisafe(load='AAA', Control_Status=0x20, CRC=0x424242, config={'length': 8, 'CRC': 3}) / PNIORealTimeIOxS(),
... ])
>>> p.show()
###[ PROFINET Real-Time ]###
  len= None
  dataLen= None
  \data\
   |###[ PNIO RTC IOxS ]###
   |  dataState= good
   |  instance= subslot
   |  reserved= 0x0
   |  extension= 0
   |###[ PNIO RTC Raw data ]###
   |  load= 'AAA'
   |###[ PNIO RTC IOxS ]###
   |     dataState= good
   |     instance= subslot
   |     reserved= 0x0
   |     extension= 0
   |###[ PROFISafe ]###
   |  load= 'AAA'
   |  Control_Status= 0x20
   |  CRC= 0x424242
   |###[ PNIO RTC IOxS ]###
   |     dataState= good
   |     instance= subslot
   |     reserved= 0x0
   |     extension= 0
  padding= ''
  cycleCounter= 1024
  dataStatus= primary+validData+run+no_problem
  transferStatus= 0

>>> p.show2()
###[ PROFINET Real-Time ]###
  len= 44
  dataLen= 15
  \data\
   |###[ PNIO RTC Raw data ]###
   |  load= '\x80AAA\x00\x80AAA\x00 BBB\x80'
  padding= ''
  cycleCounter= 1024
  dataStatus= primary+validData+run+no_problem
  transferStatus= 0

为了使Scapy能够正确进行剖析,还必须对其进行配置,以使其了解缓冲区中每个数据的位置. 此配置保存在字典conf.contribs["PNIO_RTC"] ,该字典可以使用pnio_update_config方法进行更新. 字典中的每个项目都使用元组(Ether.src, Ether.dst)作为键,以便能够分隔每个通信的配置. 然后,每个值都是描述数据包的元组列表. 它由数据缓冲区末端的负索引,数据包位置,数据包的类别(第二项)和config字典(最后提供给该类别)组成. 如果继续前面的示例,则要设置以下配置:

>>> load_contrib("pnio")
>>> e=Ether(src='00:01:02:03:04:05', dst='06:07:08:09:0a:0b') / ProfinetIO() / p
>>> e.show2()
###[ Ethernet ]###
  dst= 06:07:08:09:0a:0b
  src= 00:01:02:03:04:05
  type= 0x8892
###[ ProfinetIO ]###
     frameID= RT_CLASS_1
###[ PROFINET Real-Time ]###
  len= 44
  dataLen= 15
  \data\
   |###[ PNIO RTC Raw data ]###
   |  load= '\x80AAA\x00\x80AAA\x00 BBB\x80'
  padding= ''
  cycleCounter= 1024
  dataStatus= primary+validData+run+no_problem
  transferStatus= 0
>>> pnio_update_config({('00:01:02:03:04:05', '06:07:08:09:0a:0b'): [
... (-9, Profisafe, {'length': 8, 'CRC': 3}),
... (-9 - 5, PNIORealTimeRawData, {'length':4}),
... ]})
>>> e.show2()
###[ Ethernet ]###
  dst= 06:07:08:09:0a:0b
  src= 00:01:02:03:04:05
  type= 0x8892
###[ ProfinetIO ]###
     frameID= RT_CLASS_1
###[ PROFINET Real-Time ]###
        len= 44
        dataLen= 15
        \data\
         |###[ PNIO RTC IOxS ]###
         |  dataState= good
         |  instance= subslot
         |  reserved= 0x0L
         |  extension= 0L
         |###[ PNIO RTC Raw data ]###
         |  load= 'AAA'
         |###[ PNIO RTC IOxS ]###
         |     dataState= good
         |     instance= subslot
         |     reserved= 0x0L
         |     extension= 0L
         |###[ PROFISafe ]###
         |  load= 'AAA'
         |  Control_Status= 0x20
         |  CRC= 0x424242L
         |###[ PNIO RTC IOxS ]###
         |     dataState= good
         |     instance= subslot
         |     reserved= 0x0L
         |     extension= 0L
        padding= ''
        cycleCounter= 1024
        dataStatus= primary+validData+run+no_problem
        transferStatus= 0

如果没有为给定偏移量配置任何数据包,则默认为PNIORealTimeIOxS . 但是,该方法对于用户配置层不是很方便,它仅影响数据包的拆分. 在这种情况下,一个人可能可以访问从PCAP文件中嗅探或检索到的多个RTC数据包. 因此, PNIORealTime基于简单的启发式方法提供了一些方法来分析PNIORealTime数据包列表并在其中定位所有数据. 所有这些参数都将一个可迭代的内容作为第一个参数,其中包含要分析的数据包列表.

  • PNIORealTime.find_data()分析数据缓冲区,并将实际数据与IOxS分开. 它返回可以提供给pnio_update_config .

  • PNIORealTime.find_profisafe()分析数据缓冲区并在实际数据中找到PROFIsafe配置文件. 它返回可以提供给pnio_update_config .

  • PNIORealTime.analyse_data()执行先前的两个方法并更新配置. 这通常是调用方法.

  • PNIORealTime.draw_entropy()将绘制数据缓冲区中每个字节的熵. 由于熵是find_profisafe决策算法的基础,因此可用于轻松可视化PROFIsafe位置.

>>> load_contrib('pnio_rtc')
>>> t=rdpcap('/path/to/trace.pcap', 1024)
>>> PNIORealTime.analyse_data(t)
{('00:01:02:03:04:05', '06:07:08:09:0a:0b'): [(-19, <class 'scapy.contrib.pnio_rtc.PNIORealTimeRawData'>, {'length': 1}), (-15, <class 'scapy.contrib.pnio_rtc.Profisafe'>, {'CRC': 3, 'length': 6}), (-7, <class 'scapy.contrib.pnio_rtc.Profisafe'>, {'CRC': 3, 'length': 5})]}
>>> t[100].show()
###[ Ethernet ]###
  dst= 06:07:08:09:0a:0b
  src= 00:01:02:03:04:05
  type= n_802_1Q
###[ 802.1Q ]###
     prio= 6L
     id= 0L
     vlan= 0L
     type= 0x8892
###[ ProfinetIO ]###
        frameID= RT_CLASS_1
###[ PROFINET Real-Time ]###
           len= 44
           dataLen= 22
           \data\
            |###[ PNIO RTC Raw data ]###
            |  load= '\x80\x80\x80\x80\x80\x80\x00\x80\x80\x80\x12:\x0e\x12\x80\x80\x00\x12\x8b\x97\xe3\x80'
           padding= ''
           cycleCounter= 6208
           dataStatus= primary+validData+run+no_problem
           transferStatus= 0

>>> t[100].show2()
###[ Ethernet ]###
  dst= 06:07:08:09:0a:0b
  src= 00:01:02:03:04:05
  type= n_802_1Q
###[ 802.1Q ]###
     prio= 6L
     id= 0L
     vlan= 0L
     type= 0x8892
###[ ProfinetIO ]###
        frameID= RT_CLASS_1
###[ PROFINET Real-Time ]###
           len= 44
           dataLen= 22
           \data\
            |###[ PNIO RTC IOxS ]###
            |  dataState= good
            |  instance= subslot
            |  reserved= 0x0L
            |  extension= 0L
            [...]
            |###[ PNIO RTC IOxS ]###
            |  dataState= good
            |  instance= subslot
            |  reserved= 0x0L
            |  extension= 0L
            |###[ PNIO RTC Raw data ]###
            |  load= ''
            |###[ PNIO RTC IOxS ]###
            |     dataState= good
            |     instance= subslot
            |     reserved= 0x0L
            |     extension= 0L
            [...]
            |###[ PNIO RTC IOxS ]###
            |  dataState= good
            |  instance= subslot
            |  reserved= 0x0L
            |  extension= 0L
            |###[ PROFISafe ]###
            |  load= ''
            |  Control_Status= 0x12
            |  CRC= 0x3a0e12L
            |###[ PNIO RTC IOxS ]###
            |     dataState= good
            |     instance= subslot
            |     reserved= 0x0L
            |     extension= 0L
            |###[ PNIO RTC IOxS ]###
            |  dataState= good
            |  instance= subslot
            |  reserved= 0x0L
            |  extension= 0L
            |###[ PROFISafe ]###
            |  load= ''
            |  Control_Status= 0x12
            |  CRC= 0x8b97e3L
            |###[ PNIO RTC IOxS ]###
            |     dataState= good
            |     instance= subslot
            |     reserved= 0x0L
            |     extension= 0L
           padding= ''
           cycleCounter= 6208
           dataStatus= primary+validData+run+no_problem
           transferStatus= 0

另外,当显示PNIORealTime数据包时,可以看到len字段. 这是一个计算字段,不会在最终的数据包构建中添加. 它主要用于解剖和重建,但也可以用于修改数据包的行为. 实际上,RTC数据包对于以太网帧必须始终足够长,为此,必须在data缓冲区之后立即添加填充. 缺省行为是添加padding的大小,该padding的大小是在build过程中计算的:

>>> raw(PNIORealTime(cycleCounter=0x4242, data=[PNIORealTimeIOxS()]))
'\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00BB5\x00'

但是,可以将len设置为修改此行为. len控制整个PNIORealTime数据包的长度. 然后,为了缩短填充的长度,可以将len设置为较低的值:

>>> raw(PNIORealTime(cycleCounter=0x4242, data=[PNIORealTimeIOxS()], len=50))
'\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00BB5\x00'
>>> raw(PNIORealTime(cycleCounter=0x4242, data=[PNIORealTimeIOxS()]))
'\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00BB5\x00'
>>> raw(PNIORealTime(cycleCounter=0x4242, data=[PNIORealTimeIOxS()], len=30))
'\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00BB5\x00'