From 5627588fc0709d4c8b3561584fc5da5d0843bd71 Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 15:02:07 +0100 Subject: [PATCH 01/21] Update mitm.py --- mitm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mitm.py b/mitm.py index 4bea496..a1941bf 100644 --- a/mitm.py +++ b/mitm.py @@ -8,7 +8,7 @@ from hashlib import md5 from time import sleep import scapy.all as scapy -from IEC104_Raw.dissector import * +from .IEC104_Raw.dissector import * def getMAC(ip: str, interface: str) -> str: ans, unans = scapy.srp(scapy.Ether(dst='ff:ff:ff:ff:ff:ff')/scapy.ARP(op=1, pdst=ip), iface=interface, verbose=0) From a8e13ade4e013003b8961ebee1c78b0f62760eae Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 15:02:45 +0100 Subject: [PATCH 02/21] Update mitm.py --- mitm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mitm.py b/mitm.py index a1941bf..2fe67f9 100644 --- a/mitm.py +++ b/mitm.py @@ -8,7 +8,7 @@ from hashlib import md5 from time import sleep import scapy.all as scapy -from .IEC104_Raw.dissector import * +from nefics.IEC104_Raw.dissector import * def getMAC(ip: str, interface: str) -> str: ans, unans = scapy.srp(scapy.Ether(dst='ff:ff:ff:ff:ff:ff')/scapy.ARP(op=1, pdst=ip), iface=interface, verbose=0) From 9b3835d1719f2af2d9e22ebdbeaa8b73f40cbf6d Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 15:03:21 +0100 Subject: [PATCH 03/21] Update launch_tx.py --- launch_tx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launch_tx.py b/launch_tx.py index ecbee1b..e43b87b 100644 --- a/launch_tx.py +++ b/launch_tx.py @@ -4,8 +4,8 @@ import signal from threading import Thread from time import sleep -from rtu import Transmission, RTU_TRANSMISSION -from simcomm import SimulationHandler +from nefics.rtu import Transmission, RTU_TRANSMISSION +from nefics.simcomm import SimulationHandler if __name__ == '__main__': if sys.platform[:3] == 'win': From a134755e4917405d3dc64eaed9337d6a45f4262c Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 15:03:49 +0100 Subject: [PATCH 04/21] Update launch_src.py --- launch_src.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launch_src.py b/launch_src.py index 0d481d7..9e78769 100644 --- a/launch_src.py +++ b/launch_src.py @@ -4,8 +4,8 @@ import signal from threading import Thread from time import sleep -from rtu import Source, RTU_SOURCE -from simcomm import SimulationHandler +from nefics.rtu import Source, RTU_SOURCE +from nefics.simcomm import SimulationHandler if __name__ == '__main__': if sys.platform[:3] == 'win': From 662f67da96abfc5d64b74b76fb328795176970af Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 15:04:13 +0100 Subject: [PATCH 05/21] Update launch_load.py --- launch_load.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launch_load.py b/launch_load.py index 33bd177..12b8cd0 100644 --- a/launch_load.py +++ b/launch_load.py @@ -4,8 +4,8 @@ import signal from threading import Thread from time import sleep -from rtu import Load, RTU_LOAD -from simcomm import SimulationHandler +from nefics.rtu import Load, RTU_LOAD +from nefics.simcomm import SimulationHandler if __name__ == '__main__': if sys.platform[:3] == 'win': From eb7b6dcf20b9d102408a0af5401fe029c206bc24 Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 15:21:09 +0100 Subject: [PATCH 06/21] Create const.py --- IEC104_Parser/const.py | 186 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 IEC104_Parser/const.py diff --git a/IEC104_Parser/const.py b/IEC104_Parser/const.py new file mode 100644 index 0000000..cecfed3 --- /dev/null +++ b/IEC104_Parser/const.py @@ -0,0 +1,186 @@ +TYPEID_ASDU = { + 0x01: 'M_SP_NA_1 (1)', + 0x03: 'M_DP_NA_1 (3)', + 0x05: 'M_ST_NA_1 (5)', + 0x07: 'M_BO_NA_1 (7)', + 0x09: 'M_ME_NA_1 (9)', + 0x0D: 'M_ME_NC_1 (13)', + 0x1E: 'M_SP_TB_1 (30)', + 0x1F: 'M_DP_TB_1 (31)', + 0x24: 'M_ME_TF_1 (36)', + 0x2D: 'C_SC_NA_1 (45)', + 0x2E: 'C_DC_NA_1 (46)', + 0x32: 'C_SE_NC_1 (50)', + 0x46: 'M_EI_NA_1 (70)', + 0x64: 'C_IC_NA_1 (100)', + 0x67: 'C_CS_NA_1 (103)', +} + +TYPE_APCI = { + 0x00: 'I (0x00)', + 0x01: 'S (0x01)', + 0x03: 'U (0x03)' +} + +UNNUMBERED_CONTROL_FIELD = { + 0x80: 'TESTFR con', + 0x40: 'TESTFR act', + 0x20: 'STOPDT con', + 0x10: 'STOPDT act', + 0x08: 'STARTDT con', + 0x04: 'STARTDT act' +} + +CAUSE_OF_TX = { + 0: 'not used', + 1: 'per/cyc', + 2: 'back', + 3: 'spont', + 4: 'init', + 5: 'req', + 6: 'act', + 7: 'ActCon', + 8: 'deact', + 9: 'DeactCon', + 10: 'ActTerm', + 11: 'retrem', + 12: 'retloc', + 13: 'file', + 20: 'inrogen', + 21: 'inro1', + 22: 'inro2', + 23: 'inro3', + 24: 'inro4', + 25: 'inro5', + 26: 'inro6', + 27: 'inro7', + 28: 'inro8', + 29: 'inro9', + 30: 'inro10', + 31: 'inro11', + 32: 'inro12', + 33: 'inro13', + 34: 'inro14', + 35: 'inro15', + 36: 'inro16', + 37: 'reqcogen', + 38: 'reqco1', + 39: 'reqco2', + 40: 'reqco3', + 41: 'reqco4', + 44: 'unknown type identification', + 45: 'unknown cause of transmission', + 46: 'unknown common address of ASDU', + 47: 'unknown information object address' +} + +QDS_FLAGS = { + 1: 'Overflow', + 5: 'Blocked', + 6: 'Substituted', + 7: 'Not topical', + 8: 'Invalid' +} + +DIQ_FLAGS = { + 5: 'Blocked', + 6: 'Substituted', + 7: 'Not topical', + 8: 'Invalid' +} + +SIQ_FLAGS = { + 1: 'SPI', + 5: 'Blocked', + 6: 'Subsituted', + 7: 'Not topical', + 8: 'Invalid' +} + +SQ = { + 0X00: False, + 0x80: True +} + +SU = { + 0X80: 'summer time', + 0x00: 'normal time' +} + +#Day Of Week +DOW = { + 0x00: 'undefined', + 0x20: 'monday', + 0x40: 'tuesday', + 0x60: 'wednesday', + 0x80: 'thursday', + 0xA0: 'friday', + 0xC0: 'saturday', + 0xE0: 'sunday' +} + +SEL_EXEC = { + 0x00: 'Execute', + 0x80: 'Select' +} + +DPI_ENUM = { + 0x00: 'Indeterminate or Intermediate state', + 0x01: 'Determined state OFF', + 0x02: 'Determined state ON', + 0x03: 'Indeterminate state' +} + +TRANSIENT = { + 0x00: 'not in transient', + 0x80: 'in transient' +} + +QOI_ENUM = { + 0x14: 'Station interrogation (global)', + 0x15: 'Interrogation of group 1', + 0x16: 'Interrogation of group 2', + 0x17: 'Interrogation of group 3', + 0x18: 'Interrogation of group 4', + 0x19: 'Interrogation of group 5', + 0x1A: 'Interrogation of group 6', + 0x1B: 'Interrogation of group 7', + 0x1C: 'Interrogation of group 8', + 0x1D: 'Interrogation of group 9', + 0x1E: 'Interrogation of group 10', + 0x1F: 'Interrogation of group 11', + 0x20: 'Interrogation of group 12', + 0x21: 'Interrogation of group 13', + 0x22: 'Interrogation of group 14', + 0x23: 'Interrogation of group 15', + 0x24: 'Interrogation of group 16' +} + + +R_ENUM = { + 0x00: 'Local power switch on', + 0x01: 'Local manual reset', + 0x02: 'Remote reset', +} +for i in range(0x03, 0x7f): + R_ENUM[i] = 'Undefined' + +I_ENUM = { + 0x00: 'Initialization with unchanged local parameters', + 0x80: 'Initialization after change of local parameters' +} + +QU_ENUM = { + 0x00: 'no pulse defined', + 0x01: 'short pulse duration (circuit-breaker)', + 0x02: 'long pulse duration', + 0x03: 'persistent output', + 0x04: 'reserved', + 0x05: 'reserved', + 0x06: 'reserved', +} + +SCS_ENUM = { + 0x00: 'OFF', + 0x01: 'ON' +} From 95360d3d41df551009fb3c2c5df7d43993f76069 Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 15:21:46 +0100 Subject: [PATCH 07/21] Delete IEC104_Parser directory --- IEC104_Parser/const.py | 186 ----------------------------------------- 1 file changed, 186 deletions(-) delete mode 100644 IEC104_Parser/const.py diff --git a/IEC104_Parser/const.py b/IEC104_Parser/const.py deleted file mode 100644 index cecfed3..0000000 --- a/IEC104_Parser/const.py +++ /dev/null @@ -1,186 +0,0 @@ -TYPEID_ASDU = { - 0x01: 'M_SP_NA_1 (1)', - 0x03: 'M_DP_NA_1 (3)', - 0x05: 'M_ST_NA_1 (5)', - 0x07: 'M_BO_NA_1 (7)', - 0x09: 'M_ME_NA_1 (9)', - 0x0D: 'M_ME_NC_1 (13)', - 0x1E: 'M_SP_TB_1 (30)', - 0x1F: 'M_DP_TB_1 (31)', - 0x24: 'M_ME_TF_1 (36)', - 0x2D: 'C_SC_NA_1 (45)', - 0x2E: 'C_DC_NA_1 (46)', - 0x32: 'C_SE_NC_1 (50)', - 0x46: 'M_EI_NA_1 (70)', - 0x64: 'C_IC_NA_1 (100)', - 0x67: 'C_CS_NA_1 (103)', -} - -TYPE_APCI = { - 0x00: 'I (0x00)', - 0x01: 'S (0x01)', - 0x03: 'U (0x03)' -} - -UNNUMBERED_CONTROL_FIELD = { - 0x80: 'TESTFR con', - 0x40: 'TESTFR act', - 0x20: 'STOPDT con', - 0x10: 'STOPDT act', - 0x08: 'STARTDT con', - 0x04: 'STARTDT act' -} - -CAUSE_OF_TX = { - 0: 'not used', - 1: 'per/cyc', - 2: 'back', - 3: 'spont', - 4: 'init', - 5: 'req', - 6: 'act', - 7: 'ActCon', - 8: 'deact', - 9: 'DeactCon', - 10: 'ActTerm', - 11: 'retrem', - 12: 'retloc', - 13: 'file', - 20: 'inrogen', - 21: 'inro1', - 22: 'inro2', - 23: 'inro3', - 24: 'inro4', - 25: 'inro5', - 26: 'inro6', - 27: 'inro7', - 28: 'inro8', - 29: 'inro9', - 30: 'inro10', - 31: 'inro11', - 32: 'inro12', - 33: 'inro13', - 34: 'inro14', - 35: 'inro15', - 36: 'inro16', - 37: 'reqcogen', - 38: 'reqco1', - 39: 'reqco2', - 40: 'reqco3', - 41: 'reqco4', - 44: 'unknown type identification', - 45: 'unknown cause of transmission', - 46: 'unknown common address of ASDU', - 47: 'unknown information object address' -} - -QDS_FLAGS = { - 1: 'Overflow', - 5: 'Blocked', - 6: 'Substituted', - 7: 'Not topical', - 8: 'Invalid' -} - -DIQ_FLAGS = { - 5: 'Blocked', - 6: 'Substituted', - 7: 'Not topical', - 8: 'Invalid' -} - -SIQ_FLAGS = { - 1: 'SPI', - 5: 'Blocked', - 6: 'Subsituted', - 7: 'Not topical', - 8: 'Invalid' -} - -SQ = { - 0X00: False, - 0x80: True -} - -SU = { - 0X80: 'summer time', - 0x00: 'normal time' -} - -#Day Of Week -DOW = { - 0x00: 'undefined', - 0x20: 'monday', - 0x40: 'tuesday', - 0x60: 'wednesday', - 0x80: 'thursday', - 0xA0: 'friday', - 0xC0: 'saturday', - 0xE0: 'sunday' -} - -SEL_EXEC = { - 0x00: 'Execute', - 0x80: 'Select' -} - -DPI_ENUM = { - 0x00: 'Indeterminate or Intermediate state', - 0x01: 'Determined state OFF', - 0x02: 'Determined state ON', - 0x03: 'Indeterminate state' -} - -TRANSIENT = { - 0x00: 'not in transient', - 0x80: 'in transient' -} - -QOI_ENUM = { - 0x14: 'Station interrogation (global)', - 0x15: 'Interrogation of group 1', - 0x16: 'Interrogation of group 2', - 0x17: 'Interrogation of group 3', - 0x18: 'Interrogation of group 4', - 0x19: 'Interrogation of group 5', - 0x1A: 'Interrogation of group 6', - 0x1B: 'Interrogation of group 7', - 0x1C: 'Interrogation of group 8', - 0x1D: 'Interrogation of group 9', - 0x1E: 'Interrogation of group 10', - 0x1F: 'Interrogation of group 11', - 0x20: 'Interrogation of group 12', - 0x21: 'Interrogation of group 13', - 0x22: 'Interrogation of group 14', - 0x23: 'Interrogation of group 15', - 0x24: 'Interrogation of group 16' -} - - -R_ENUM = { - 0x00: 'Local power switch on', - 0x01: 'Local manual reset', - 0x02: 'Remote reset', -} -for i in range(0x03, 0x7f): - R_ENUM[i] = 'Undefined' - -I_ENUM = { - 0x00: 'Initialization with unchanged local parameters', - 0x80: 'Initialization after change of local parameters' -} - -QU_ENUM = { - 0x00: 'no pulse defined', - 0x01: 'short pulse duration (circuit-breaker)', - 0x02: 'long pulse duration', - 0x03: 'persistent output', - 0x04: 'reserved', - 0x05: 'reserved', - 0x06: 'reserved', -} - -SCS_ENUM = { - 0x00: 'OFF', - 0x01: 'ON' -} From ff1c39f2e3f3dd864c2457d516c1d698a7aaff08 Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 15:22:18 +0100 Subject: [PATCH 08/21] Create const.py --- IEC104_Raw/const.py | 186 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 IEC104_Raw/const.py diff --git a/IEC104_Raw/const.py b/IEC104_Raw/const.py new file mode 100644 index 0000000..cecfed3 --- /dev/null +++ b/IEC104_Raw/const.py @@ -0,0 +1,186 @@ +TYPEID_ASDU = { + 0x01: 'M_SP_NA_1 (1)', + 0x03: 'M_DP_NA_1 (3)', + 0x05: 'M_ST_NA_1 (5)', + 0x07: 'M_BO_NA_1 (7)', + 0x09: 'M_ME_NA_1 (9)', + 0x0D: 'M_ME_NC_1 (13)', + 0x1E: 'M_SP_TB_1 (30)', + 0x1F: 'M_DP_TB_1 (31)', + 0x24: 'M_ME_TF_1 (36)', + 0x2D: 'C_SC_NA_1 (45)', + 0x2E: 'C_DC_NA_1 (46)', + 0x32: 'C_SE_NC_1 (50)', + 0x46: 'M_EI_NA_1 (70)', + 0x64: 'C_IC_NA_1 (100)', + 0x67: 'C_CS_NA_1 (103)', +} + +TYPE_APCI = { + 0x00: 'I (0x00)', + 0x01: 'S (0x01)', + 0x03: 'U (0x03)' +} + +UNNUMBERED_CONTROL_FIELD = { + 0x80: 'TESTFR con', + 0x40: 'TESTFR act', + 0x20: 'STOPDT con', + 0x10: 'STOPDT act', + 0x08: 'STARTDT con', + 0x04: 'STARTDT act' +} + +CAUSE_OF_TX = { + 0: 'not used', + 1: 'per/cyc', + 2: 'back', + 3: 'spont', + 4: 'init', + 5: 'req', + 6: 'act', + 7: 'ActCon', + 8: 'deact', + 9: 'DeactCon', + 10: 'ActTerm', + 11: 'retrem', + 12: 'retloc', + 13: 'file', + 20: 'inrogen', + 21: 'inro1', + 22: 'inro2', + 23: 'inro3', + 24: 'inro4', + 25: 'inro5', + 26: 'inro6', + 27: 'inro7', + 28: 'inro8', + 29: 'inro9', + 30: 'inro10', + 31: 'inro11', + 32: 'inro12', + 33: 'inro13', + 34: 'inro14', + 35: 'inro15', + 36: 'inro16', + 37: 'reqcogen', + 38: 'reqco1', + 39: 'reqco2', + 40: 'reqco3', + 41: 'reqco4', + 44: 'unknown type identification', + 45: 'unknown cause of transmission', + 46: 'unknown common address of ASDU', + 47: 'unknown information object address' +} + +QDS_FLAGS = { + 1: 'Overflow', + 5: 'Blocked', + 6: 'Substituted', + 7: 'Not topical', + 8: 'Invalid' +} + +DIQ_FLAGS = { + 5: 'Blocked', + 6: 'Substituted', + 7: 'Not topical', + 8: 'Invalid' +} + +SIQ_FLAGS = { + 1: 'SPI', + 5: 'Blocked', + 6: 'Subsituted', + 7: 'Not topical', + 8: 'Invalid' +} + +SQ = { + 0X00: False, + 0x80: True +} + +SU = { + 0X80: 'summer time', + 0x00: 'normal time' +} + +#Day Of Week +DOW = { + 0x00: 'undefined', + 0x20: 'monday', + 0x40: 'tuesday', + 0x60: 'wednesday', + 0x80: 'thursday', + 0xA0: 'friday', + 0xC0: 'saturday', + 0xE0: 'sunday' +} + +SEL_EXEC = { + 0x00: 'Execute', + 0x80: 'Select' +} + +DPI_ENUM = { + 0x00: 'Indeterminate or Intermediate state', + 0x01: 'Determined state OFF', + 0x02: 'Determined state ON', + 0x03: 'Indeterminate state' +} + +TRANSIENT = { + 0x00: 'not in transient', + 0x80: 'in transient' +} + +QOI_ENUM = { + 0x14: 'Station interrogation (global)', + 0x15: 'Interrogation of group 1', + 0x16: 'Interrogation of group 2', + 0x17: 'Interrogation of group 3', + 0x18: 'Interrogation of group 4', + 0x19: 'Interrogation of group 5', + 0x1A: 'Interrogation of group 6', + 0x1B: 'Interrogation of group 7', + 0x1C: 'Interrogation of group 8', + 0x1D: 'Interrogation of group 9', + 0x1E: 'Interrogation of group 10', + 0x1F: 'Interrogation of group 11', + 0x20: 'Interrogation of group 12', + 0x21: 'Interrogation of group 13', + 0x22: 'Interrogation of group 14', + 0x23: 'Interrogation of group 15', + 0x24: 'Interrogation of group 16' +} + + +R_ENUM = { + 0x00: 'Local power switch on', + 0x01: 'Local manual reset', + 0x02: 'Remote reset', +} +for i in range(0x03, 0x7f): + R_ENUM[i] = 'Undefined' + +I_ENUM = { + 0x00: 'Initialization with unchanged local parameters', + 0x80: 'Initialization after change of local parameters' +} + +QU_ENUM = { + 0x00: 'no pulse defined', + 0x01: 'short pulse duration (circuit-breaker)', + 0x02: 'long pulse duration', + 0x03: 'persistent output', + 0x04: 'reserved', + 0x05: 'reserved', + 0x06: 'reserved', +} + +SCS_ENUM = { + 0x00: 'OFF', + 0x01: 'ON' +} From 823267254708d743bc72c47e123224aaf89c40f4 Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 15:23:00 +0100 Subject: [PATCH 09/21] Create dissector.py --- IEC104_Raw/dissector.py | 200 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 IEC104_Raw/dissector.py diff --git a/IEC104_Raw/dissector.py b/IEC104_Raw/dissector.py new file mode 100644 index 0000000..37cfdb9 --- /dev/null +++ b/IEC104_Raw/dissector.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 + +from struct import unpack, pack +from scapy.packet import Raw, bind_layers, Padding, Packet, conf +from scapy.layers.inet import TCP, Ether +from scapy.fields import XByteField, ByteField, ShortField, PacketListField, ByteEnumField, PacketField, ConditionalField +from .ioa import IOAS, IOALEN +from .const import TYPE_APCI, SQ, CAUSE_OF_TX, TYPEID_ASDU +from scapy.all import conf + +class ASDU(Packet): + + name = 'IEC 60870-5-104-ASDU' + fields_desc = [ + ByteField('TypeId',None), + ByteField('SQ',None), + ByteField('NumIx',0), + ByteEnumField('CauseTx',None, CAUSE_OF_TX), + ByteField('Negative',False), + ByteField('Test', None), + ByteField('OA',None), + ByteField('Addr',None), + PacketListField('IOA', None) + ] + + def do_dissect(self, s): + try: # TODO: [Luis] How to use Try & Exception + self.TypeId = s[0] & 0xff + except Exception: + if conf.debug_dissector: + raise NameError('HiThere') + self.TypeId = 'Error' + typeId = s[0] & 0xff + flags_SQ = s[1] & 0x80 + + self.SQ = flags_SQ + self.NumIx = s[1] & 0x7f + self.CauseTx = s[2] & 0x3F + self.Negative = s[2] & 0x40 + self.Test = s[2] & 0x80 + self.OA = s[3] + self.Addr = unpack('> 8) & 0xFF) + if self.IOA is not None: + for i in self.IOA: + s += i.do_build() + + return bytes(s) + + def __bytes__(self): + return bytes(self.build()) + +class APCI(Packet): + + name = 'IEC 60870-5-104-APCI' + + fields_desc = [ + XByteField('START', 0x68), + ByteField('ApduLen', 4), + ByteEnumField('Type', 0x00, TYPE_APCI), + ConditionalField(XByteField('UType', 0x01), lambda pkt: pkt.Type == 0x03), + ConditionalField(ShortField('Tx', 0x00), lambda pkt: pkt.Type == 0x00), + ConditionalField(ShortField('Rx', 0x00), lambda pkt: pkt.Type < 3), + ] + + def do_dissect(self, s): + self.START = s[0] + self.ApduLen = s[1] + self.Type = s[2] & 0x03 if bool(s[2] & 0x01) else 0x00 + if self.Type == 3: + self.UType = (s[2] & 0xfc) >> 2 + else: + if self.Type == 0: + self.Tx = (s[3] << 7) | (s[2] >> 1) + self.Rx = (s[5] << 7) | (s[4] >> 1) + return s[6:] + + def dissect(self, s): + s = self.pre_dissect(s) + s = self.do_dissect(s) + s = self.post_dissect(s) + payl, pad = self.extract_padding(s) + self.do_dissect_payload(payl) + if pad and conf.padding: + self.add_payload(Padding(pad)) + + def do_build(self): + s = list(range(6)) + s[0] = 0x68 + s[1] = self.ApduLen + if self.Type == 0x03: + s[2] = ((self.UType << 2) & 0xfc) | self.Type + s[3] = 0 + s[4] = 0 + s[5] = 0 + else: + if self.Type == 0x00: + s[2] = ((self.Tx << 1) & 0x00fe) | self.Type + s[3] = ((self.Tx << 1) & 0xff00) >> 8 + else: + s[2] = self.Type + s[3] = 0 + s[4] = (self.Rx << 1) & 0x00fe + s[5] = (self.Rx & 0xff00) >> 8 + return bytes(s) + + def extract_padding(self, s): + if self.Type == 0x00 and self.ApduLen > 4: + return s[:self.ApduLen - 4], s[self.ApduLen - 4:] + return None, s + + def do_dissect_payload(self, s): + if s is not None: + p = ASDU(s, _internal=1, _underlayer=self) + self.add_payload(p) + +class APDU(Packet): + name = 'APDU' + + def dissect(self, s): + s = self.pre_dissect(s) + s = self.do_dissect(s) + s = self.post_dissect(s) + payl, pad = self.extract_padding(s) + self.do_dissect_payload(payl) + if pad and conf.padding: + if pad[0] in [0x68]: #TODO: [Luis] "self.underlayer is not None" + self.add_payload(APDU(pad, _internal=1, _underlayer=self)) + else: + self.add_payload(Padding(pad)) + + def do_dissect(self, s): + apci = APCI(s, _internal=1, _underlayer=self) + self.add_payload(apci) + + def extract_padding(self, s): + return None, s + +bind_layers(TCP, APDU, sport=2404) +bind_layers(TCP, APDU, dport=2404) + +if __name__ == '__main__': + from binascii import hexlify, unhexlify + print('Dissecting "68040e001e00" ...\r\n') + data = unhexlify('68040e001e00') + data2 = unhexlify('00000c9ff00000090f09020708004500003a1dc540003f06337fc0a8fa03c0a86f25cdf40964d5df3c27dab0e477801801f5de5400000101080abca025b50574f04168040100c252') + APDU(data).show() + Ether(data2).show() + print('\r\nBuilding "68040e001e00"...\r\n') + pkt = APDU()/APCI(ApduLen=4, Type=0x00, Tx=7, Rx=15) + a = pkt.build() + pkt.show() + print('Result:', hexlify(a)) From b48c4d371e30bcc8925ecaebfe2d852384d2f35d Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 15:23:30 +0100 Subject: [PATCH 10/21] Create fields.py --- IEC104_Raw/fields.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 IEC104_Raw/fields.py diff --git a/IEC104_Raw/fields.py b/IEC104_Raw/fields.py new file mode 100644 index 0000000..3f2a90b --- /dev/null +++ b/IEC104_Raw/fields.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +import struct +from scapy.config import conf +from scapy.packet import Packet +from scapy.fields import Field, StrField, XByteField, ByteField, PacketField + +class LEFloatField(Field): + ''' + little-endian float + ''' + def __init__(self, name, default): + Field.__init__(self, name, default, ' Date: Tue, 16 Mar 2021 15:24:17 +0100 Subject: [PATCH 11/21] Create ioa.py --- IEC104_Raw/ioa.py | 400 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 400 insertions(+) create mode 100644 IEC104_Raw/ioa.py diff --git a/IEC104_Raw/ioa.py b/IEC104_Raw/ioa.py new file mode 100644 index 0000000..4a57b38 --- /dev/null +++ b/IEC104_Raw/ioa.py @@ -0,0 +1,400 @@ +#!/usr/bin/env python3 + +from struct import unpack +from scapy.fields import PacketField, ShortField, FlagsField, ByteEnumField, XIntField +from .fields import IOAID, LEFloatField, ByteField, SignedShortField +from .const import QDS_FLAGS, SU, DOW, SEL_EXEC, DPI_ENUM, DIQ_FLAGS, SIQ_FLAGS, TRANSIENT, QOI_ENUM, R_ENUM, I_ENUM, QU_ENUM, SEL_EXEC, SCS_ENUM +from scapy.packet import Packet + +# class BSI(Packet): +# name = 'BSI' +# fields_desc = [ +# ShortField('BSI',None), +# ] + +# def do_dissect(self, s): +# self.BSI = ''.join(format(bt, '08b') for bt in s[0:4]) +# return s[4:] + +# def do_build(self): +# s = list(range(4)) +# s[0] = self.BSI & 0xFF +# s[1] = self.BSI & 0xFF00 +# s[2] = self.BSI & 0xFF0000 +# s[3] = self.BSI & 0xFF000000 + +# return s + +# def __bytes__(self): +# return bytes(self.build()) + +# def extract_padding(self, s): +# return '', s + +class COI(Packet): + name = 'COI' + fields_desc = [ + ByteEnumField('R', None, R_ENUM), + ByteEnumField('I', None, I_ENUM), + ] + + def do_dissect(self, s): + self.R = s[0] & 0x7f + self.I = s[0] & 0x80 + return s[1:] + + + def do_build(self): + s = list(range(1)) + s[0] = self.I | self.R + return bytes(s) + + def extract_padding(self, s): + return '', s + +class VTI(Packet): + name = 'VTI' + fields_desc = [ + ByteField('Value',False), + ByteEnumField('Transient',None, TRANSIENT) + ] + + def do_dissect(self, s): + self.Value = s[0] & 0x7f + self.Transient = s[0] & 0x80 + + return s[1:] + + def do_build(self): + s = list(range(1)) + s[0] = self.Transient | self.Value + + return bytes(s) + + def extract_padding(self, s): + return '', s + +class DIQ(Packet): + name = 'QDS' + fields_desc = [ + ByteEnumField('DPI', 0x00, DPI_ENUM), + FlagsField('flags', 0x00, 8, DIQ_FLAGS), + # ByteField('BL',None), + # ByteField('SB',None), + # ByteField('NT',None), + # ByteField('IV',None) + ] + + def do_dissect(self, s): + self.DPI = s[0] & 0x03 + self.flags = s[0] & 0xf0 + # self.BL = BL[s[0] & 0x10] + # self.SB = SB[s[0] & 0x20] + # self.NT = NT[s[0] & 0x40] + # self.IV = IV[s[0] & 0x80] + + return s[1:] + + def do_build(self): + s = list(range(1)) + # s[0] = (self.DPI & 0x11) | (self.BL << 4 & 0x10) | (self.SB << 5 & 0x20) | (self.NT << 6 & 0x40) | (self.IV << 7 & 0x80) + s[0] = self.DPI | self.flags + + return bytes(s) + + def extract_padding(self, s): + return '', s + +class QOS(Packet): + name = 'QOS' + fields_desc = [ + ByteField('QL',False), + ByteEnumField('SE', 0x00, SEL_EXEC) + ] + + def do_dissect(self, s): + self.QL = s[0] & 0x7F + self.SE = s[0] & 0x10 + + return s[1:] + + def do_build(self): + s = list(range(1)) + s[0] = (self.SE << 7 & 0x80) | (self.QL & 0x7F) + + return s + + def __bytes__(self): + return bytes(self.build()) + + def extract_padding(self, s): + return '', s + +class CP56Time(Packet): + + name = 'CP56Time' + fields_desc = [ + ByteField('MS',None), + ByteField('Min',None), + ByteField('IV',None), + ByteField('Hour',None), + ByteField('SU',None), + ByteField('Day',None), + ByteField('DOW',None), + ByteField('Month',None), + ByteField('Year',None), + ] + + def do_dissect(self, s): + try: + self.MS = unpack('> 8) & 0xFF + s[2] = (self.IV & 0x80) | (self.Min & 0x3F) + s[3] = (self.SU & 0x80) | (self.Hour & 0x1F) + s[4] = (self.DOW & 0xE0) | (self.Day & 0x1F) + s[5] = self.Month & 0xF + s[6] = (self.Year & 0x7F) + + return bytes(s) + + def extract_padding(self, s): + return '', s + +class SCO(Packet): + name = 'SCO' + fields_desc = [ + ByteEnumField('SCS', 0x00, SCS_ENUM), + ByteEnumField('QU', None, QU_ENUM), + ByteEnumField('SE', None, SEL_EXEC) + ] + + def do_dissect(self, s): + self.SCS = s[0] & 0x01 + self.QU = s[0] & 0x7C + self.SE = s[0] & 0x80 + + return s[1:] + + def do_build(self): + s = list(range(1)) + s[0] = self.SE | self.SCS | self.QU + + return s + + def __bytes__(self): + return bytes(self.build()) + + def extract_padding(self, s): + return '', s + +class IOA36(Packet): + + name = 'IOA' + fields_desc = [ + IOAID('IOA', None), + LEFloatField('Value', None), + FlagsField('QDS', 0x00, 8, QDS_FLAGS), + PacketField('CP56Time', None, CP56Time), + ] + + def extract_padding(self, s): + return '', s + +class IOA13(Packet): + name = 'IOA' + fields_desc = [ + IOAID('IOA', None), + LEFloatField('Value', None), + FlagsField('QDS', 0x00, 8, QDS_FLAGS), + ] + + def extract_padding(self, s): + return '', s + +class IOA9(Packet): + name = 'IOA' + fields_desc = [ + IOAID('IOA', None), + SignedShortField('Value', None), + FlagsField('QDS', 0x00, 8, QDS_FLAGS), + ] + + def extract_padding(self, s): + return '', s + +class IOA50(Packet): + name = 'IOA' + fields_desc = [ + IOAID('IOA', None), + LEFloatField('Value', None), + PacketField('QOS', None, QOS) + ] + + def extract_padding(self, s): + return '', s + +class IOA3(Packet): + name = 'IOA' + fields_desc = [ + IOAID('IOA', None), + PacketField('DIQ', None, DIQ) + ] + + def extract_padding(self, s): + return '', s + +class IOA5(Packet): + name = 'IOA' + fields_desc = [ + IOAID('IOA', None), + PacketField('VTI', None, VTI), + FlagsField('QDS', 0x00, 8, QDS_FLAGS), + ] + + def extract_padding(self, s): + return '', s + +class IOA100(Packet): + name = 'IOA' + fields_desc = [ + IOAID('IOA', None), + ByteEnumField('QOI', None, QOI_ENUM), + ] + + def extract_padding(self, s): + return '', s + +class IOA103(Packet): + name = 'IOA' + fields_desc = [ + IOAID('IOA', None), + PacketField('CP56Time', None, CP56Time) + ] + + def extract_padding(self, s): + return '', s + +class IOA30(Packet): + name = 'IOA' + fields_desc = [ + IOAID('IOA', None), + FlagsField('SIQ', 0x00, 8, SIQ_FLAGS), + PacketField('CP56Time', None, CP56Time) + ] + + def extract_padding(self, s): + return '', s + +class IOA70(Packet): + name = 'IOA' + fields_desc = [ + IOAID('IOA', None), + PacketField('COI', None, COI), + ] + + def extract_padding(self, s): + return '', s + +class IOA31(Packet): + name = 'IOA' + fields_desc = [ + IOAID('IOA', None), + PacketField('DIQ', None, DIQ), + PacketField('CP56Time', None, CP56Time) + ] + + def extract_padding(self, s): + return '', s + +class IOA1(Packet): + name = 'IOA' + fields_desc = [ + IOAID('IOA', None), + FlagsField('SIQ', 0x00, 8, SIQ_FLAGS), + ] + + def extract_padding(self, s): + return '', s + +class IOA7(Packet): + name = 'IOA' + fields_desc = [ + IOAID('IOA', None), + # PacketField('BSI', None, BSI), + XIntField('BSI', 0x00000000), + # PacketField('QDS', None, QDS_FLAGS) + FlagsField('QDS', 0x00, 8, QDS_FLAGS), + ] + + def extract_padding(self, s): + return '', s + +class IOA45(Packet): + name = 'IOA' + fields_desc = [ + IOAID('IOA', None), + PacketField('SCO', None, SCO) + ] + + def extract_padding(self, s): + return '', s + +IOAS = { + 36: IOA36, + 13: IOA13, + 9: IOA9, + 50: IOA50, + 3: IOA3, + 5: IOA5, + 100: IOA100, + 103: IOA103, + 30: IOA30, + 70: IOA70, + 31: IOA31, + 1: IOA1, + 7: IOA7, + 45: IOA45, +} + +IOALEN = { + 36: 15, + #13: 7, # NOTE: For INFORMATION OBJECT ADDRESS of two octets + 13: 8, + 9: 6, + 50: 8, + 3: 4, + 5: 5, + 100: 4, + 103: 10, # NOTE: For INFORMATION OBJECT ADDRESS of two octets + # 30: 11, + 30: 10, + 70: 4, + 31: 11, + 1: 4, + 7: 8, + 45: 4, +} From 7b5c9d63ce7bc01db0f3d3b582e3fdabc6e4f84c Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 15:26:01 +0100 Subject: [PATCH 12/21] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 5e5bd79..695a9c5 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ # Network Emulation Framework for Industrial Control Systems (NEFICS) + +Bruh, I just copied the files from multiple projects that had dependencies and corrected some stuff that was wrong with the imports + +## source +https://github.com/Cyphysecurity/IEC104_Parser +https://github.com/Cyphysecurity/NEFICS From d1ecccb8288671273c7a1a0e660fa5a844ed0ab9 Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 15:26:22 +0100 Subject: [PATCH 13/21] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 695a9c5..e3e8532 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,5 @@ Bruh, I just copied the files from multiple projects that had dependencies and c ## source https://github.com/Cyphysecurity/IEC104_Parser + https://github.com/Cyphysecurity/NEFICS From 36458b4396127f5c2ea1f1cb0fa142aaaf72bd43 Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 15:59:25 +0100 Subject: [PATCH 14/21] Delete IEC104_Raw directory --- IEC104_Raw/const.py | 186 ------------------- IEC104_Raw/dissector.py | 200 -------------------- IEC104_Raw/fields.py | 37 ---- IEC104_Raw/ioa.py | 400 ---------------------------------------- 4 files changed, 823 deletions(-) delete mode 100644 IEC104_Raw/const.py delete mode 100644 IEC104_Raw/dissector.py delete mode 100644 IEC104_Raw/fields.py delete mode 100644 IEC104_Raw/ioa.py diff --git a/IEC104_Raw/const.py b/IEC104_Raw/const.py deleted file mode 100644 index cecfed3..0000000 --- a/IEC104_Raw/const.py +++ /dev/null @@ -1,186 +0,0 @@ -TYPEID_ASDU = { - 0x01: 'M_SP_NA_1 (1)', - 0x03: 'M_DP_NA_1 (3)', - 0x05: 'M_ST_NA_1 (5)', - 0x07: 'M_BO_NA_1 (7)', - 0x09: 'M_ME_NA_1 (9)', - 0x0D: 'M_ME_NC_1 (13)', - 0x1E: 'M_SP_TB_1 (30)', - 0x1F: 'M_DP_TB_1 (31)', - 0x24: 'M_ME_TF_1 (36)', - 0x2D: 'C_SC_NA_1 (45)', - 0x2E: 'C_DC_NA_1 (46)', - 0x32: 'C_SE_NC_1 (50)', - 0x46: 'M_EI_NA_1 (70)', - 0x64: 'C_IC_NA_1 (100)', - 0x67: 'C_CS_NA_1 (103)', -} - -TYPE_APCI = { - 0x00: 'I (0x00)', - 0x01: 'S (0x01)', - 0x03: 'U (0x03)' -} - -UNNUMBERED_CONTROL_FIELD = { - 0x80: 'TESTFR con', - 0x40: 'TESTFR act', - 0x20: 'STOPDT con', - 0x10: 'STOPDT act', - 0x08: 'STARTDT con', - 0x04: 'STARTDT act' -} - -CAUSE_OF_TX = { - 0: 'not used', - 1: 'per/cyc', - 2: 'back', - 3: 'spont', - 4: 'init', - 5: 'req', - 6: 'act', - 7: 'ActCon', - 8: 'deact', - 9: 'DeactCon', - 10: 'ActTerm', - 11: 'retrem', - 12: 'retloc', - 13: 'file', - 20: 'inrogen', - 21: 'inro1', - 22: 'inro2', - 23: 'inro3', - 24: 'inro4', - 25: 'inro5', - 26: 'inro6', - 27: 'inro7', - 28: 'inro8', - 29: 'inro9', - 30: 'inro10', - 31: 'inro11', - 32: 'inro12', - 33: 'inro13', - 34: 'inro14', - 35: 'inro15', - 36: 'inro16', - 37: 'reqcogen', - 38: 'reqco1', - 39: 'reqco2', - 40: 'reqco3', - 41: 'reqco4', - 44: 'unknown type identification', - 45: 'unknown cause of transmission', - 46: 'unknown common address of ASDU', - 47: 'unknown information object address' -} - -QDS_FLAGS = { - 1: 'Overflow', - 5: 'Blocked', - 6: 'Substituted', - 7: 'Not topical', - 8: 'Invalid' -} - -DIQ_FLAGS = { - 5: 'Blocked', - 6: 'Substituted', - 7: 'Not topical', - 8: 'Invalid' -} - -SIQ_FLAGS = { - 1: 'SPI', - 5: 'Blocked', - 6: 'Subsituted', - 7: 'Not topical', - 8: 'Invalid' -} - -SQ = { - 0X00: False, - 0x80: True -} - -SU = { - 0X80: 'summer time', - 0x00: 'normal time' -} - -#Day Of Week -DOW = { - 0x00: 'undefined', - 0x20: 'monday', - 0x40: 'tuesday', - 0x60: 'wednesday', - 0x80: 'thursday', - 0xA0: 'friday', - 0xC0: 'saturday', - 0xE0: 'sunday' -} - -SEL_EXEC = { - 0x00: 'Execute', - 0x80: 'Select' -} - -DPI_ENUM = { - 0x00: 'Indeterminate or Intermediate state', - 0x01: 'Determined state OFF', - 0x02: 'Determined state ON', - 0x03: 'Indeterminate state' -} - -TRANSIENT = { - 0x00: 'not in transient', - 0x80: 'in transient' -} - -QOI_ENUM = { - 0x14: 'Station interrogation (global)', - 0x15: 'Interrogation of group 1', - 0x16: 'Interrogation of group 2', - 0x17: 'Interrogation of group 3', - 0x18: 'Interrogation of group 4', - 0x19: 'Interrogation of group 5', - 0x1A: 'Interrogation of group 6', - 0x1B: 'Interrogation of group 7', - 0x1C: 'Interrogation of group 8', - 0x1D: 'Interrogation of group 9', - 0x1E: 'Interrogation of group 10', - 0x1F: 'Interrogation of group 11', - 0x20: 'Interrogation of group 12', - 0x21: 'Interrogation of group 13', - 0x22: 'Interrogation of group 14', - 0x23: 'Interrogation of group 15', - 0x24: 'Interrogation of group 16' -} - - -R_ENUM = { - 0x00: 'Local power switch on', - 0x01: 'Local manual reset', - 0x02: 'Remote reset', -} -for i in range(0x03, 0x7f): - R_ENUM[i] = 'Undefined' - -I_ENUM = { - 0x00: 'Initialization with unchanged local parameters', - 0x80: 'Initialization after change of local parameters' -} - -QU_ENUM = { - 0x00: 'no pulse defined', - 0x01: 'short pulse duration (circuit-breaker)', - 0x02: 'long pulse duration', - 0x03: 'persistent output', - 0x04: 'reserved', - 0x05: 'reserved', - 0x06: 'reserved', -} - -SCS_ENUM = { - 0x00: 'OFF', - 0x01: 'ON' -} diff --git a/IEC104_Raw/dissector.py b/IEC104_Raw/dissector.py deleted file mode 100644 index 37cfdb9..0000000 --- a/IEC104_Raw/dissector.py +++ /dev/null @@ -1,200 +0,0 @@ -#!/usr/bin/env python3 - -from struct import unpack, pack -from scapy.packet import Raw, bind_layers, Padding, Packet, conf -from scapy.layers.inet import TCP, Ether -from scapy.fields import XByteField, ByteField, ShortField, PacketListField, ByteEnumField, PacketField, ConditionalField -from .ioa import IOAS, IOALEN -from .const import TYPE_APCI, SQ, CAUSE_OF_TX, TYPEID_ASDU -from scapy.all import conf - -class ASDU(Packet): - - name = 'IEC 60870-5-104-ASDU' - fields_desc = [ - ByteField('TypeId',None), - ByteField('SQ',None), - ByteField('NumIx',0), - ByteEnumField('CauseTx',None, CAUSE_OF_TX), - ByteField('Negative',False), - ByteField('Test', None), - ByteField('OA',None), - ByteField('Addr',None), - PacketListField('IOA', None) - ] - - def do_dissect(self, s): - try: # TODO: [Luis] How to use Try & Exception - self.TypeId = s[0] & 0xff - except Exception: - if conf.debug_dissector: - raise NameError('HiThere') - self.TypeId = 'Error' - typeId = s[0] & 0xff - flags_SQ = s[1] & 0x80 - - self.SQ = flags_SQ - self.NumIx = s[1] & 0x7f - self.CauseTx = s[2] & 0x3F - self.Negative = s[2] & 0x40 - self.Test = s[2] & 0x80 - self.OA = s[3] - self.Addr = unpack('> 8) & 0xFF) - if self.IOA is not None: - for i in self.IOA: - s += i.do_build() - - return bytes(s) - - def __bytes__(self): - return bytes(self.build()) - -class APCI(Packet): - - name = 'IEC 60870-5-104-APCI' - - fields_desc = [ - XByteField('START', 0x68), - ByteField('ApduLen', 4), - ByteEnumField('Type', 0x00, TYPE_APCI), - ConditionalField(XByteField('UType', 0x01), lambda pkt: pkt.Type == 0x03), - ConditionalField(ShortField('Tx', 0x00), lambda pkt: pkt.Type == 0x00), - ConditionalField(ShortField('Rx', 0x00), lambda pkt: pkt.Type < 3), - ] - - def do_dissect(self, s): - self.START = s[0] - self.ApduLen = s[1] - self.Type = s[2] & 0x03 if bool(s[2] & 0x01) else 0x00 - if self.Type == 3: - self.UType = (s[2] & 0xfc) >> 2 - else: - if self.Type == 0: - self.Tx = (s[3] << 7) | (s[2] >> 1) - self.Rx = (s[5] << 7) | (s[4] >> 1) - return s[6:] - - def dissect(self, s): - s = self.pre_dissect(s) - s = self.do_dissect(s) - s = self.post_dissect(s) - payl, pad = self.extract_padding(s) - self.do_dissect_payload(payl) - if pad and conf.padding: - self.add_payload(Padding(pad)) - - def do_build(self): - s = list(range(6)) - s[0] = 0x68 - s[1] = self.ApduLen - if self.Type == 0x03: - s[2] = ((self.UType << 2) & 0xfc) | self.Type - s[3] = 0 - s[4] = 0 - s[5] = 0 - else: - if self.Type == 0x00: - s[2] = ((self.Tx << 1) & 0x00fe) | self.Type - s[3] = ((self.Tx << 1) & 0xff00) >> 8 - else: - s[2] = self.Type - s[3] = 0 - s[4] = (self.Rx << 1) & 0x00fe - s[5] = (self.Rx & 0xff00) >> 8 - return bytes(s) - - def extract_padding(self, s): - if self.Type == 0x00 and self.ApduLen > 4: - return s[:self.ApduLen - 4], s[self.ApduLen - 4:] - return None, s - - def do_dissect_payload(self, s): - if s is not None: - p = ASDU(s, _internal=1, _underlayer=self) - self.add_payload(p) - -class APDU(Packet): - name = 'APDU' - - def dissect(self, s): - s = self.pre_dissect(s) - s = self.do_dissect(s) - s = self.post_dissect(s) - payl, pad = self.extract_padding(s) - self.do_dissect_payload(payl) - if pad and conf.padding: - if pad[0] in [0x68]: #TODO: [Luis] "self.underlayer is not None" - self.add_payload(APDU(pad, _internal=1, _underlayer=self)) - else: - self.add_payload(Padding(pad)) - - def do_dissect(self, s): - apci = APCI(s, _internal=1, _underlayer=self) - self.add_payload(apci) - - def extract_padding(self, s): - return None, s - -bind_layers(TCP, APDU, sport=2404) -bind_layers(TCP, APDU, dport=2404) - -if __name__ == '__main__': - from binascii import hexlify, unhexlify - print('Dissecting "68040e001e00" ...\r\n') - data = unhexlify('68040e001e00') - data2 = unhexlify('00000c9ff00000090f09020708004500003a1dc540003f06337fc0a8fa03c0a86f25cdf40964d5df3c27dab0e477801801f5de5400000101080abca025b50574f04168040100c252') - APDU(data).show() - Ether(data2).show() - print('\r\nBuilding "68040e001e00"...\r\n') - pkt = APDU()/APCI(ApduLen=4, Type=0x00, Tx=7, Rx=15) - a = pkt.build() - pkt.show() - print('Result:', hexlify(a)) diff --git a/IEC104_Raw/fields.py b/IEC104_Raw/fields.py deleted file mode 100644 index 3f2a90b..0000000 --- a/IEC104_Raw/fields.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 - -import struct -from scapy.config import conf -from scapy.packet import Packet -from scapy.fields import Field, StrField, XByteField, ByteField, PacketField - -class LEFloatField(Field): - ''' - little-endian float - ''' - def __init__(self, name, default): - Field.__init__(self, name, default, '> 8) & 0xFF - s[2] = (self.IV & 0x80) | (self.Min & 0x3F) - s[3] = (self.SU & 0x80) | (self.Hour & 0x1F) - s[4] = (self.DOW & 0xE0) | (self.Day & 0x1F) - s[5] = self.Month & 0xF - s[6] = (self.Year & 0x7F) - - return bytes(s) - - def extract_padding(self, s): - return '', s - -class SCO(Packet): - name = 'SCO' - fields_desc = [ - ByteEnumField('SCS', 0x00, SCS_ENUM), - ByteEnumField('QU', None, QU_ENUM), - ByteEnumField('SE', None, SEL_EXEC) - ] - - def do_dissect(self, s): - self.SCS = s[0] & 0x01 - self.QU = s[0] & 0x7C - self.SE = s[0] & 0x80 - - return s[1:] - - def do_build(self): - s = list(range(1)) - s[0] = self.SE | self.SCS | self.QU - - return s - - def __bytes__(self): - return bytes(self.build()) - - def extract_padding(self, s): - return '', s - -class IOA36(Packet): - - name = 'IOA' - fields_desc = [ - IOAID('IOA', None), - LEFloatField('Value', None), - FlagsField('QDS', 0x00, 8, QDS_FLAGS), - PacketField('CP56Time', None, CP56Time), - ] - - def extract_padding(self, s): - return '', s - -class IOA13(Packet): - name = 'IOA' - fields_desc = [ - IOAID('IOA', None), - LEFloatField('Value', None), - FlagsField('QDS', 0x00, 8, QDS_FLAGS), - ] - - def extract_padding(self, s): - return '', s - -class IOA9(Packet): - name = 'IOA' - fields_desc = [ - IOAID('IOA', None), - SignedShortField('Value', None), - FlagsField('QDS', 0x00, 8, QDS_FLAGS), - ] - - def extract_padding(self, s): - return '', s - -class IOA50(Packet): - name = 'IOA' - fields_desc = [ - IOAID('IOA', None), - LEFloatField('Value', None), - PacketField('QOS', None, QOS) - ] - - def extract_padding(self, s): - return '', s - -class IOA3(Packet): - name = 'IOA' - fields_desc = [ - IOAID('IOA', None), - PacketField('DIQ', None, DIQ) - ] - - def extract_padding(self, s): - return '', s - -class IOA5(Packet): - name = 'IOA' - fields_desc = [ - IOAID('IOA', None), - PacketField('VTI', None, VTI), - FlagsField('QDS', 0x00, 8, QDS_FLAGS), - ] - - def extract_padding(self, s): - return '', s - -class IOA100(Packet): - name = 'IOA' - fields_desc = [ - IOAID('IOA', None), - ByteEnumField('QOI', None, QOI_ENUM), - ] - - def extract_padding(self, s): - return '', s - -class IOA103(Packet): - name = 'IOA' - fields_desc = [ - IOAID('IOA', None), - PacketField('CP56Time', None, CP56Time) - ] - - def extract_padding(self, s): - return '', s - -class IOA30(Packet): - name = 'IOA' - fields_desc = [ - IOAID('IOA', None), - FlagsField('SIQ', 0x00, 8, SIQ_FLAGS), - PacketField('CP56Time', None, CP56Time) - ] - - def extract_padding(self, s): - return '', s - -class IOA70(Packet): - name = 'IOA' - fields_desc = [ - IOAID('IOA', None), - PacketField('COI', None, COI), - ] - - def extract_padding(self, s): - return '', s - -class IOA31(Packet): - name = 'IOA' - fields_desc = [ - IOAID('IOA', None), - PacketField('DIQ', None, DIQ), - PacketField('CP56Time', None, CP56Time) - ] - - def extract_padding(self, s): - return '', s - -class IOA1(Packet): - name = 'IOA' - fields_desc = [ - IOAID('IOA', None), - FlagsField('SIQ', 0x00, 8, SIQ_FLAGS), - ] - - def extract_padding(self, s): - return '', s - -class IOA7(Packet): - name = 'IOA' - fields_desc = [ - IOAID('IOA', None), - # PacketField('BSI', None, BSI), - XIntField('BSI', 0x00000000), - # PacketField('QDS', None, QDS_FLAGS) - FlagsField('QDS', 0x00, 8, QDS_FLAGS), - ] - - def extract_padding(self, s): - return '', s - -class IOA45(Packet): - name = 'IOA' - fields_desc = [ - IOAID('IOA', None), - PacketField('SCO', None, SCO) - ] - - def extract_padding(self, s): - return '', s - -IOAS = { - 36: IOA36, - 13: IOA13, - 9: IOA9, - 50: IOA50, - 3: IOA3, - 5: IOA5, - 100: IOA100, - 103: IOA103, - 30: IOA30, - 70: IOA70, - 31: IOA31, - 1: IOA1, - 7: IOA7, - 45: IOA45, -} - -IOALEN = { - 36: 15, - #13: 7, # NOTE: For INFORMATION OBJECT ADDRESS of two octets - 13: 8, - 9: 6, - 50: 8, - 3: 4, - 5: 5, - 100: 4, - 103: 10, # NOTE: For INFORMATION OBJECT ADDRESS of two octets - # 30: 11, - 30: 10, - 70: 4, - 31: 11, - 1: 4, - 7: 8, - 45: 4, -} From 43c2f74bb10e3b363521e3cd34cbb5594ed428e5 Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 15:59:53 +0100 Subject: [PATCH 15/21] Update commander.py --- commander.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commander.py b/commander.py index 4a9829e..22915f9 100644 --- a/commander.py +++ b/commander.py @@ -7,7 +7,7 @@ import scapy.all as scapy import netifaces as nic import ipaddress -from IEC104_Raw.dissector import APDU +from nefics.IEC104.dissector import APDU from iec104 import IEC104, get_command IEC104_PORT = 2404 From a6ef5d6130f51e9f5177f8c079224038216f7e7a Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 16:00:58 +0100 Subject: [PATCH 16/21] Update rtu.py --- nefics/rtu.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nefics/rtu.py b/nefics/rtu.py index 5c83fdb..761122c 100644 --- a/nefics/rtu.py +++ b/nefics/rtu.py @@ -7,8 +7,8 @@ from datetime import datetime from time import sleep from binascii import hexlify -from IEC104.dissector import APDU -from IEC104.const import * +from nefics.IEC104.dissector import APDU +from nefics.IEC104.const import * from helper104 import * RTU_TYPES = [ # Supported RTU types From 9d5efe6198d0400b2ee3993926a16bc2b7f33274 Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 16:01:43 +0100 Subject: [PATCH 17/21] Update rtu.py --- nefics/rtu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nefics/rtu.py b/nefics/rtu.py index 761122c..ddb79b3 100644 --- a/nefics/rtu.py +++ b/nefics/rtu.py @@ -9,7 +9,7 @@ from binascii import hexlify from nefics.IEC104.dissector import APDU from nefics.IEC104.const import * -from helper104 import * +from nefics.helper104 import * RTU_TYPES = [ # Supported RTU types 'SOURCE', From a780db571534a571ee8492e10bad4287bde42cae Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 16:02:50 +0100 Subject: [PATCH 18/21] Update helper104.py --- nefics/helper104.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nefics/helper104.py b/nefics/helper104.py index 76bb5cf..90947ab 100644 --- a/nefics/helper104.py +++ b/nefics/helper104.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -from IEC104.dissector import ASDU, APCI, APDU -from IEC104.ioa import * +from nefics.IEC104.dissector import ASDU, APCI, APDU +from nefics.IEC104.ioa import * import time from datetime import datetime @@ -86,4 +86,4 @@ def testfr(actcon:bool=False) -> bytes: if __name__ == '__main__': print(build_104_asdu_packet(3, 1, 1003, 4, 3, 1, value=67)) print(build_104_asdu_packet(36, 2, 101, 4, 7, 1, value=54.3)) - print(build_104_asdu_packet(45, 3, 123, 2, 5, 1, SE=1, QU=1, SCS=0)) \ No newline at end of file + print(build_104_asdu_packet(45, 3, 123, 2, 5, 1, SE=1, QU=1, SCS=0)) From b5a45a0447030ae7327e9be0e550f80396a42821 Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 16:03:44 +0100 Subject: [PATCH 19/21] Update simcomm.py --- nefics/simcomm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nefics/simcomm.py b/nefics/simcomm.py index 6703d82..c442f5e 100644 --- a/nefics/simcomm.py +++ b/nefics/simcomm.py @@ -6,7 +6,7 @@ from time import sleep from struct import pack, unpack from threading import Thread -from rtu import RTU, RTU_SOURCE, RTU_TRANSMISSION, RTU_LOAD, RTU_TYPES +from nefics.rtu import RTU, RTU_SOURCE, RTU_TRANSMISSION, RTU_LOAD, RTU_TYPES if sys.platform[:3] == 'win': print('Intended to be executed in a mininet Linux environment.') From 9388702485f9fc5ffa40793484493fde2da467b4 Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 16:05:59 +0100 Subject: [PATCH 20/21] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e3e8532..aca52d2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Network Emulation Framework for Industrial Control Systems (NEFICS) -Bruh, I just copied the files from multiple projects that had dependencies and corrected some stuff that was wrong with the imports +Bruh, I just copied the files from multiple projects that had dependencies and corrected some stuff that was wrong with the imports. +Maybe I'm stoopid, but those imports looked like this script was written in a single evening. But I'm not a Python programmer, so I can't judge. Still, they were junk. ## source https://github.com/Cyphysecurity/IEC104_Parser From 9b3d587631eac8e88490b536194c679eb913c959 Mon Sep 17 00:00:00 2001 From: obeesechurger <61499620+obeesechurger@users.noreply.github.com> Date: Tue, 16 Mar 2021 16:15:27 +0100 Subject: [PATCH 21/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aca52d2..1cdd133 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Network Emulation Framework for Industrial Control Systems (NEFICS) Bruh, I just copied the files from multiple projects that had dependencies and corrected some stuff that was wrong with the imports. -Maybe I'm stoopid, but those imports looked like this script was written in a single evening. But I'm not a Python programmer, so I can't judge. Still, they were junk. +But maybe I'm just stoopid. ## source https://github.com/Cyphysecurity/IEC104_Parser