From eb9f2fcbf17c51f126dca3b820d6aa1d595d2158 Mon Sep 17 00:00:00 2001 From: FredericJamar Date: Mon, 28 Apr 2025 08:21:07 +0000 Subject: [PATCH] =?UTF-8?q?Chargement=20du=20script=20prennant=20en=20entr?= =?UTF-8?q?=C3=A9e=20un=20fichier=20K12=20plutot=20qu'un=20pcapng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout de la version prennant en entrée un fichier text K12 exporté depuis wireshark plutot qu'un pcapng --- decode_modbus_k12.json | 229 +++++++++++++++++++++++++++ decode_modbus_k12.py | 351 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 580 insertions(+) create mode 100644 decode_modbus_k12.json create mode 100644 decode_modbus_k12.py diff --git a/decode_modbus_k12.json b/decode_modbus_k12.json new file mode 100644 index 0000000..5bfde43 --- /dev/null +++ b/decode_modbus_k12.json @@ -0,0 +1,229 @@ +{ + "log_file": "..\\logs\\sys003.txt", + "ip_ats": "10.110.1.142", + "ip_srvr": "10.113.23.7", + "scada_ene": true, + "scada_eqpt": false, + "plc": false, + "eth_l":14, + "ip_l":20, + "tcp_l":20, + "registersNames": { + "1000": "TCD_900V_774 (Beekkant)", + "1001": "TCD_900V_29 (Etangs Noirs)", + "1002": "TCD_900V_28 (Cte de Flandre)", + "1003": "TCD_900V_27 (St Catherine)", + "1004": "TCD_900V_01 (De Brouckere L1)", + "1005": "TCD_900V_02 (Centrale)", + "1006": "TCD_900V_03 (Parc)", + "1007": "TCD_900V_04 (Arts-Loi L1)", + "1008": "TCD_900V_05 (Maelbeek)", + "1009": "TCD_900V_06A (Schuman A)", + "1010": "TCD_900V_07A (Merode A)", + "1011": "TCD_900V_08 (Montgomery L1)", + "1012": "TCD_900V_09 (Josephine Charlotte)", + "1013": "TCD_900V_10 (Gribaumont)", + "1014": "TCD_900V_11 (Tomberg)", + "1015": "TCD_900V_12 (Roodebeek)", + "1016": "TCD_900V_13 (Vandervelde)", + "1017": "TCD_900V_14 (Alma)", + "1018": "TCD_900V_15 (Kraainem)", + "1019": "TCD_900V_16 (Stockel)", + "1020": "TCD_900V_20 (Thieffry)", + "1021": "TCD_900V_21 (Petillon)", + "1022": "TCD_900V_22 (Hankar)", + "1023": "TCD_900V_23 (Delta)", + "1024": "TCD_900V_24 (Beaulieu)", + "1025": "TCD_900V_25 (Demey)", + "1026": "TCD_900V_26 (H.Debroux)", + "1027": "TCD_900V_773 (G. Ouest E.O.)", + "1028": "TCD_900V_772 (J. Brel)", + "1029": "TCD_900V_771 (Aumale)", + "1030": "TCD_900V_770 (Saint Guidon)", + "1031": "TCD_900V_769 (Veeweyde)", + "1032": "TCD_900V_768 (Bizet)", + "1033": "TCD_900V_767 (La Roue)", + "1034": "TCD_900V_766 (CERIA)", + "1035": "TCD_900V_765 (E.Merckx)", + "1036": "TCD_900V_764 (Erasme)", + "1037": "TCD_900V_775 (L2-6 - Osseghem)", + "1038": "TCD_900V_776 (L2-6 - Simonis EO)", + "1039": "TCD_900V_777 (L2-6 - Belgica)", + "1040": "TCD_900V_778 (L2-6 - Pannenhuis)", + "1041": "TCD_900V_779 (L2-6 - Bockstael)", + "1042": "TCD_900V_780 (L2-6 - Stuyvenberg)", + "1043": "TCD_900V_781 (L2-6 - H. Brugmann)", + "1044": "TCD_900V_782 (L2-6 - Heysel)", + "1045": "TCD_900V_783 (L2-6 - R. Baudouin)", + "2000": "SIMONET (A) ", + "2001": "SIMONET (B) ", + "2002": "VOGELZANG (A) ", + "2003": "VOGELZANG (B) ", + "2004": "EDELWEISS (A) ", + "2005": "EDELWEISS (B) ", + "2006": "GRYSON ", + "2007": "DEBUSSY (A) ", + "2008": "DEBUSSY (B) ", + "2009": "DEBUSSY (C) ", + "2010": "DELEERS (A) ", + "2011": "DELEERS (B) ", + "2012": "DOUVRES (A) ", + "2013": "DOUVRES (B) ", + "2014": "JANSON (A) ", + "2015": "JANSON (B) ", + "2016": "SCHEUT (A) ", + "2017": "SCHEUT (B) ", + "2018": "MALHERBE (A) ", + "2019": "MALHERBE (B) ", + "2020": "MALHERBE (C) ", + "2021": "PIERMEZ (A) ", + "2022": "PIERMEZ (B) ", + "2023": "MENIN (A) ", + "2024": "MENIN (B) ", + "2025": "MENIN (C) ", + "2026": "MOMAERTS ", + "2027": "TAZIEAUX ", + "2028": "EVEQUE ", + "2029": "Q. BRIQUES ", + "2030": "ECUYER ", + "2031": "ARENBERG ", + "2032": "DUCALE ", + "2033": "COLONIES (A) ", + "2034": "COLONIES (B) ", + "2035": "CHARLEMAGNE (A)", + "2036": "CHARLEMAGNE (B)", + "2037": "CHARLEMAGNE (C)", + "2038": "CHARLEMAGNE (D)", + "2039": "TREVES (A) ", + "2040": "TREVES (B) ", + "2041": "TREVES (C) ", + "2042": "P.TERVUERN (A) ", + "2043": "P.TERVUERN (B) ", + "2044": "50NAIR (A) ", + "2045": "50NAIR (B) ", + "2046": "MENAPIENS (A) ", + "2047": "MENAPIENS (B) ", + "2048": "MENAPIENS (C) ", + "2049": "BATAVES (A) ", + "2050": "BATAVES (B) ", + "2051": "BATAVES (C) ", + "2052": "BATAVES (D) ", + "2053": "ROI CHEVALIER ", + "2054": "DE BROQUEVILLE ", + "2055": "SOLLEVELD (A) ", + "2056": "SOLLEVELD (B) ", + "2057": "*Reserved* ", + "2058": "VERVLOESEM (A) ", + "2059": "VERVLOESEM (B) ", + "2060": "*Reserved* ", + "2061": "MOUNIER (A) ", + "2062": "MOUNIER (B) ", + "2063": "ASSOMPTION (A) ", + "2064": "ASSOMPTION (B) ", + "2065": "BORNIVAL (A) ", + "2066": "BORNIVAL (B) ", + "2067": "BORNIVAL (C) ", + "2068": "AOUT (A) ", + "2069": "AOUT (B) ", + "2070": "DE DEKEN (A) ", + "2071": "DE DEKEN (B) ", + "2072": "LEBON (A) ", + "2073": "LEBON (B) ", + "2074": "LEBON (C) ", + "2075": "LEBON (D) ", + "2076": "PAEPEDELLE ", + "2077": "BELLE-VUE ", + "2078": "MEUNIERS (A) ", + "2079": "MEUNIERS (B) ", + "2080": "POELS (A) ", + "2081": "POELS (B) ", + "2082": "WYBRAN ", + "2083": "BOLET ", + "2084": "REMISE 5 ", + "2085": "REMISE 6 ", + "2500": "REMISE 5 ", + "2501": "REMISE 5 TCV 11", + "2502": "REMISE 5 TCV 31", + "2503": "REMISE 5 TCV 32", + "2504": "REMISE 5 TCV 33", + "2505": "REMISE 6 ", + "2506": "REMISE 6 TCV 41", + "2507": "REMISE 6 TCV 42", + "2508": "REMISE 6 TCV43A", + "2509": "REMISE 6 TCV43B", + "2510": "40AINE ", + "2511": "40AINE TCV 51 ", + "2512": "40AINE TCV 52A ", + "2513": "40AINE TCV 52B ", + "2514": "40AINE TCV 54 ", + "2515": "40AINE TCV 55 ", + "2516": "SCHOLLE ", + "2517": "TEST TRACK ", + "2600": "REMISE 2 TCV 1 ", + "2601": "REMISE 2 TCV 2 ", + "2602": "REMISE 2 TCV 3 ", + "2603": "REMISE 2 TCV 4 ", + "2604": "REMISE 2 TCV 5 ", + "2605": "REMISE 2 TCV 6 ", + "2606": "VOIE D'ESSAIS ", + "2607": "REMISE 1 TCV 7 ", + "2608": "REMISE 1 TCV 8 ", + "2609": "REMISE 1 TCV 9 ", + "2610": "REMISE 1 TCV 10", + "2611": "REMISE 1 TCV 11", + "2612": "REMISE 1 TCV 12", + "2613": "PEIGNE A ", + "2614": "PEIGNE B ", + "2615": "PEIGNE C ", + "2616": "PEIGNE D ", + "2617": "PEIGNE E TCV 30", + "2618": "PEIGNE F ", + "2619": "PEIGNE G TCV 13", + "2620": "PEIGNE H ", + "2621": "PEIGNE I ", + "2622": "PEIGNE J ", + "2623": "PEIGNE K ", + "2624": "*Reserved* ", + "2625": "FOSSES TCV 16 ", + "2626": "FOSSES TCV 17 ", + "2627": "FOSSES TCV 18 ", + "2628": "FOSSES TCV 19 ", + "2629": "FOSSES TCV 20 ", + "2630": "FOSSES TCV 21 ", + "2631": "FOSSES TCV 22 ", + "2632": "FOSSES TCV 23 ", + "2633": "FOSSES TCV 24 ", + "2634": "FOSSES TCV 25 ", + "2635": "*Reserved* ", + "10004": "MERODE_TC", + "10005": "ALMA_TC", + "10006": "MONTGOMERY_TC", + "10007": "THIEFFRY_TC", + "10008": "PETILLON_TC", + "10009": "DELTA_TC", + "10010": "TOMBERG_TC", + "10011": "ST-CATHERINE_TC", + "10012": "NOT_USED", + "10013": "NOT_USED", + "10014": "COMTE_DE_FLANDRE_TC", + "10015": "AUMALE_TC", + "10016": "NOT_USED", + "10017": "NOT_USED", + "10018": "BREL_TC", + "11004": "MERODE_TS", + "11005": "ALMA_TS", + "11006": "MONTGOMERY_TS", + "11007": "THIEFFRY_TS", + "11008": "PETILLON_TS", + "11009": "DELTA_TS", + "11010": "TOMBERG_TS", + "11011": "ST-CATHERINE_TS", + "11012": "NOT_USED", + "11013": "NOT_USED", + "11014": "COMTE_DE_FLANDRE_TS", + "11015": "AUMALE_TS", + "11016": "NOT_USED", + "11017": "NOT_USED", + "11018": "BREL_TS" + } +} \ No newline at end of file diff --git a/decode_modbus_k12.py b/decode_modbus_k12.py new file mode 100644 index 0000000..7f1e2fe --- /dev/null +++ b/decode_modbus_k12.py @@ -0,0 +1,351 @@ +import re +import json + +class Settings: + def __init__(self, file): + self.file = file + with open(self.file) as f: + self.data = json.load(f) + self.log_file = self.data['log_file'] + self.output_file = self.data['log_file']+'.out' + self.ip_ats = self.data['ip_ats'] + self.ip_srvr = self.data['ip_srvr'] + self.scada_ene = self.data['scada_ene'] + self.scada_eqpt = self.data['scada_eqpt'] + self.plc = self.data['plc'] + self.eth_l = self.data["eth_l"] + self.ip_l = self.data["ip_l"] + self.tcp_l = self.data["tcp_l"] + self.registersNames = self.data['registerNames'] + +class Ip: + def __init__(self, array_bytes): + self.telegram=array_bytes + self.src='.'.join(str(int(i,16)) for i in array_bytes[12:16]) + self.dst='.'.join(str(int(i,16)) for i in array_bytes[16:]) + +class Frame: + def __init__(self, timestamp, array_bytes, ip_ats, ip_srvr, scada_ene, scada_eqpt, plc, eth_l, ip_l, tcp_l, registersNames): + #computing index from to for array in frame + self.eth_i = eth_l + self.ip_i = self.eth_i + ip_l + self.tcp_i = self.ip_i + tcp_l + self.timestamp = timestamp + self.ethernet=array_bytes[:self.eth_i] + self.ip=Ip(array_bytes[self.eth_i:self.ip_i]) + self.tcp=array_bytes[self.ip_i:self.tcp_i] + self.modbus='N/A' + if plc: + if len(array_bytes) >= self.tcp_i+12: + if ip_ats == self.ip.src and ip_srvr == self.ip.dst: + self.modbus=Modbus_request(array_bytes[self.tcp_i:], scada_ene, scada_eqpt, plc, registersNames) + elif ip_ats == self.ip.dst and ip_srvr == self.ip.src: + self.modbus=Modbus_response(array_bytes[self.tcp_i:], scada_ene, scada_eqpt, plc, registersNames) + # 66 = header ETH+IP+TCP + 8 min length of Modbus frame + else: + if len(array_bytes) >= self.tcp_i+12: + if ip_ats == self.ip.src and ip_srvr == self.ip.dst: + self.modbus=Modbus_request(array_bytes[self.tcp_i:], scada_ene, scada_eqpt, plc, registersNames) + elif ip_ats == self.ip.dst and ip_srvr == self.ip.src: + self.modbus=Modbus_response(array_bytes[self.tcp_i:], scada_ene, scada_eqpt, plc, registersNames) + def __str__(self): + s = 'Frame:{' + s += '\n Ethernet: '+''.join(self.ethernet) + s += '\n IP : '+''.join(self.ip.telegram) + s += '\n TCP : '+''.join(self.tcp) + if self.modbus == 'N/A': + s += '\n MODBUS : '+''.join(self.modbus) + elif isinstance(self.modbus, Modbus_msg): + s += '\n MODBUS : '+''.join(self.modbus.__str__()) + s += '\n}' + return s + +class Modbus_msg: + def __init__(self,array_bytes, registersNames): + self.msg = ''.join(array_bytes) + self.tr_id = int(self.msg[:4],16) + self.id = int(self.msg[4:8],16) + self.long_sup = int(self.msg[8:10],16) + self.long_inf_nb_octets = int(self.msg[10:12],16) + self.addr_slave = int(self.msg[12:14],16) + self.func_id = int(self.msg[14:16],16) + def _str_(self): + s = '\n transaction_id = '+str(self.tr_id) + s += '\n id = '+str(self.id) + s += '\n long_sup = '+str(self.long_sup) + s += '\n size (bytes) = '+str(self.long_inf_nb_octets) + s += '\n addr_slave = '+str(self.addr_slave) + if self.func_id == 1: + s += '\n function_id = '+str(self.func_id)+' -> Read Coil' + elif self.func_id == 2: + s += '\n function_id = '+str(self.func_id)+' -> Read Discrete Input' + elif self.func_id == 3: + s += '\n function_id = '+str(self.func_id)+' -> Read Holding Registers' + elif self.func_id == 4: + s += '\n function_id = '+str(self.func_id)+' -> Read Input Registers' + elif self.func_id == 5: + s += '\n function_id = '+str(self.func_id)+' -> Write Single Coil' + elif self.func_id == 6: + s += '\n function_id = '+str(self.func_id)+' -> Write Single Holding Register' + elif self.func_id == 15: + s += '\n function_id = '+str(self.func_id)+' -> Write Multiple Coils' + elif self.func_id == 16: + s += '\n function_id = '+str(self.func_id)+' -> Write Multiple Register' + elif self.func_id == 129: + s += '\n function_id = '+str(self.func_id)+' -> Exception' + elif self.func_id == 130 or self.func_id == 131 or self.func_id == 132 or self.func_id == 133 or self.func_id == 134 or self.func_id == 143 or self.func_id == 144: + s += '\n function_id = '+str(self.func_id)+' -> Functional error code in response' + else: + s += '\n function_id = '+str(self.func_id)+' -> ILLEGAL_FUNCTION | NON-MODBUS PACKET' + s += '\n' + return s + +class Modbus_request(Modbus_msg): + def __init__(self, array_bytes, scada_ene, scada_eqpt, plc, registersNames): + msg = 'MODBUS_REQUEST' + Modbus_msg.__init__(self, array_bytes, registersNames) + self.scada_ene = scada_ene + self.scada_eqpt = scada_eqpt + self.plc = plc + self.registersNames = registersNames + if self.msg[16:20] != '': + self.addr_fst_register = int(self.msg[16:20],16) + if self.func_id == 3: + self.nb_registers = int(self.msg[20:],16) + elif self.func_id == 16: + self.nb_registers = int(self.msg[20:24],16) + self.nb_octets = int(self.msg[24:26],16) + self.registers = self.msg[26:] + elif self.func_id == 15: + self.nb_registers = int(self.msg[20:24],16) + self.nb_octets = int(self.msg[24:26],16) + self.registers = self.msg[26:] + else: + msg += ' -> ILLEGAL PACKET' + self.addr_fst_register = -1 + self.nb_registers = -1 + self.nb_octets = -1 + self.registers = -1 + print(msg) + def __str__(self): + s='PACKET: '+self.msg+'\n' + if self.func_id == 3: + s += 'Modbus TX:{'+self._str_() + s += ' fst_r_register = '+str(self.addr_fst_register) + s += '\n nb_registers = '+str(self.nb_registers) + s += '\n}' + elif self.func_id == 16: + s += 'Modbus TX:{'+self._str_() + s += ' fst_w_register = '+str(self.addr_fst_register)+' - '+self.registersNames[str(self.addr_fst_register)] + if self.plc and (self.scada_ene or self.scada_eqpt): + s += ' -> '+str(self.registersNames[str(self.addr_fst_register)]) + s += '\n nb_registers = '+str(self.nb_registers) + s += '\n nb_octets = '+str(self.nb_octets) + s += '\n value(s) = 0x'+str(self.registers)+' ('+str(int(self.registers, 16))+')' + if self.plc and (self.scada_ene or self.scada_eqpt): + if self.nb_registers < 1: + s +=' -> ' + if '1' in self.registers: + if self.scada_ene and self.scada_eqpt: + s += "ENERGIZATION | BREL" + elif self.scada_ene and not self.scada_eqpt: + s += "ENERGIZATION" + else: + s += "BREL" + else: + if self.scada_ene and self.scada_eqpt: + s += "DE-ENERGIZATION | PARC" + elif self.scada_ene and not self.scada_eqpt: + s += "DE-ENERGIZATION" + else: + s += "PARC" + else: + s += "{" + dic = self.decodeValuesForRead() + for i in range(self.addr_fst_register, (self.nb_octets/2)+self.addr_fst_register): + if i%1==0: + s += '\n ' + s += ' '+str(i)+' ('+str(self.registersNames[str(i)])+') -> '+dic[i]+" ("+self.computeState(dic[i])+")" + s += "\n }" + s += '\n}' + else: + s += 'Modbus TX:{'+self._str_() + s += ' fst_w_register = '+str(self.addr_fst_register) + s += '\n nb_registers = '+str(self.nb_registers) if hasattr(self, 'nb_registers') else '\n nb_registers = None' + s += '\n nb_octets = '+str(self.nb_octets) if hasattr(self, 'nb_octets') else '\n nb_octets = None' + s += '\n value(s) = 0x'+str(self.registers)+' -> ' if hasattr(self, 'registers') else '\n value(s) = None' + s += '\n}' + return s + + def decodeValuesForRead(self): + dicRegisters = {} + j=0 + for i in range(self.addr_fst_register, (self.nb_octets/2)+self.addr_fst_register): + dicRegisters[i]=self.registers[j:j+4] + j+=4 + return dicRegisters + + def computeState(self, state): + s = "" + if state[:2] == "00": + s += "KNOWN-COMMANDABLE" + elif state[:2] == "01": + s += "UNKNOWN-COMMANDABLE" + elif state[:2] == "02": + s += "KNOWN-UNCOMMANDABLE" + elif state[:2] == "03": + s += "UNKNOWN-UNCOMMANDABLE" + s += "-" + if state[2:] == "00": + if self.scada_ene and self.scada_eqpt: + s += "0V | PARC" + elif self.scada_ene and not self.scada_eqpt: + s += "0V" + else: + s += "PARC" + elif state[2:] == "01": + if self.scada_ene and self.scada_eqpt: + s += "900V | BREL" + elif self.scada_ene and not self.scada_eqpt: + s += "900V" + else: + s += "BREL" + return s + +class Modbus_response(Modbus_msg): + def __init__(self, array_bytes, scada_ene, scada_eqpt, plc, registersNames): + print('MODBUS_RESPONSE') + Modbus_msg.__init__(self, array_bytes, registersNames) + self.request = None + self.scada_ene = scada_ene + self.scada_eqpt = scada_eqpt + self.plc = plc + self.registersNames = registersNames + self.exception='' + if self.func_id == 131: + self.exception= int(self.msg[16:],16) + else: + if self.func_id == 3: + self.nb_octets = int(self.msg[16:18],16) + self.registers = self.msg[18:] + elif self.func_id == 16: + self.addr_fst_register = int(self.msg[16:20],16) + self.nb_w_registers = int(self.msg[20:24],16) + elif self.func_id == 15: + self.addr_fst_register = int(self.msg[16:20],16) + self.nb_w_registers = int(self.msg[20:24],16) + def __str__(self): + s='PACKET: '+self.msg+'\n' + if self.func_id == 131 or self.func_id == 144: + s += 'Modbus RX:{'+self._str_() + s += ' Exception = '+str(self.exception) + if self.exception == 1: + s += ' -> ILLEGAL_FUNCTION' + elif self.exception == 2: + s += ' -> ILLEGAL_DATA_ADDRESS' + elif self.exception == 3: + s += ' -> ILLEGAL_DATA_VALUE' + elif self.exception == 4: + s += ' -> SLAVE_DEVICE_FAILURE' + elif self.exception == 6: + s += ' -> SLAVE_DEVICE_BUSY' + elif self.exception == 10: + s += ' -> GATEWAY_PATH_UNAVAILABLE' + elif self.exception == 11: + s += ' -> GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND' + s += '\n}' + elif self.func_id == 3: + s += 'Modbus RX:{'+self._str_() + s += ' nb_octets = '+str(self.nb_octets)+' -> '+str(int(self.nb_octets/2))+' registers' + # s += '\n value(s) = 0x'+str(self.registers) + s += '\n value(s) = {' + if self.request is not None: + if self.plc or (self.scada_ene or self.scada_eqpt): + dic = self.decodeValuesForRead() + for i in range(self.request.addr_fst_register, int(self.nb_octets/2)+self.request.addr_fst_register): + if i%1==0: + s += '\n ' + s += ' '+str(i)+' ('+str(self.registersNames[str(i)])+') -> '+dic[i]+" ("+self.computeState(dic[i])+")" + s += "\n }" + else: + s += 'LINKED REQUEST NOT FOUND}' + s += '\n}' + elif self.func_id == 16: + s += 'Modbus RX:{'+self._str_() + s += ' addr_fst_rgstr = '+str(self.addr_fst_register) + s += '\n nb_w_registers = '+str(self.nb_w_registers) + s += '\n}' + elif self.func_id == 15: + s += 'Modbus RX:{'+self._str_() + s += ' addr_fst_rgstr = '+str(self.addr_fst_register) + s += '\n nb_w_registers = '+str(self.nb_w_registers) + s += '\n}' + return s + + def decodeValuesForRead(self): + dicRegisters = {} + j=0 + for i in range(self.request.addr_fst_register, int(self.nb_octets/2)+self.request.addr_fst_register): + dicRegisters[i]=self.registers[j:j+4] + j+=4 + return dicRegisters + + def computeState(self, state): + s = "" + if state[:2] == "00": + s += "KNOWN-COMMANDABLE" + elif state[:2] == "01": + s += "UNKNOWN-COMMANDABLE" + elif state[:2] == "02": + s += "KNOWN-UNCOMMANDABLE" + elif state[:2] == "03": + s += "UNKNOWN-UNCOMMANDABLE" + s += "-" + if state[2:] == "00": + if self.scada_ene and self.scada_eqpt: + s += "0V | PARC" + elif self.scada_ene and not self.scada_eqpt: + s += "0V" + else: + s += "PARC" + elif state[2:] == "01": + if self.scada_ene and self.scada_eqpt: + s += "900V | BREL" + elif self.scada_ene and not self.scada_eqpt: + s += "900V" + else: + s += "BREL" + return s + +if __name__ == '__main__': + s = Settings('decode_scada_modbus.json') + f = open(s.log_file, 'r') + dModbus = [] + timestamp = None + frame = None + catch_next = False + for line in f.readlines(): + if not catch_next and re.match(r'[0-9]{2}:.*', line): + timestamp = re.match(r'([0-9]{2}:[0-9]{2}:[0-9]{2},[0-9]{3},[0-9]{3}).*', line).groups()[0] + catch_next = True + if catch_next and re.match(r'\|.*',line): + frame = Frame(timestamp, re.match(r'(\|.*)', line).groups()[0][6:-1].split('|'), s.ip_ats, s.ip_srvr, s.scada_ene, s.scada_eqpt, s.plc, s.eth_l, s.ip_l, s.tcp_l, s.registersNames) + # checking if frame is the response of previously request and adding this request to the response linked. + if isinstance(frame.modbus, Modbus_response) and len(dModbus) > 1: + for i in range(len(dModbus)): + if isinstance(dModbus[-i].modbus, Modbus_request): + if frame.modbus.tr_id == dModbus[-i].modbus.tr_id: + frame.modbus.request = dModbus[-i].modbus + break + dModbus.append(frame) + catch_next = False + o = open(s.output_file, 'w') + for frame in dModbus: + if frame.modbus != 'N/A': + o.write(frame.timestamp + ': ' + frame.ip.src + ' -> ' + frame.ip.dst+'\n') + o.write(frame.modbus.__str__()+'\n\n') + #o.write(frame.__str__()+'\n') + o.close() + f.close() + print('TERMINATED') + input() \ No newline at end of file