1% PNIO RTC layer test campaign 2 3+ Syntax check 4= Import the PNIO RTC layer 5from scapy.contrib.pnio import * 6from scapy.contrib.pnio_rtc import * 7 8 9+ Check PNIORealTimeIOxS 10 11= PNIORealTimeIOxS default values 12raw(PNIORealTimeIOxS()) == b'\x80' 13 14= Check no payload is dissected (only padding) 15* In order for the PNIORealTime to dissect correctly all the data buffer, data field must strictly dissect what they know as being of themselves 16p = PNIORealTimeIOxS(b'\x40\x01\x02') 17p == PNIORealTimeIOxS(dataState='bad', instance='device') / conf.padding_layer(b'\x01\x02') 18 19 20+ Check PNIORealTimeRawData 21 22= PNIORealTimeRawData default values 23raw(PNIORealTimeRawData(config={'length': 5})) == b'\x00\x00\x00\x00\x00' 24 25= PNIORealTimeRawData must always be the same configured length 26raw(PNIORealTimeRawData(load='ABC', config={'length': 5})) == b'ABC\x00\x00' 27 28= PNIORealTimeRawData may be truncated 29raw(PNIORealTimeRawData(load='ABCDEF', config={'length': 5})) == b'ABCDE' 30 31= Check that the dissected payload is an PNIORealTimeIOxS (IOPS) 32p = PNIORealTimeRawData(b'ABCDE\x80\x01\x02', config={'length': 5}) 33p == PNIORealTimeRawData(load=b'ABCDE', config={'length': 5}) / PNIORealTimeIOxS() / conf.padding_layer(b'\x01\x02') 34 35= PNIORealTimeRawData is capable of dissected uncomplete packets 36p = PNIORealTimeRawData('ABC', config={'length': 5}) 37p == PNIORealTimeRawData(load=b'ABC', config={'length': 5}) 38 39 40+ Check Profisafe 41 42= Profisafe default values 43raw(Profisafe(config={'length': 7, 'CRC': 3})) == b'\0\0\0\0\0\0\0' 44 45= Profisafe must always be the same configured length 46raw(Profisafe(load=b'AB', config={'length': 7, 'CRC': 3})) == b'AB\0\0\0\0\0' 47 48= Profisafe load may be truncated 49raw(Profisafe(load=b'ABCDEF', config={'length': 7, 'CRC': 3})) == b'ABC\0\0\0\0' 50 51= Check that the dissected payload is an PNIORealTimeIOxS (IOPS) 52p = Profisafe(b'ABC\x20\x12\x34\x56\x80\x01\x02', config={'length': 7, 'CRC': 3}) 53p == Profisafe(load=b'ABC', Control_Status=0x20, CRC=0x123456, config={'length': 7, 'CRC': 3}) / PNIORealTimeIOxS() / conf.padding_layer(b'\x01\x02') 54 55= Profisafe with a CRC-32 56raw(Profisafe(load=b'ABC', Control_Status=0x33, CRC=0x12345678, config={'length': 8, 'CRC': 4})) == b'ABC\x33\x12\x34\x56\x78' 57 58= Profisafe is capable of dissected uncomplete packets 59p = Profisafe('AB', config={'length': 7, 'CRC': 3}) 60p == Profisafe(load=b'AB', Control_Status=0, CRC=0) 61 62 63+ Check PNIORealTime layer 64 65= PNIORealTime default values 66raw(PNIORealTime()) == b'\0' * 40 + b'\0\0\x35\0' 67 68= PNIORealTime default values under an UDP packet 69raw(UDP(sport=0x1234) / ProfinetIO(frameID=0x8002) / PNIORealTime()) == hex_bytes('12348892001a00008002') + b'\0' * 12 + b'\0\0\x35\0' 70 71= PNIORealTime simple packet 72* a simple data packet with a raw profinet data and its IOPS, an IOCS and a Profisafe data and its IOPS. 15B data length, 1B padding (20 - 15 -4) 73raw(PNIORealTime(len=20, dataLen=15, cycleCounter=0x1234, dataStatus='redundancy+validData+no_problem', transferStatus=3, 74 data=[ 75 PNIORealTimeRawData(load=b'\x01\x02\x03\x04', config={'length': 5}) / PNIORealTimeIOxS(), 76 PNIORealTimeIOxS(dataState='bad'), 77 Profisafe(load=b'\x05\x06', Control_Status=0x20, CRC=0x12345678, config={'length': 7, 'CRC': 4}) / PNIORealTimeIOxS() 78 ] 79 )) == hex_bytes('0102030400800005062012345678800012342603') 80 81= PNIORealTime dissects to PNIORealTimeRawData when no config is available 82p = PNIORealTime(hex_bytes('0102030400800005062012345678800012342603')) 83p == PNIORealTime(len=20, dataLen=15, cycleCounter=0x1234, dataStatus='redundancy+validData+no_problem', transferStatus=3, padding=b'\0', 84 data=[ 85 PNIORealTimeRawData(load=hex_bytes('010203040080000506201234567880')) 86 ] 87 ) 88 89= PNIORealTime dissection is configurable 90* Usually, the configuration is not given manually, but using PNIORealTime.analyse_data() on a list of Packets which analyses and updates the configuration 91pnio_update_config({ 92 ('06:07:08:09:0a:0b', '00:01:02:03:04:05'): [ 93 (-15, PNIORealTimeRawData, {'length': 5}), 94 (-8, Profisafe, {'length': 7, 'CRC': 4}), 95 ] 96 }) 97p = Ether(hex_bytes('000102030405060708090a0b889280020102030400800005062012345678800012342603')) 98p == Ether(dst='00:01:02:03:04:05', src='06:07:08:09:0a:0b') / ProfinetIO(frameID=0x8002) / PNIORealTime( 99 len=20, dataLen=15, cycleCounter=0x1234, dataStatus='redundancy+validData+no_problem', transferStatus=3, padding=b'\0', 100 data=[ 101 PNIORealTimeRawData(load=b'\x01\x02\x03\x04\0', config={'length': 5}) / PNIORealTimeIOxS(), 102 PNIORealTimeIOxS(dataState='bad'), 103 Profisafe(load=b'\x05\x06', Control_Status=0x20, CRC=0x12345678, config={'length': 7, 'CRC': 4}) / PNIORealTimeIOxS() 104 ] 105 ) 106 107= PNIORealTime - Analyse data 108# Possible thanks to https://github.com/ITI/ICS-Security-Tools/blob/master/pcaps/profinet/profinet.pcap 109 110bind_layers(UDP, ProfinetIO, dport=0x8894) 111bind_layers(UDP, ProfinetIO, sport=0x8894) 112 113packets = [Ether(hex_bytes('0090274ee3fc000991442017080045000128000c00004011648f0a0a00810a0a00968894061e011444c604022800100000000000a0de976cd111827100010003015a0100a0de976cd111827100a02442df7ddbabbaec1d005443b2500b01630abafd0100000001000000000000000500ffffffffbc000000000000000000a80000004080000000000000a80000008009003c0100000a0000000000000000000000000000000000000000000000010000f840000000680000000000000000000000000000000000000000000000000030002c0100000100000000000200000000000100020001000000010003ffff010a0001ffff814000010001ffff814000310018010000010000000000010001ffff814000010001ffff814000320018010000010000000000010000000000010001000100000001')), 114 Ether(hex_bytes('0009914420170090274ee3fc0800450000c0b28800008011727a0a0a00960a0a0081061e889400ac689504000800100000000000a0de976cd111827100010003015a0100a0de976cd111827100a02442df7ddbabbaec1d005443b2500b01630abafd0000000001000000000000000500ffffffff54000000000040800000400000004080000000000000400000000009003c0100000a0000000000000000000000000000000000000000000000010000f84000008000000000000000000000000000000000000000000000000000'))] 115 116analysed_data = PNIORealTime.analyse_data(packets) 117assert len(analysed_data) == 2 118x = analysed_data[('00:09:91:44:20:17', '00:90:27:4e:e3:fc')] 119assert len(x) == 2 120assert x[0][1] == PNIORealTimeRawData 121assert x[0][0] == -262 122assert x[0][2]["length"] == 87 123 124ProfinetIO._overload_fields[UDP].pop("sport") 125ProfinetIO._overload_fields[UDP]["dport"] = 0x8892