Chargement du script prennant en entrée un fichier K12 plutot qu'un pcapng
- Ajout de la version prennant en entrée un fichier text K12 exporté depuis wireshark plutot qu'un pcapng
This commit is contained in:
229
decode_modbus_k12.json
Normal file
229
decode_modbus_k12.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
351
decode_modbus_k12.py
Normal file
351
decode_modbus_k12.py
Normal file
@@ -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()
|
||||
Reference in New Issue
Block a user