def recv(self): """Receive a packet from the target host. If the talk mode in use is native and we've already set the route, the packet received is a raw packet. Otherwise, the packet received is a NI layer packet in the same way the :class:`SAPNIStreamSocket` works. """ # If we're working on native mode and the route was accepted, we don't # need the NI layer anymore. Just use the plain socket inside the # NIStreamSockets. if self.routed and self.talk_mode == 1: return StreamSocket.recv(self) # If the route was not accepted yet or we're working on non-native talk # mode, we need the NI layer. return SAPNIStreamSocket.recv(self)
class CIPClient(Base): def __init__(self, name, ip, port=44818, timeout=2): ''' :param name: Name of this targets :param ip: Target ip :param port: CIP port (default: 44818) :param timeout: timeout of socket (default: 2) ''' super(CIPClient, self).__init__(name=name) self._ip = ip self._port = port self._timeout = timeout self._connection = None self._target_info = {} self._session = 0x0 self.target_info = {} def connect(self): sock = socket.socket() sock.settimeout(self._timeout) sock.connect((self._ip, self._port)) self._connection = StreamSocket(sock, Raw) packet_1 = ENIPHeader(Command=0x65) / RegisterSession() rsp_1 = self.send_receive_cip_packet(packet_1) try: if rsp_1.haslayer(ENIPHeader): self._session = rsp_1.Session except Exception as err: self.logger.error(err) return def reconnect(self): self.connect() def _fix_session(self, packet): try: packet.Session = self._session return packet except Exception as err: self.logger.error(err) return packet def send_packet(self, packet): if self._connection: try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_packet(self, packet): if self._connection: try: rsp = self._connection.sr1(packet, timeout=self._timeout) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_packet(self): if self._connection: try: rsp = self._connection.recv() return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") def send_cip_packet(self, packet): if self._connection: packet = self._fix_session(packet) try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_cip_packet(self, packet): if self._connection: packet = self._fix_session(packet) # packet.show2() try: rsp = self._connection.sr1(packet, timeout=self._timeout) if rsp: rsp = ENIPHeader(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_cip_packet(self): if self._connection: try: rsp = self._connection.recv() if rsp: rsp = ENIPHeader(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") def get_target_info(self, port=0x01, port_segment=0x00): product_name = '' device_type = '' vendor = '' revision = '' serial_number = '' info_packet = ENIPHeader(Command=0x6f)/CIPCommandSpecificData()/\ CIPHeader(Type="Request", Service=0x52,)/\ CIPConnectionManager() info_packet[CIPCommandSpecificData].Items = [ NullAddressItem(), UnconnectedDataItem() ] info_packet[CIPHeader].RequestPath = [ CIPRequestPath(PathSegmentType=1, InstanceSegment=0x06), CIPRequestPath(PathSegmentType=1, LogicalSegmentType=0x01, InstanceSegment=0x01) ] info_packet[CIPConnectionManager].MessageRequest = CIPHeader( Type="Request", Service=0x01, RequestPath=[ CIPRequestPath(PathSegmentType=1, InstanceSegment=0x01), CIPRequestPath(PathSegmentType=1, LogicalSegmentType=0x01, InstanceSegment=0x01) ]) info_packet[CIPRoutePath].Port = port info_packet[CIPRoutePath].PortSegment = port_segment rsp = self.send_receive_cip_packet(info_packet) if rsp.haslayer(CIPHeader): if rsp[CIPHeader].GeneralStatus == 0x00: try: if rsp.haslayer(GetAttributesAll): product_name = rsp[GetAttributesAll].ProductName device_type = rsp[GetAttributesAll].DeviceType if device_type in DEVICE_TYPES.keys(): device_type = DEVICE_TYPES[device_type] else: device_type = "%s (%s)" % (product_name, hex(device_type)) vendor = rsp[GetAttributesAll].VendorID if vendor in VENDOR_IDS.keys(): vendor = VENDOR_IDS[vendor] else: vendor = "%s (%s)" % (product_name, hex(vendor)) revision = str(rsp[GetAttributesAll].MajorRevision) + '.'\ + str(rsp[GetAttributesAll].MinorRevision) serial_number = hex(rsp[GetAttributesAll].SerialNumber) except Exception as err: pass else: self.logger.warning( "Got Error Code:%s when get target info with port:%s and port_segment:%s" % (port, port_segment, rsp[CIPHeader].GeneralStatus)) return product_name, device_type, vendor, revision, serial_number
class S7PlusClient(Base): def __init__(self, name, ip, port=102, src_tsap='\x01\x00', timeout=2): ''' :param name: Name of this targets :param ip: S7 PLC ip :param port: S7 PLC port (default: 102) :param src_tsap: src_tsap :param rack: cpu rack (default: 0) :param slot: cpu slot (default: 2) :param timeout: timeout of socket (default: 2) ''' super(S7PlusClient, self).__init__(name=name) self._ip = ip self._port = port self._src_tsap = src_tsap self._dst_tsap = "SIMATIC-ROOT-ES" self._seq = 1 self.session = 0x0120 self._connection = None self._connected = False self._timeout = timeout self._pdu_length = 480 self._info = {} self._server_session_version_data = None def connect(self): sock = socket.socket() sock.settimeout(self._timeout) sock.connect((self._ip, self._port)) self._connection = StreamSocket(sock, Raw) packet1 = TPKT() / COTPCR() packet1.Parameters = [COTPOption() for i in range(3)] packet1.PDUType = "CR" packet1.Parameters[0].ParameterCode = "tpdu-size" packet1.Parameters[0].Parameter = "\x0a" packet1.Parameters[1].ParameterCode = "src-tsap" packet1.Parameters[2].ParameterCode = "dst-tsap" packet1.Parameters[1].Parameter = self._src_tsap packet1.Parameters[2].Parameter = self._dst_tsap self.send_receive_packet(packet1) packet2 = TPKT() / COTPDT(EOT=1) / S7PlusHeader( Data=S7PlusData(OPCode=0x31, Function=0x04ca)) packet2[S7PlusData].DataSet = S7PlusCrateObjectRequest( IDNumber=0x0000011d, DataType=0x04, DataValue=S7PlusUDIntValue(Value=0)) packet2[S7PlusData].DataSet.Elements = [ S7PlusObjectField(RelationID=0xd3, ClassID=0x821f) ] packet2[S7PlusData].DataSet.Elements[0].Elements = [ S7PlusAttributeField( IDNumber=0x00e9, DataType=0x15, DataValue=S7PlusWStringValue(Value=RandString(8))), S7PlusAttributeField( IDNumber=0x0121, DataType=0x15, DataValue=S7PlusWStringValue(Value=RandString(8))), S7PlusAttributeField(IDNumber=0x0128, DataType=0x15, DataValue=S7PlusWStringValue(Value="")), S7PlusAttributeField(IDNumber=0x0129, DataType=0x15, DataValue=S7PlusWStringValue(Value="")), S7PlusAttributeField( IDNumber=0x012a, DataType=0x15, DataValue=S7PlusWStringValue(Value=RandString(8))), S7PlusAttributeField(IDNumber=0x012b, DataType=0x04, DataValue=S7PlusUDIntValue(Value=0)), S7PlusAttributeField(IDNumber=0x012c, DataType=0x12, DataValue=S7PlusRIDValue(Value=RandInt())), S7PlusAttributeField(IDNumber=0x012d, DataType=0x15, DataValue=S7PlusWStringValue(Value="")), S7PlusSubObjectField( RelationID=0xd3, ClassID=0x817f, Elements=[ S7PlusAttributeField(IDNumber=0x00e9, DataType=0x15, DataValue=S7PlusWStringValue( Value="SubscriptionContainer")) ], ) ] rsp2 = self.send_receive_s7plus_packet(packet2) try: if rsp2.haslayer(S7PlusCrateObjectResponse): self.session = rsp2[S7PlusCrateObjectResponse].ObjectIDs[ 0].Value # Todo: remove this when find out how get these value from get_target_info for elment in rsp2[S7PlusCrateObjectResponse].Elements: if isinstance(elment, S7PlusObjectField): for sub_elment in elment.Elements: if isinstance(sub_elment, S7PlusAttributeField): if sub_elment.IDNumber == 0x0132: self._server_session_version_data = sub_elment for item in sub_elment.DataValue.Items: if item.IDNumber == 0x013f: data = item.DataValue.Value self._info['HW_Version'], self._info[ 'Order_Code'], self._info[ 'FW_Version'] = data.split( ';') except Exception as err: self.logger.error("Can't get order code and version from target") if self._server_session_version_data: packet3 = TPKT() / COTPDT(EOT=1) / S7PlusHeader( Data=S7PlusData(OPCode=0x31, Function=0x0542)) packet3[S7PlusData].DataSet = S7PlusSetMultiVariablesRequest( ObjectID=self.session, AddressList=S7PlusAddressListPacket( Elements=[S7PlusUDIntValue(Value=0x0132)]), ValueList=[ S7PlusItemValue( IDNumber=0x01, DataType=0x17, DataValue=self._server_session_version_data.DataValue), ], ObjectQualifier=S7PlusObjectQualifierPacket()) packet3[ S7PlusData].DataSet.ObjectQualifier.Items = OBJECT_QUALIFIER_ITEMS rsp3 = self.send_receive_s7plus_packet(packet3) def set_var(self, id_number, item_list): packet = TPKT() / COTPDT(EOT=1) / S7PlusHeader( Data=S7PlusData(OPCode=0x31, Function=0x04f2, Unknown1=0x34)) packet[S7PlusData].DataSet = S7PlusSetVariableRequest( ObjectID=id_number, ValueList=item_list) packet[ S7PlusData].DataSet.ObjectQualifier.Items = OBJECT_QUALIFIER_ITEMS packet.show2() self.send_s7plus_packet(packet) # rsp = self.send_receive_s7plus_packet(packet) def get_var_sub_streamed(self, id_number, data_type_flags, data_type, data_value): packet = TPKT() / COTPDT(EOT=1) / S7PlusHeader( Data=S7PlusData(OPCode=0x31, Function=0x0586)) packet[S7PlusData].DataSet = S7PlusGetVarSubStreamedRequest( IDNumber=id_number, DATATypeFlags=data_type_flags, DataType=data_type, DataValue=data_value, ObjectQualifier=S7PlusObjectQualifierPacket()) packet[ S7PlusData].DataSet.ObjectQualifier.Items = OBJECT_QUALIFIER_ITEMS rsp = self.send_receive_s7plus_packet(packet) try: if rsp.haslayer(S7PlusGetVarSubStreamedResponse): return rsp[S7PlusGetVarSubStreamedResponse].DataValue except Exception as err: self.logger.error("Response is not correct format") return None def get_target_info(self): request_items = S7PlusUDIntValueArray(UDIntItems=S7PlusUDIntValue( Value=0xea9)) data = self.get_var_sub_streamed(0x31, 0x02, 0x04, request_items) try: info_data = data[0].Value self._info['Serial_Number'] = info_data.split(' ')[3] except Exception as err: self._info['Serial_Number'] = '' self.logger.error("Can't get serial numbertarget") return self._info['Order_Code'], self._info[ 'Serial_Number'], self._info['HW_Version'], self._info[ 'FW_Version'] def delete_object(self, object_id): packet = TPKT() / COTPDT(EOT=1) / S7PlusHeader( Data=S7PlusData(OPCode=0x31, Function=0x04d4)) packet[S7PlusData].DataSet = S7PlusDeleteObjectRequest( IDNumber=object_id, ObjectQualifier=S7PlusObjectQualifierPacket()) packet[ S7PlusData].DataSet.ObjectQualifier.Items = OBJECT_QUALIFIER_ITEMS # packet.show2() self.send_s7plus_packet(packet) # rsp = self.send_receive_s7plus_packet(packet) def _fix_session(self, packet): if self._seq > 65535: self._seq = 1 try: if packet.haslayer(S7PlusData): if packet[S7PlusData].OPCode == 0x31: packet[S7PlusData].Seq = self._seq packet[S7PlusData].Session = self.session self._seq += 1 return packet except Exception as err: self.logger.error(err) return packet def send_packet(self, packet): if self._connection: try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_packet(self, packet): if self._connection: try: rsp = self._connection.sr1(packet, timeout=self._timeout) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_packet(self): if self._connection: try: rsp = self._connection.recv() return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") def send_s7plus_packet(self, packet): if self._connection: try: packet = self._fix_session(packet) self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_s7plus_packet(self, packet): if self._connection: try: packet = self._fix_session(packet) rsp = self._connection.sr1(packet, timeout=self._timeout) if rsp: rsp = TPKT(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_s7plus_packet(self): if self._connection: try: rsp = self._connection.recv() if rsp: rsp = TPKT(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!")
class S7Client(Base): def __init__(self, name, ip, port=102, src_tsap='\x01\x00', rack=0, slot=2, timeout=2): ''' :param name: Name of this targets :param ip: S7 PLC ip :param port: S7 PLC port (default: 102) :param src_tsap: src_tsap :param rack: cpu rack (default: 0) :param slot: cpu slot (default: 2) :param timeout: timeout of socket (default: 2) ''' super(S7Client, self).__init__(name=name) self._ip = ip self._port = port self._slot = slot self._src_tsap = src_tsap self._dst_tsap = '\x01' + struct.pack('B', rack * 0x20 + slot) self._pdur = 1 self.protect_level = None self._connection = None self._connected = False self._timeout = timeout self._pdu_length = 480 self.readable = False self.writeable = False self.authorized = False self._password = None self._mmc_password = None self.is_running = False def connect(self): sock = socket.socket() sock.settimeout(self._timeout) sock.connect((self._ip, self._port)) self._connection = StreamSocket(sock, Raw) packet1 = TPKT() / COTPCR() packet1.Parameters = [COTPOption() for i in range(3)] packet1.PDUType = "CR" packet1.Parameters[0].ParameterCode = "tpdu-size" packet1.Parameters[0].Parameter = "\x0a" packet1.Parameters[1].ParameterCode = "src-tsap" packet1.Parameters[2].ParameterCode = "dst-tsap" packet1.Parameters[1].Parameter = self._src_tsap packet1.Parameters[2].Parameter = self._dst_tsap self.send_receive_packet(packet1) packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7SetConParameter()) rsp2 = self.send_receive_s7_packet(packet2) if rsp2: self._connected = True # Todo: Need get pdu length from rsp2 def _get_cpu_protect_level(self): packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7ReadSZLParameterReq(), Data=S7ReadSZLDataReq(SZLId=0x0232, SZLIndex=0x0004)) rsp = self.send_receive_s7_packet(packet1) self.protect_level = int(str(rsp)[48].encode('hex')) self.logger.info("CPU protect level is %s" % self.protect_level) def get_target_info(self): order_code = '' version = '' module_type_name = '' as_name = '' module_name = '' serial_number = '' packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7ReadSZLParameterReq(), Data=S7ReadSZLDataReq(SZLId=0x0011, SZLIndex=0x0000)) rsp1 = self.send_receive_s7_packet(packet1) try: order_code_data = rsp1[ S7ReadSZLDataTreeRsp].Data[:rsp1[S7ReadSZLDataRsp].SZLLength] order_code = order_code_data[2:-7] version_data = rsp1[S7ReadSZLDataTreeRsp].Data[-3:] version = 'V {:x}.{:x}.{:x}'.format( int(version_data[0].encode('hex'), 16), int(version_data[1].encode('hex'), 16), int(version_data[2].encode('hex'), 16), ) except Exception as err: self.logger.error("Can't get order code and version from target") return order_code, version, module_type_name, as_name, module_name, serial_number packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7ReadSZLParameterReq(), Data=S7ReadSZLDataReq(SZLId=0x001c, SZLIndex=0x0000)) rsp2 = self.send_receive_s7_packet(packet2) try: module_name_data = rsp2[S7ReadSZLDataTreeRsp].Data[ rsp2[S7ReadSZLDataRsp].SZLLength + 2:rsp2[S7ReadSZLDataRsp].SZLLength * 2] module_name = str( module_name_data[:module_name_data.index('\x00')]) self.logger.debug("module_name:%s " % module_name) as_name_data = rsp2[S7ReadSZLDataTreeRsp].Data[ 2:rsp2[S7ReadSZLDataRsp].SZLLength] as_name = str(as_name_data[:as_name_data.index('\x00')]) self.logger.debug("as_name:%s " % as_name) serial_number_data = rsp2[S7ReadSZLDataTreeRsp].Data[ rsp2[S7ReadSZLDataRsp].SZLLength * 4 + 2:rsp2[S7ReadSZLDataRsp].SZLLength * 5] serial_number = str( serial_number_data[:serial_number_data.index('\x00')]) self.logger.debug("serial_number:%s " % serial_number) module_type_name_data = rsp2[S7ReadSZLDataTreeRsp].Data[ rsp2[S7ReadSZLDataRsp].SZLLength * 5 + 2:rsp2[S7ReadSZLDataRsp].SZLLength * 6] module_type_name = str( module_type_name_data[:module_type_name_data.index('\x00')]) self.logger.debug("module_type_name:%s " % module_type_name) except Exception as err: self.logger.error("Can't get module info from target") return order_code, version, module_type_name, as_name, module_name, serial_number return order_code, version, module_type_name, as_name, module_name, serial_number def check_privilege(self): self._get_cpu_protect_level() if self.protect_level == 1: self.logger.info("You have full privilege with this targets") self.readable = True self.writeable = True if self.protect_level == 2: if self.authorized is True: self.logger.info("You have full privilege with this targets") self.readable = True self.writeable = True else: self.logger.info( "You only have read privilege with this targets") self.readable = True self.writeable = False if self.protect_level == 3: if self.authorized is True: self.logger.info("You have full privilege with this targets") self.readable = True self.writeable = True else: self.logger.info("You can't read or write with this targets") self.readable = False self.writeable = False def auth(self, password): """ :param password: Paintext PLC password. """ self.logger.info("Start authenticate with password %s" % password) password_hash = self._hash_password(password) packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7PasswordParameterReq(), Data=S7PasswordDataReq(Data=password_hash)) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.haslayer(S7PasswordParameterRsp): if rsp1[S7PasswordParameterRsp].ErrorCode == 0: self.authorized = True self.logger.info("Authentication succeed") else: if self.authorized is True: self.logger.info("Already authorized") else: error_code = rsp1[S7PasswordParameterRsp].ErrorCode if error_code in S7_ERROR_CLASS.keys(): self.logger.error("Got error code: %s" % S7_ERROR_CLASS[error_code]) else: self.logger.error("Get error code: %s" % hex(error_code)) self.logger.error("Authentication failure") self.check_privilege() else: self.logger.info( "Receive unknown format packet, authentication failure") def clean_session(self): self.logger.info("Start clean the session") packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7CleanSessionParameterReq(), Data=S7CleanSessionDataReq()) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.haslayer(S7CleanSessionParameterRsp): if rsp1[S7CleanSessionParameterRsp].ErrorCode == 0: self.authorized = False self.logger.info("session cleaned") else: error_code = rsp1[S7CleanSessionParameterRsp].ErrorCode if error_code in S7_ERROR_CLASS.keys(): self.logger.error("Got error code: %s" % S7_ERROR_CLASS[error_code]) else: self.logger.error("Get error code: %s" % hex(error_code)) else: self.logger.info( "Receive unknown format packet, authentication failure") def _hash_password(self, password): password_hash_new = '' if len(password) < 1 or len(password) > 8: self.logger.error("Password length must between 1 to 8") return None else: password += '20'.decode('hex') * (8 - len(password)) for i in range(8): if i < 2: temp_data = ord(password[i]) temp_data ^= 0x55 password_hash_new += str(chr(temp_data)) else: temp_data1 = ord(password[i]) temp_data2 = ord(password_hash_new[i - 2]) temp_data1 = temp_data1 ^ 0x55 ^ temp_data2 password_hash_new += str(chr(temp_data1)) return password_hash_new def _fix_pdur(self, payload): if self._pdur > 65535: self._pdur = 1 try: payload.PDUR = self._pdur self._pdur += 1 return payload except Exception as err: self.logger.error(err) return payload def send_packet(self, packet): if self._connection: try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_packet(self, packet): if self._connection: try: rsp = self._connection.sr1(packet, timeout=self._timeout) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_packet(self): if self._connection: try: rsp = self._connection.recv() return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") def send_s7_packet(self, packet): if self._connection: packet = self._fix_pdur(packet) try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_s7_packet(self, packet): if self._connection: packet = self._fix_pdur(packet) try: rsp = self._connection.sr1(packet, timeout=self._timeout) if rsp: rsp = TPKT(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_s7_packet(self): if self._connection: try: rsp = self._connection.recv() if rsp: rsp = TPKT(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") def upload_block_from_target(self, block_type, block_num, dist='A'): """ :param block_type: "08": 'OB', "09": 'CMOD', "0A": 'DB', "0B": 'SDB', "0C": 'FC', "0D": 'SFC', "0E": 'FB', "0F": 'SFB' :param block_num: Block number. :param dist: 'A': "Active embedded module", 'B': "Active as well as passive module", 'P': "Passive (copied, but not chained) module" :return: Block Data """ if self.readable is False: self.logger.info("Didn't have read privilege on targets") return None block_data = '' if block_type in S7_BLOCK_TYPE_IN_FILE_NAME.keys(): file_block_type = block_type else: for key, name in S7_BLOCK_TYPE_IN_FILE_NAME.iteritems(): if name == block_type: file_block_type = key break else: self.logger.error( "block_type: %s is incorrect please check again" % block_type) return if type(block_num) != int: self.logger.error("block_num must be int format.") return file_block_num = "{0:05d}".format(block_num) file_name = '_' + file_block_type + file_block_num + dist self.logger.info("Start upload %s%s from target" % (block_type, block_num)) packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7RequestUploadBlockParameterReq(Filename=file_name)) rsp1 = self.send_receive_s7_packet(packet1) # Todo: Might got some error if rsp1.ErrorClass != 0x0: self.logger.error("Can't upload %s%s from target" % (block_type, block_num)) self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return None packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7UploadBlockParameterReq()) packet2[S7UploadBlockParameterReq].UploadId = rsp1[ S7RequestUploadBlockParameterRsp].UploadId while True: rsp2 = self.send_receive_s7_packet(packet2) if rsp2.ErrorClass != 0x0: self.logger.error("Can't upload %s%s from targets" % (block_type, block_num)) self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return None self.logger.debug("rsp2: %s" % str(rsp2).encode('hex')) block_data += rsp2.Data.Data if rsp2.Parameters.FunctionStatus != 1: break packet3 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7UploadBlockEndParameterReq()) self.send_receive_s7_packet(packet3) self.logger.info("Upload %s%s from target succeed" % (block_type, block_num)) return block_data def get_info_from_block(self, block_data): """ :param block_data: Block data. :return: mem_length, mc7_length, block_type, block_num """ try: mem_length = struct.unpack('!i', block_data[8:12])[0] mc7_length = struct.unpack('!h', block_data[34:36])[0] block_type = S7_BLOCK_TYPE_IN_BLOCK[ord(block_data[5])] block_num = struct.unpack('!h', block_data[6:8])[0] return mem_length, mc7_length, block_type, block_num except Exception as err: self.logger.error(err) return None def download_block_to_target(self, block_data, dist='P', transfer_size=462, stop_target=False): """ Download block to target and active block. :param block_data: Block data to download. :param dist: 'A': "Active embedded module", 'B': "Active as well as passive module", 'P': "Passive (copied, but not chained) module". :param transfer_size: Transfer size for each packet. :param stop_target: Stop target PLC before download block, True or False. """ if self.writeable is False: self.logger.info("Didn't have write privilege on targets") return None mem_length, mc7_length, block_type, block_num = self.get_info_from_block( block_data) self.logger.info("Start download %s%s to targets" % (block_type, block_num)) file_block_type = None for key, name in S7_BLOCK_TYPE_IN_FILE_NAME.iteritems(): if name == block_type: file_block_type = key break if not file_block_type: self.logger.error( "block_type: %s is incorrect please check again" % block_type) return file_block_num = "{0:05d}".format(block_num) file_name = '_' + file_block_type + file_block_num + dist load_memory_length = '0' * (6 - len(str(mem_length))) + str(mem_length) mc7_length = '0' * (6 - len(str(mc7_length))) + str(mc7_length) packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7RequestDownloadParameterReq( Filename=file_name, LoadMemLength=load_memory_length, MC7Length=mc7_length)) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.ErrorClass != 0x0: self.logger.error("Can't Download %s%s to targets" % (block_type, block_num)) self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return None if len(rsp1) > 20: download_req = TPKT(rsp1.load) else: download_req = self.receive_s7_packet() # Get pdur from download_req self._pdur = download_req.PDUR # DownloadBlock for i in range(0, len(block_data), transfer_size): if i + transfer_size <= len(block_data): packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadParameterRsp(FunctionStatus=1), Data=S7DownloadDataRsp(Data=block_data[i:i + transfer_size])) rsp2 = self.send_receive_s7_packet(packet2) self._pdur = rsp2.PDUR else: packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadParameterRsp(FunctionStatus=0), Data=S7DownloadDataRsp(Data=block_data[i:i + transfer_size])) self.send_s7_packet(packet2) # DownloadBlockEnd download_end_req = self.receive_s7_packet() self._pdur = download_end_req.PDUR packet3 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadEndParameterRsp()) self.send_s7_packet(packet3) # Insert block self.logger.debug("File_name:%s" % ('\x00' + file_name[1:])) packet4 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7PIServiceParameterReq( ParameterBlock=S7PIServiceParameterBlock( FileNames=['\x00' + file_name[1:]]), PI="_INSE")) # Todo: Might have a better way to do this # packet4[S7PIServiceParameterReq].ParameterBlock = S7PIServiceParameterBlock(FileNames=[file_name[1:]]) rsp4 = self.send_receive_s7_packet(packet4) if rsp4.ErrorClass != 0x0: self.logger.error("Can't insert %s%s to targets" % (block_type, block_num)) self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return None self.logger.info("Download %s%s to target succeed" % (block_type, block_num)) def download_block_to_target_only(self, block_data, dist='P', transfer_size=462, stop_target=False): """ Download block to target only (didn't active block). :param block_data: Block data to download. :param dist: 'A': "Active embedded module", 'B': "Active as well as passive module", 'P': "Passive (copied, but not chained) module". :param transfer_size: Transfer size for each packet. :param stop_target: Stop target PLC before download block, True or False. """ if self.writeable is False: self.logger.info("Didn't have write privilege on targets") return None mem_length, mc7_length, block_type, block_num = self.get_info_from_block( block_data) self.logger.info("Start download %s%s to targets" % (block_type, block_num)) file_block_type = None for key, name in S7_BLOCK_TYPE_IN_FILE_NAME.iteritems(): if name == block_type: file_block_type = key break if not file_block_type: self.logger.error( "block_type: %s is incorrect please check again" % block_type) return file_block_num = "{0:05d}".format(block_num) file_name = '_' + file_block_type + file_block_num + dist load_memory_length = '0' * (6 - len(str(mem_length))) + str(mem_length) mc7_length = '0' * (6 - len(str(mc7_length))) + str(mc7_length) packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7RequestDownloadParameterReq( Filename=file_name, LoadMemLength=load_memory_length, MC7Length=mc7_length)) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.ErrorClass != 0x0: self.logger.error("Can't Download %s%s to targets" % (block_type, block_num)) self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return None if len(rsp1) > 20: download_req = TPKT(rsp1.load) else: download_req = self.receive_s7_packet() # Get pdur from download_req self._pdur = download_req.PDUR # DownloadBlock for i in range(0, len(block_data), transfer_size): if i + transfer_size <= len(block_data): packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadParameterRsp(FunctionStatus=1), Data=S7DownloadDataRsp(Data=block_data[i:i + transfer_size])) rsp2 = self.send_receive_s7_packet(packet2) self._pdur = rsp2.PDUR else: packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadParameterRsp(FunctionStatus=0), Data=S7DownloadDataRsp(Data=block_data[i:i + transfer_size])) self.send_s7_packet(packet2) # DownloadBlockEnd download_end_req = self.receive_s7_packet() self._pdur = download_end_req.PDUR packet3 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadEndParameterRsp()) self.send_s7_packet(packet3) self.logger.info("Download %s%s to target succeed" % (block_type, block_num)) def get_target_status(self): packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7ReadSZLParameterReq(), Data=S7ReadSZLDataReq(SZLId=0x0424, SZLIndex=0x0000)) rsp = self.send_receive_s7_packet(packet1) status = str(rsp)[44] if status == '\x08': self.logger.info("Target is in run mode") self.is_running = True elif status == '\x04': self.logger.info("Target is in stop mode") self.is_running = False else: self.logger.info("Target is in unknown mode") self.is_running = False def stop_target(self): self.get_target_status() if not self.is_running: self.logger.info("Target is already stop") return self.logger.info("Trying to stop targets") packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7StopCpuParameterReq()) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.ErrorClass != 0x0: self.logger.error("Can't Stop Target") self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return time.sleep(2) # wait targets to stop self.get_target_status() def start_target(self, cold=False): ''' Start target PLC :param cold: Doing cold restart, True or False. ''' self.get_target_status() if self.is_running: self.logger.info("Target is already running") return self.logger.info("Trying to start targets") if cold: packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7PIServiceParameterReq( ParameterBlock=S7PIServiceParameterStringBlock())) else: packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7PIServiceParameterReq()) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.ErrorClass != 0x0: self.logger.error("Can't Start Target") self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return time.sleep(2) # wait targets to start self.get_target_status() @staticmethod def get_transport_size_from_data_type(data_type): for key, name in S7_TRANSPORT_SIZE_IN_PARM_ITEMS.iteritems(): if isinstance(data_type, str): if name.startswith(data_type.upper()): return key elif isinstance(data_type, int): return data_type return None def get_item_pram_from_item(self, item): block_num = '' area_type = '' address = '' transport_size = '' try: for key in VAR_NAME_TYPES: if isinstance(item[0], str): if item[0].startswith(key): area_type = VAR_NAME_TYPES[key] elif isinstance(item[0], int): if item[0] in VAR_NAME_TYPES.keys(): area_type = item[0] # Data block if area_type == 0x84: block_num = int(item[0][2:]) else: block_num = 0 if isinstance(item[1], str): address_data = item[1].split('.') address = int(address_data[0]) * 8 + int(address_data[1]) elif isinstance(item[1], int): address = item[1] else: self.logger.error( "Address: %s is not string or int format, please check again" % item[1]) transport_size = self.get_transport_size_from_data_type(item[2]) except Exception as err: self.logger.error( "Can't get item parameter with var_name: %s with error: \r %s" % (item, err)) return transport_size, block_num, area_type, address return transport_size, block_num, area_type, address @staticmethod def bytes_to_bit_array(bytes_data): bit_array = "" for data in bytes_data: bit_array += '{:08b}'.format(ord(data)) return map(int, list(bit_array)) def _unpack_data_with_transport_size(self, req_item, rsp_item): # ref http://www.plcdev.com/step_7_elementary_data_types if isinstance(rsp_item, S7ReadVarDataItemsRsp): try: req_type = req_item.TransportSize if req_type not in S7_TRANSPORT_SIZE_IN_PARM_ITEMS.keys(): return [] # BIT (0x01) elif req_type == 0x01: bit_list = self.bytes_to_bit_array(rsp_item.Data) return bit_list[-1:][0] # BYTE (0x02) elif req_type == 0x02: byte_list = list(rsp_item.Data) return map(ord, byte_list) # CHAR (0x03) elif req_type == 0x03: char_list = list(rsp_item.Data) return char_list # WORD (0x04) 2 bytes Decimal number unsigned elif req_type == 0x04: word_data = rsp_item.Data word_list = [ struct.unpack('!H', word_data[i:i + 2])[0] for i in range(0, len(word_data), 2) ] return word_list # INT (0x05) 2 bytes Decimal number signed elif req_type == 0x05: int_data = rsp_item.Data int_list = [ struct.unpack('!h', int_data[i:i + 2])[0] for i in range(0, len(int_data), 2) ] return int_list # DWORD (0x06) 4 bytes Decimal number unsigned elif req_type == 0x06: dword_data = rsp_item.Data dword_list = [ struct.unpack('!I', dword_data[i:i + 4])[0] for i in range(0, len(dword_data), 4) ] return dword_list # DINT (0x07) 4 bytes Decimal number signed elif req_type == 0x07: dint_data = rsp_item.Data dint_list = [ struct.unpack('!i', dint_data[i:i + 4])[0] for i in range(0, len(dint_data), 4) ] return dint_list # REAL (0x08) 4 bytes IEEE Floating-point number elif req_type == 0x08: dint_data = rsp_item.Data dint_list = [ struct.unpack('!f', dint_data[i:i + 4])[0] for i in range(0, len(dint_data), 4) ] return dint_list else: return rsp_item.Data except Exception as err: return [] return [] @staticmethod def _pack_data_with_transport_size(req_item, data_list): # ref http://www.plcdev.com/step_7_elementary_data_types if isinstance(req_item, S7WriteVarItemsReq): try: req_type = req_item.TransportSize if req_type not in S7_TRANSPORT_SIZE_IN_PARM_ITEMS.keys(): return [] # BIT (0x01) elif req_type == 0x01: # Only support write 1 bit. if isinstance(data_list, list): bit_data = chr(data_list[0]) else: bit_data = chr(data_list) return bit_data # BYTE (0x02) elif req_type == 0x02: byte_data = ''.join(chr(x) for x in data_list) return byte_data # CHAR (0x03) elif req_type == 0x03: char_data = ''.join(x for x in data_list) return char_data # WORD (0x04) 2 bytes Decimal number unsigned elif req_type == 0x04: word_data = ''.join( struct.pack('!H', x) for x in data_list) return word_data # INT (0x05) 2 bytes Decimal number signed elif req_type == 0x05: int_data = ''.join(struct.pack('!h', x) for x in data_list) return int_data # DWORD (0x06) 4 bytes Decimal number unsigned elif req_type == 0x06: dword_data = ''.join( struct.pack('!I', x) for x in data_list) return dword_data # DINT (0x07) 4 bytes Decimal number signed elif req_type == 0x07: dint_data = ''.join( struct.pack('!i', x) for x in data_list) return dint_data # REAL (0x08) 4 bytes IEEE Floating-point number elif req_type == 0x08: real_data = ''.join( struct.pack('!f', x) for x in data_list) return real_data # Other data else: other_data = ''.join(x for x in data_list) return other_data except Exception as err: return '' return '' @staticmethod def _convert_transport_size_from_parm_to_data(parm_transport_size): if parm_transport_size not in S7_TRANSPORT_SIZE_IN_PARM_ITEMS.keys(): return None else: # BIT (0x03) if parm_transport_size == 0x01: return 0x03 # BYTE/WORD/DWORD (0x04) elif parm_transport_size in (0x02, 0x04, 0x06): return 0x04 # INTEGER (0x05) elif parm_transport_size in (0x05, 0x07): return 0x05 # REAL (0x07) elif parm_transport_size == 0x08: return 0x07 # OCTET STRING (0x09) else: return 0x09 def read_var(self, items): ''' :param items: :return: Return data list of read_var items. ''' read_items = [] items_data = [] if isinstance(items, list): for i in range(len(items)): try: transport_size, block_num, area_type, address = self.get_item_pram_from_item( items[i]) length = int(items[i][3]) if transport_size: read_items.append( S7ReadVarItemsReq(TransportSize=transport_size, GetLength=length, BlockNum=block_num, AREAType=area_type, Address=address)) except Exception as err: self.logger.error( "Can't create read var packet because of: \r %s" % err) return None else: self.logger.error("items is not list please check again") return None packet = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7ReadVarParameterReq(Items=read_items)) rsp = self.send_receive_s7_packet(packet) if rsp.ErrorClass != 0x0: self.logger.error("Can't Read var from Target") self.logger.error("Error Class: %s, Error Code %s" % (rsp.ErrorClass, rsp.ErrorCode)) return None if rsp.haslayer(S7ReadVarDataItemsRsp): for i in range(len(rsp[S7ReadVarDataRsp].Items)): req_item = read_items[i][S7ReadVarItemsReq] rsp_item = rsp[S7ReadVarDataRsp].Items[i] if rsp_item.ReturnCode == 0xff: rsp_item_data = self._unpack_data_with_transport_size( req_item, rsp_item) items_data.append(rsp_item_data) else: items_data.append('') return items_data def write_var(self, items): """ :param items: :return: """ write_items = [] items_data = [] write_data_rsp = [] if isinstance(items, list): for i in range(len(items)): try: transport_size, block_num, area_type, address = self.get_item_pram_from_item( items[i]) length = len(items[i][3]) if transport_size: write_items.append( S7WriteVarItemsReq(TransportSize=transport_size, ItemCount=length, BlockNum=block_num, AREAType=area_type, BitAddress=address)) write_data = self._pack_data_with_transport_size( write_items[i], items[i][3]) items_data.append( S7WriteVarDataItemsReq( TransportSize=self. _convert_transport_size_from_parm_to_data( transport_size), Data=write_data)) except Exception as err: self.logger.error( "Can't create write var packet because of: \r %s" % err) return None else: self.logger.error("items is not list please check again") return None packet = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7WriteVarParameterReq(Items=write_items), Data=S7WriteVarDataReq(Items=items_data)) rsp = self.send_receive_s7_packet(packet) if rsp.ErrorClass != 0x0: self.logger.error("Can't write var to Target.") self.logger.error("Error Class: %s, Error Code %s" % (rsp.ErrorClass, rsp.ErrorCode)) return None if rsp.haslayer(S7WriteVarDataRsp): for rsp_items in rsp[S7WriteVarDataRsp].Items: write_data_rsp.append(rsp_items.ReturnCode) return write_data_rsp else: self.logger.error("Unknown response packet format.") return None
class S7Client(Base): def __init__(self, name, ip, port=102, src_tsap='\x01\x00', rack=0, slot=2, timeout=2): ''' :param name: Name of this targets :param ip: S7 PLC ip :param port: S7 PLC port (default: 102) :param src_tsap: src_tsap :param rack: cpu rack (default: 0) :param slot: cpu slot (default: 2) :param timeout: timeout of socket (default: 2) ''' super(S7Client, self).__init__(name=name) self._ip = ip self._port = port self._slot = slot self._src_tsap = src_tsap self._dst_tsap = '\x01' + struct.pack('B', rack * 0x20 + slot) self._pdur = 1 self.protect_level = None self._connection = None self._connected = False self._timeout = timeout self._pdu_length = 480 self.readable = False self.writeable = False self.authorized = False self._password = None self._mmc_password = None self.is_running = False def connect(self): sock = socket.socket() sock.connect((self._ip, self._port)) sock.settimeout(self._timeout) self._connection = StreamSocket(sock, Raw) packet1 = TPKT() / COTPCR() packet1.Parameters = [COTPOption() for i in range(3)] packet1.PDUType = "CR" packet1.Parameters[0].ParameterCode = "tpdu-size" packet1.Parameters[0].Parameter = "\x0a" packet1.Parameters[1].ParameterCode = "src-tsap" packet1.Parameters[2].ParameterCode = "dst-tsap" packet1.Parameters[1].Parameter = self._src_tsap packet1.Parameters[2].Parameter = self._dst_tsap self.send_receive_packet(packet1) packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7SetConParameter()) rsp2 = self.send_receive_s7_packet(packet2) if rsp2: self._connected = True # Todo: Need get pdu length from rsp2 def _get_cpu_protect_level(self): packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7ReadSZLParameterReq(), Data=S7ReadSZLDataReq(SZLId=0x0232, SZLIndex=0x0004)) rsp = self.send_receive_s7_packet(packet1) self.protect_level = int(str(rsp)[48].encode('hex')) self.logger.info("CPU protect level is %s" % self.protect_level) def get_target_info(self): order_code = '' version = '' module_type_name = '' as_name = '' module_name = '' serial_number = '' packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7ReadSZLParameterReq(), Data=S7ReadSZLDataReq(SZLId=0x0011, SZLIndex=0x0000)) rsp1 = self.send_receive_s7_packet(packet1) try: order_code_data = rsp1[ S7ReadSZLDataTreeRsp].Data[:rsp1[S7ReadSZLDataRsp].SZLLength] order_code = order_code_data[2:-7] version_data = rsp1[S7ReadSZLDataTreeRsp].Data[-3:] version = 'V {:x}.{:x}.{:x}'.format( int(version_data[0].encode('hex'), 16), int(version_data[1].encode('hex'), 16), int(version_data[2].encode('hex'), 16), ) except Exception as err: self.logger.error("Can't get order code and version from target") return order_code, version, module_type_name, as_name, module_name, serial_number packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7ReadSZLParameterReq(), Data=S7ReadSZLDataReq(SZLId=0x001c, SZLIndex=0x0000)) rsp2 = self.send_receive_s7_packet(packet2) try: module_name_data = rsp2[S7ReadSZLDataTreeRsp].Data[ rsp2[S7ReadSZLDataRsp].SZLLength + 2:rsp2[S7ReadSZLDataRsp].SZLLength * 2] module_name = str( module_name_data[:module_name_data.index('\x00')]) self.logger.debug("module_name:%s " % module_name) as_name_data = rsp2[S7ReadSZLDataTreeRsp].Data[ 2:rsp2[S7ReadSZLDataRsp].SZLLength] as_name = str(as_name_data[:as_name_data.index('\x00')]) self.logger.debug("as_name:%s " % as_name) serial_number_data = rsp2[S7ReadSZLDataTreeRsp].Data[ rsp2[S7ReadSZLDataRsp].SZLLength * 4 + 2:rsp2[S7ReadSZLDataRsp].SZLLength * 5] serial_number = str( serial_number_data[:serial_number_data.index('\x00')]) self.logger.debug("serial_number:%s " % serial_number) module_type_name_data = rsp2[S7ReadSZLDataTreeRsp].Data[ rsp2[S7ReadSZLDataRsp].SZLLength * 5 + 2:rsp2[S7ReadSZLDataRsp].SZLLength * 6] module_type_name = str( module_type_name_data[:module_type_name_data.index('\x00')]) self.logger.debug("module_type_name:%s " % module_type_name) except Exception as err: self.logger.error("Can't get module info from target") return order_code, version, module_type_name, as_name, module_name, serial_number return order_code, version, module_type_name, as_name, module_name, serial_number def check_privilege(self): self._get_cpu_protect_level() if self.protect_level == 1: self.logger.info("You have full privilege with this targets") self.readable = True self.writeable = True if self.protect_level == 2: if self.authorized is True: self.logger.info("You have full privilege with this targets") self.readable = True self.writeable = True else: self.logger.info( "You only have read privilege with this targets") self.readable = True self.writeable = False if self.protect_level == 3: if self.authorized is True: self.logger.info("You have full privilege with this targets") self.readable = True self.writeable = True else: self.logger.info("You can't read or write with this targets") self.readable = False self.writeable = False def auth(self, password): ''' :param password: Paintext PLC password. ''' self.logger.info("Start authenticate with password %s" % password) password_hash = self._hash_password(password) packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7PasswordParameterReq(), Data=S7PasswordDataReq(Data=password_hash)) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.haslayer(S7PasswordParameterRsp): if rsp1[S7PasswordParameterRsp].ErrorCode == 0: self.authorized = True self.logger.info("Authentication succeed") else: if self.authorized is True: self.logger.info("Already authorized") else: error_code = rsp1[S7PasswordParameterRsp].ErrorCode if error_code in S7_ERROR_CLASS.keys(): self.logger.error("Got error code: %s" % S7_ERROR_CLASS[error_code]) else: self.logger.error("Get error code: %s" % hex(error_code)) self.logger.error("Authentication failure") self.check_privilege() else: self.logger.info( "Receive unknown format packet, authentication failure") def clean_session(self): self.logger.info("Start clean the session") packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7CleanSessionParameterReq(), Data=S7CleanSessionDataReq()) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.haslayer(S7CleanSessionParameterRsp): if rsp1[S7CleanSessionParameterRsp].ErrorCode == 0: self.authorized = False self.logger.info("session cleaned") else: error_code = rsp1[S7CleanSessionParameterRsp].ErrorCode if error_code in S7_ERROR_CLASS.keys(): self.logger.error("Got error code: %s" % S7_ERROR_CLASS[error_code]) else: self.logger.error("Get error code: %s" % hex(error_code)) else: self.logger.info( "Receive unknown format packet, authentication failure") def _hash_password(self, password): password_hash_new = '' if len(password) < 1 or len(password) > 8: self.logger.error("Password length must between 1 to 8") return None else: password += '20'.decode('hex') * (8 - len(password)) for i in range(8): if i < 2: temp_data = ord(password[i]) temp_data ^= 0x55 password_hash_new += str(chr(temp_data)) else: temp_data1 = ord(password[i]) temp_data2 = ord(password_hash_new[i - 2]) temp_data1 = temp_data1 ^ 0x55 ^ temp_data2 password_hash_new += str(chr(temp_data1)) return password_hash_new def _fix_pdur(self, payload): if self._pdur > 65535: self._pdur = 1 try: payload.PDUR = self._pdur self._pdur += 1 return payload except Exception as err: self.logger.error(err) return payload def send_packet(self, packet): if self._connection: try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_packet(self, packet): if self._connection: try: rsp = self._connection.sr1(packet, timeout=self._timeout) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_packet(self): if self._connection: try: rsp = self._connection.recv() return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") def send_s7_packet(self, packet): if self._connection: packet = self._fix_pdur(packet) try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_s7_packet(self, packet): if self._connection: packet = self._fix_pdur(packet) try: rsp = self._connection.sr1(packet, timeout=self._timeout) if rsp: rsp = TPKT(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_s7_packet(self): if self._connection: try: rsp = self._connection.recv() if rsp: rsp = TPKT(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") def upload_block_from_target(self, block_type, block_num, dist='A'): """ :param block_type: "08": 'OB', "09": 'CMOD', "0A": 'DB', "0B": 'SDB', "0C": 'FC', "0D": 'SFC', "0E": 'FB', "0F": 'SFB' :param block_num: Block number. :param dist: 'A': "Active embedded module", 'B': "Active as well as passive module", 'P': "Passive (copied, but not chained) module" :return: Block Data """ if self.readable is False: self.logger.info("Didn't have read privilege on targets") return None block_data = '' if block_type in S7_BLOCK_TYPE_IN_FILE_NAME.keys(): file_block_type = block_type else: for key, name in S7_BLOCK_TYPE_IN_FILE_NAME.iteritems(): if name == block_type: file_block_type = key break else: self.logger.error( "block_type: %s is incorrect please check again" % block_type) return if type(block_num) != int: self.logger.error("block_num must be int format.") return file_block_num = "{0:05d}".format(block_num) file_name = '_' + file_block_type + file_block_num + dist self.logger.info("Start upload %s%s from target" % (block_type, block_num)) packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7RequestUploadBlockParameterReq(Filename=file_name)) rsp1 = self.send_receive_s7_packet(packet1) # Todo: Might got some error if rsp1.ErrorClass != 0x0: self.logger.error("Can't upload %s%s from target" % (block_type, block_num)) self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return None packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7UploadBlockParameterReq()) packet2[S7UploadBlockParameterReq].UploadId = rsp1[ S7RequestUploadBlockParameterRsp].UploadId while True: rsp2 = self.send_receive_s7_packet(packet2) if rsp2.ErrorClass != 0x0: self.logger.error("Can't upload %s%s from targets" % (block_type, block_num)) self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return None self.logger.debug("rsp2: %s" % str(rsp2).encode('hex')) block_data += rsp2.Data.Data if rsp2.Parameters.FunctionStatus != 1: break packet3 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7UploadBlockEndParameterReq()) self.send_receive_s7_packet(packet3) self.logger.info("Upload %s%s from target succeed" % (block_type, block_num)) return block_data def get_info_from_block(self, block_data): """ :param block_data: Block data. :return: mem_length, mc7_length, block_type, block_num """ try: mem_length = struct.unpack('!i', block_data[8:12])[0] mc7_length = struct.unpack('!h', block_data[34:36])[0] block_type = S7_BLOCK_TYPE_IN_BLOCK[ord(block_data[5])] block_num = struct.unpack('!h', block_data[6:8])[0] return mem_length, mc7_length, block_type, block_num except Exception as err: self.logger.error(err) return None def download_block_to_target(self, block_data, dist='P', transfer_size=462, stop_target=False): """ Download block to target and active block. :param block_data: Block data to download. :param dist: 'A': "Active embedded module", 'B': "Active as well as passive module", 'P': "Passive (copied, but not chained) module". :param transfer_size: Transfer size for each packet. :param stop_target: Stop target PLC before download block, True or False. """ if self.writeable is False: self.logger.info("Didn't have write privilege on targets") return None mem_length, mc7_length, block_type, block_num = self.get_info_from_block( block_data) self.logger.info("Start download %s%s to targets" % (block_type, block_num)) file_block_type = None for key, name in S7_BLOCK_TYPE_IN_FILE_NAME.iteritems(): if name == block_type: file_block_type = key break if not file_block_type: self.logger.error( "block_type: %s is incorrect please check again" % block_type) return file_block_num = "{0:05d}".format(block_num) file_name = '_' + file_block_type + file_block_num + dist load_memory_length = '0' * (6 - len(str(mem_length))) + str(mem_length) mc7_length = '0' * (6 - len(str(mc7_length))) + str(mc7_length) packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7RequestDownloadParameterReq( Filename=file_name, LoadMemLength=load_memory_length, MC7Length=mc7_length)) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.ErrorClass != 0x0: self.logger.error("Can't Download %s%s to targets" % (block_type, block_num)) self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return None if len(rsp1) > 20: download_req = TPKT(rsp1.load) else: download_req = self.receive_s7_packet() # Get pdur from download_req self._pdur = download_req.PDUR # DownloadBlock for i in range(0, len(block_data), transfer_size): if i + transfer_size <= len(block_data): packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadParameterRsp(FunctionStatus=1), Data=S7DownloadDataRsp(Data=block_data[i:i + transfer_size])) rsp2 = self.send_receive_s7_packet(packet2) self._pdur = rsp2.PDUR else: packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadParameterRsp(FunctionStatus=0), Data=S7DownloadDataRsp(Data=block_data[i:i + transfer_size])) self.send_s7_packet(packet2) # DownloadBlockEnd download_end_req = self.receive_s7_packet() self._pdur = download_end_req.PDUR packet3 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadEndParameterRsp()) self.send_s7_packet(packet3) # Insert block self.logger.debug("File_name:%s" % ('\x00' + file_name[1:])) packet4 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7PIServiceParameterReq( ParameterBlock=S7PIServiceParameterBlock( FileNames=['\x00' + file_name[1:]]), PI="_INSE")) # Todo: Might have a better way to do this # packet4[S7PIServiceParameterReq].ParameterBlock = S7PIServiceParameterBlock(FileNames=[file_name[1:]]) rsp4 = self.send_receive_s7_packet(packet4) if rsp4.ErrorClass != 0x0: self.logger.error("Can't insert %s%s to targets" % (block_type, block_num)) self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return None self.logger.info("Download %s%s to target succeed" % (block_type, block_num)) def download_block_to_target_only(self, block_data, dist='P', transfer_size=462, stop_target=False): """ Download block to target only (didn't active block). :param block_data: Block data to download. :param dist: 'A': "Active embedded module", 'B': "Active as well as passive module", 'P': "Passive (copied, but not chained) module". :param transfer_size: Transfer size for each packet. :param stop_target: Stop target PLC before download block, True or False. """ if self.writeable is False: self.logger.info("Didn't have write privilege on targets") return None mem_length, mc7_length, block_type, block_num = self.get_info_from_block( block_data) self.logger.info("Start download %s%s to targets" % (block_type, block_num)) file_block_type = None for key, name in S7_BLOCK_TYPE_IN_FILE_NAME.iteritems(): if name == block_type: file_block_type = key break if not file_block_type: self.logger.error( "block_type: %s is incorrect please check again" % block_type) return file_block_num = "{0:05d}".format(block_num) file_name = '_' + file_block_type + file_block_num + dist load_memory_length = '0' * (6 - len(str(mem_length))) + str(mem_length) mc7_length = '0' * (6 - len(str(mc7_length))) + str(mc7_length) packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7RequestDownloadParameterReq( Filename=file_name, LoadMemLength=load_memory_length, MC7Length=mc7_length)) rsp1 = self.send_receive_s7_packet(packet1) # print(len(rsp1)) if rsp1.ErrorClass != 0x0: self.logger.error("Can't Download %s%s to targets" % (block_type, block_num)) self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return None if len(rsp1) > 20: download_req = TPKT(rsp1.load) else: download_req = self.receive_s7_packet() # Get pdur from download_req self._pdur = download_req.PDUR # DownloadBlock for i in range(0, len(block_data), transfer_size): if i + transfer_size <= len(block_data): packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadParameterRsp(FunctionStatus=1), Data=S7DownloadDataRsp(Data=block_data[i:i + transfer_size])) rsp2 = self.send_receive_s7_packet(packet2) self._pdur = rsp2.PDUR else: packet2 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadParameterRsp(FunctionStatus=0), Data=S7DownloadDataRsp(Data=block_data[i:i + transfer_size])) self.send_s7_packet(packet2) # DownloadBlockEnd download_end_req = self.receive_s7_packet() self._pdur = download_end_req.PDUR packet3 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="AckData", Parameters=S7DownloadEndParameterRsp()) self.send_s7_packet(packet3) self.logger.info("Download %s%s to target succeed" % (block_type, block_num)) def get_target_status(self): packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="UserData", Parameters=S7ReadSZLParameterReq(), Data=S7ReadSZLDataReq(SZLId=0x0424, SZLIndex=0x0000)) rsp = self.send_receive_s7_packet(packet1) status = str(rsp)[44] if status == '\x08': self.logger.info("Target is in run mode") self.is_running = True elif status == '\x04': self.logger.info("Target is in stop mode") self.is_running = False else: self.logger.info("Target is in unknown mode") self.is_running = False def stop_target(self): self.get_target_status() if not self.is_running: self.logger.info("Target is already stop") return self.logger.info("Trying to stop targets") packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7StopCpuParameterReq()) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.ErrorClass != 0x0: self.logger.error("Can't Stop Target") self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return time.sleep(2) # wait targets to stop self.get_target_status() def start_target(self, cold=False): ''' Start target PLC :param cold: Doing cold restart, True or False. ''' self.get_target_status() if self.is_running: self.logger.info("Target is already running") return self.logger.info("Trying to start targets") if cold: packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7PIServiceParameterReq( ParameterBlock=S7PIServiceParameterStringBlock())) else: packet1 = TPKT() / COTPDT(EOT=1) / S7Header( ROSCTR="Job", Parameters=S7PIServiceParameterReq()) rsp1 = self.send_receive_s7_packet(packet1) if rsp1.ErrorClass != 0x0: self.logger.error("Can't Start Target") self.logger.error("Error Class: %s, Error Code %s" % (rsp1.ErrorClass, rsp1.ErrorCode)) return time.sleep(2) # wait targets to start self.get_target_status()
class ModbusClient(Base): def __init__(self, name, ip, port=502, timeout=2): ''' :param name: Name of this targets :param ip: Modbus Target ip :param port: Modbus TCP port (default: 502) :param timeout: timeout of socket (default: 2) ''' super(ModbusClient, self).__init__(name=name) self._ip = ip self._port = port self._connection = None self._connected = False self._timeout = timeout def connect(self): sock = socket.socket() sock.connect((self._ip, self._port)) sock.settimeout(self._timeout) self._connection = StreamSocket(sock, Raw) def send_packet(self, packet): if self._connection: try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_packet(self, packet): if self._connection: try: rsp = self._connection.sr1(packet, timeout=self._timeout) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_packet(self): if self._connection: try: rsp = self._connection.recv() return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") def send_modbus_packet(self, packet): if self._connection: try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_modbus_packet(self, packet): func_code = packet.func_code if self._connection: try: rsp = self._connection.sr1(packet, timeout=self._timeout) if rsp: rsp = ModbusHeaderResponse(str(rsp)) if rsp.haslayer(modbus_response_classes[func_code]): return rsp elif rsp.haslayer(GenericError): self.logger.error("Got error with error code:%s" % rsp.exceptCode) return None except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_modbus_packet(self): if self._connection: try: rsp = self._connection.recv() if rsp: rsp = ModbusHeaderResponse(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") @staticmethod def bytes_to_bit_array(coils_bytes): bit_array = "" for data in coils_bytes: bit_array += '{:08b}'.format(ord(data))[::-1] return list(bit_array) def read_coils(self, address, count): ''' :param address: Reference Number of coils :param count: Bit Count for read :return: Coil Status in list, if got some error return None. ''' packet = ModbusHeaderRequest(func_code=0x01) / ReadCoilsRequest( ReferenceNumber=address, BitCount=count) rsp = self.send_receive_modbus_packet(packet) if rsp: coils = rsp.CoilsStatus coils = self.bytes_to_bit_array(coils) return coils[:count] else: return None def read_discrete_inputs(self, address, count): ''' :param address: Reference Number of discrete inputs :param count: Bit Count for read :return: InputStatus in list, if got some error return None. ''' packet = ModbusHeaderRequest( func_code=0x02) / ReadDiscreteInputsRequest( ReferenceNumber=address, BitCount=count) rsp = self.send_receive_modbus_packet(packet) if rsp: inputStatus = rsp.InputStatus inputStatus = self.bytes_to_bit_array(inputStatus) return inputStatus[:count] else: return None def read_holding_registers(self, address, count): ''' :param address: Reference Number of holding registers :param count: Word count for read :return: Registers in list ''' packet = ModbusHeaderRequest(func_code=0x03) / \ ReadHoldingRegistersRequest(ReferenceNumber=address, WordCount=count) rsp = self.send_receive_modbus_packet(packet) registers = rsp.RegisterValue return registers def read_input_registers(self, address, count): ''' :param address: Reference Number of input registers :param count: Word count for read :return: Registers in list ''' packet = ModbusHeaderRequest(func_code=0x04) / \ ReadInputRegistersRequest(ReferenceNumber=address, WordCount=count) rsp = self.send_receive_modbus_packet(packet) registers = rsp.RegisterValue return registers def write_single_coil(self, address, value): ''' :param address: Reference Number of coil :param value: coil's value(True or False) :return: Response packet ''' # TODO: Need return only value. if value is True: data = 0xFF00 else: data = 0x0000 packet = ModbusHeaderRequest(func_code=0x05) / WriteSingleCoilRequest( ReferenceNumber=address, Value=data) rsp = self.send_receive_modbus_packet(packet) return rsp def write_single_register(self, address, value): ''' :param address: Reference Number of register :param value: value of register (0x0-0xffff) :return: Response packet ''' packet = ModbusHeaderRequest( func_code=0x06) / WriteSingleRegisterRequest( ReferenceNumber=address, Value=value) rsp = self.send_receive_modbus_packet(packet) return rsp def write_multiple_coils(self, address, values): ''' :param address: Reference Number of coils :param values: values to write in list must in multiples of 8. example: values = [0, 0, 0, 0, 1, 1, 1, 1] :return: Response packet ''' values = values[::-1] # least significant bit = first coil packet = ModbusHeaderRequest( func_code=0x0F) / WriteMultipleCoilsRequest( ReferenceNumber=address, Values=values) rsp = self.send_receive_modbus_packet(packet) return rsp def write_multiple_registers(self, address, values): ''' :param address: address: Reference Number of register :param values: values to write in list. example: values = [0x01, 0x02, 0x03, 0x04] :return: Response packet ''' packet = ModbusHeaderRequest(func_code=0x10) / \ WriteMultipleRegistersRequest(ReferenceNumber=address, Values=values) rsp = self.send_receive_modbus_packet(packet) return rsp def read_file_record(self, file_number, offset, length): ''' :param file_number: File number :param offset: offset of file :param length: length to read :return: Response packet ''' packet = ModbusHeaderRequest(func_code=0x14) / ReadFileRecordRequest() packet[ReadFileRecordRequest].Groups = ReadFileSubRequest( FileNumber=file_number, Offset=offset, Length=length) rsp = self.send_receive_modbus_packet(packet) return rsp def write_file_record(self, file_number, offset, data): ''' :param file_number: File number :param offset: offset of file :param data: data to write :return: Response packet ''' data_list = [] for i in range(0, len(data), 0x02): data1 = struct.unpack("!H", data[i:i + 2])[0] data_list.append(data1) packet = ModbusHeaderRequest(func_code=0x15) / WriteFileRecordRequest() packet[WriteFileRecordRequest].Groups = WriteFileSubRequest( FileNumber=file_number, Offset=offset, Data=data_list) rsp = self.send_receive_modbus_packet(packet) return rsp def mask_write_register(self, address, and_mask=0xffff, or_mask=0x0000): ''' :param address: Reference Number of register :param and_mask: And mask of register :param or_mask: Or mask of register :return: Response packet ''' packet = ModbusHeaderRequest(func_code=0x16) / \ MaskWriteRegisterRequest(ReferenceNumber=address, AndMask=and_mask, OrMask=or_mask), rsp = self.send_receive_modbus_packet(packet) return rsp def read_write_multiple_registers(self, read_address, read_count, write_address, values): ''' :param read_address: Reference Number of register to read :param read_count: Word count for read :param write_address: Reference Number of register to write :param values: values to write in list. example: values = [0x01, 0x02, 0x03, 0x04] :return: Response packet ''' packet = ModbusHeaderRequest(func_code=0x17) / \ ReadWriteMultipleRegistersRequest(ReadReferenceNumber=read_address, ReadWordCount=read_count, WriteReferenceNumber=write_address, RegisterValues=values) rsp = self.send_receive_modbus_packet(packet) return rsp def read_fifo_queue(self, address): ''' :param address: Reference Number of fifo :return: Response packet ''' packet = ModbusHeaderRequest(func_code=0x17) / ReadFIFOQueueRequest( ReferenceNumber=address) rsp = self.send_receive_modbus_packet(packet) return rsp
class Client(Client): __info__ = { 'name': 'clients/modbus', 'display_name': 'Modbus Client', 'description': '', 'authors': [ 'D0ubl3G <d0ubl3g[at]protonmail.com>', ], 'references': [ '', ], 'devices': [ 'Multi', ], } target = Option('', 'Target IP address.') port = Option(502, 'Target port.') timeout = Option(2, 'Connection timeout.') def __init__(self): super(Client, self).__init__() self._connection = None self._connected = False def run(self): if self.connect(self.target, self.port): print_success("Connected to " + self.target + ":" + str(self.port) + " successfully.") def connect(self, target, port): try: sock = socket.socket() sock.connect((target, port)) sock.settimeout(self.timeout) self._connection = StreamSocket(sock, Raw) return True except ConnectionRefusedError as e: print_error("Conection was refused.") return False def send_packet(self, packet): if self._connection: try: self._connection.send(packet) except Exception as err: print_error(err) return None else: print_error("Please create connect before send packet!") def send_receive_packet(self, packet): if self._connection: try: rsp = self._connection.sr1(packet, timeout=self.timeout) return rsp except Exception as err: print_error(err) return None else: print_error("Please create connect before send packet!") def receive_packet(self): if self._connection: try: rsp = self._connection.recv() return rsp except Exception as err: print_error(err) return None else: print_error("Please create connect before receive packet!") def send_modbus_packet(self, packet): if self._connection: try: self._connection.send(packet) except Exception as err: print_error(err) return None else: print_error("Please create connect before send packet!") def send_receive_modbus_packet(self, packet): func_code = packet.func_code if self._connection: try: rsp = self._connection.sr1(packet, timeout=self.timeout) if rsp: rsp = ModbusHeaderResponse(str(rsp)) if rsp.haslayer(modbus_response_classes[func_code]): return rsp elif rsp.haslayer(GenericError): print_error("Got error with error code:%s" % rsp.exceptCode) return None except Exception as err: print_error(err) return None else: print_error("Please create connect before send packet!") def receive_modbus_packet(self): if self._connection: try: rsp = self._connection.recv() if rsp: rsp = ModbusHeaderResponse(str(rsp)) return rsp except Exception as err: print_error(err) return None else: print_error("Please create connect before receive packet!") @staticmethod def bytes_to_bit_array(coils_bytes): bit_array = "" for data in coils_bytes: bit_array += '{:08b}'.format(ord(data))[::-1] return list(bit_array) def read_coils(self, address, count): """ :param address: Reference Number of coils :param count: Bit Count for read :return: Coil Status in list, if got some error return None. """ packet = ModbusHeaderRequest(func_code=0x01) / ReadCoilsRequest( ReferenceNumber=address, BitCount=count) rsp = self.send_receive_modbus_packet(packet) if rsp: coils = rsp.CoilsStatus coils = self.bytes_to_bit_array(coils) return coils[:count] else: return None def read_discrete_inputs(self, address, count): """ :param address: Reference Number of discrete inputs :param count: Bit Count for read :return: InputStatus in list, if got some error return None. """ packet = ModbusHeaderRequest( func_code=0x02) / ReadDiscreteInputsRequest( ReferenceNumber=address, BitCount=count) rsp = self.send_receive_modbus_packet(packet) if rsp: inputStatus = rsp.InputStatus inputStatus = self.bytes_to_bit_array(inputStatus) return inputStatus[:count] else: return None def read_holding_registers(self, address, count): """ :param address: Reference Number of holding registers :param count: Word count for read :return: Registers in list """ packet = ModbusHeaderRequest(func_code=0x03) / \ ReadHoldingRegistersRequest(ReferenceNumber=address, WordCount=count) rsp = self.send_receive_modbus_packet(packet) registers = rsp.RegisterValue return registers def read_input_registers(self, address, count): """ :param address: Reference Number of input registers :param count: Word count for read :return: Registers in list """ packet = ModbusHeaderRequest(func_code=0x04) / \ ReadInputRegistersRequest(ReferenceNumber=address, WordCount=count) rsp = self.send_receive_modbus_packet(packet) registers = rsp.RegisterValue return registers def write_single_coil(self, address, value): """ :param address: Reference Number of coil :param value: coil's value(True or False) :return: Response packet """ # TODO: Need return only value. if value is True: data = 0xFF00 else: data = 0x0000 packet = ModbusHeaderRequest(func_code=0x05) / WriteSingleCoilRequest( ReferenceNumber=address, Value=data) rsp = self.send_receive_modbus_packet(packet) return rsp def write_single_register(self, address, value): """ :param address: Reference Number of register :param value: value of register (0x0-0xffff) :return: Response packet """ packet = ModbusHeaderRequest( func_code=0x06) / WriteSingleRegisterRequest( ReferenceNumber=address, Value=value) rsp = self.send_receive_modbus_packet(packet) return rsp def write_multiple_coils(self, address, values): """ :param address: Reference Number of coils :param values: values to write in list must in multiples of 8. example: values = [0, 0, 0, 0, 1, 1, 1, 1] :return: Response packet """ values = values[::-1] # least significant bit = first coil packet = ModbusHeaderRequest( func_code=0x0F) / WriteMultipleCoilsRequest( ReferenceNumber=address, Values=values) rsp = self.send_receive_modbus_packet(packet) return rsp def write_multiple_registers(self, address, values): """ :param address: address: Reference Number of register :param values: values to write in list. example: values = [0x01, 0x02, 0x03, 0x04] :return: Response packet """ packet = ModbusHeaderRequest(func_code=0x10) / \ WriteMultipleRegistersRequest(ReferenceNumber=address, Values=values) rsp = self.send_receive_modbus_packet(packet) return rsp def read_file_record(self, file_number, offset, length): """ :param file_number: File number :param offset: offset of file :param length: length to read :return: Response packet """ packet = ModbusHeaderRequest(func_code=0x14) / ReadFileRecordRequest() packet[ReadFileRecordRequest].Groups = ReadFileSubRequest( FileNumber=file_number, Offset=offset, Length=length) rsp = self.send_receive_modbus_packet(packet) return rsp def write_file_record(self, file_number, offset, data): """ :param file_number: File number :param offset: offset of file :param data: data to write :return: Response packet """ data_list = [] for i in range(0, len(data), 0x02): data1 = struct.unpack("!H", data[i:i + 2])[0] data_list.append(data1) packet = ModbusHeaderRequest(func_code=0x15) / WriteFileRecordRequest() packet[WriteFileRecordRequest].Groups = WriteFileSubRequest( FileNumber=file_number, Offset=offset, Data=data_list) rsp = self.send_receive_modbus_packet(packet) return rsp def mask_write_register(self, address, and_mask=0xffff, or_mask=0x0000): """ :param address: Reference Number of register :param and_mask: And mask of register :param or_mask: Or mask of register :return: Response packet """ packet = ModbusHeaderRequest(func_code=0x16) / \ MaskWriteRegisterRequest(ReferenceNumber=address, AndMask=and_mask, OrMask=or_mask), rsp = self.send_receive_modbus_packet(packet) return rsp def read_write_multiple_registers(self, read_address, read_count, write_address, values): """ :param read_address: Reference Number of register to read :param read_count: Word count for read :param write_address: Reference Number of register to write :param values: values to write in list. example: values = [0x01, 0x02, 0x03, 0x04] :return: Response packet """ packet = ModbusHeaderRequest(func_code=0x17) / \ ReadWriteMultipleRegistersRequest(ReadReferenceNumber=read_address, ReadWordCount=read_count, WriteReferenceNumber=write_address, RegisterValues=values) rsp = self.send_receive_modbus_packet(packet) return rsp def read_fifo_queue(self, address): """ :param address: Reference Number of fifo :return: Response packet """ packet = ModbusHeaderRequest(func_code=0x17) / ReadFIFOQueueRequest( ReferenceNumber=address) rsp = self.send_receive_modbus_packet(packet) return rsp
class Wdb2Client(Base): def __init__(self, name, ip, port=17185, timeout=2, mem_buff_size=300): """ :param name: Name of this targets :param ip: VxWorks ip :param port: WDB port (default: 17185) :param timeout: timeout of socket (default: 2) :param mem_buff_size: Mem buff size for memory read or write (default: 300) """ super(Wdb2Client, self).__init__(name=name) self._ip = ip self._port = port self._timeout = timeout self._connection = None self._target_info = {} self._seq = None self._mem_buff_size = mem_buff_size self.mem_dump = '' self.target_info = {} def connect(self): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.connect((self._ip, self._port)) sock.settimeout(self._timeout) self._connection = StreamSocket(sock, Raw) self._seq = 1 connect_packet = RPCReq() / WdbConnectReq() connect_packet[RPCReq].Procedure = 0x7a connect_packet[RPCReq].Seq = self._seq self.send_receive_wdb_packet(connect_packet) def reconnect(self): self.connect() def _get_seq(self): if self._seq >= 65535: self.connect() return self._seq else: return self._seq def _fix_seq(self, payload): if self._seq > 65535: self._seq = 1 try: payload.Seq = self._seq self._seq += 1 return payload except Exception as err: self.logger.error(err) return payload def send_packet(self, packet): if self._connection: try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_packet(self, packet): if self._connection: try: rsp = self._connection.sr1(packet, timeout=self._timeout) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_packet(self): if self._connection: try: rsp = self._connection.recv() return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") def send_wdb_packet(self, packet): if self._connection: packet = self._fix_seq(packet) try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_wdb_packet(self, packet): if self._connection: packet = self._fix_seq(packet) try: rsp = self._connection.sr1(packet, timeout=self._timeout) if rsp: rsp = RPCRsp(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_wdb_packet(self): if self._connection: try: rsp = self._connection.recv() if rsp: rsp = RPCRsp(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") def _unpack_info(self, info): self.target_info = {} info = xdrlib.Unpacker(info) self.target_info["Target_Type"] = info.unpack_string() # 'VxWorks\x00' self.target_info["Vx_Version"] = info.unpack_string() # '6.6\x00' self.target_info["Unknow1"] = info.unpack_uint() # 80 self.target_info["Unknow2"] = info.unpack_uint() # 86 self.target_info["CPU_Type"] = info.unpack_string() # '86\x00' self.target_info["compiler"] = info.unpack_string() # '86\x00' self.target_info["Unknow3"] = info.unpack_uint() # 86 self.target_info["Unknow4"] = info.unpack_uint() # 86 self.target_info["Unknow5"] = info.unpack_uint() # 86 self.target_info["Unknow6"] = info.unpack_uint() # 86 self.target_info["Unknow7"] = info.unpack_uint() # 86 self.target_info["CPU_Model"] = info.unpack_string() # '86\x00' self.target_info["Unknow8"] = info.unpack_string() # '86\x00' self.target_info["Unknow9"] = info.unpack_uint() # 86 self.target_info["Memory_Size"] = info.unpack_uint() # 86 self.target_info["Unknow10"] = info.unpack_uint() self.target_info["Unknow11"] = info.unpack_uint() self.target_info["Unknow12"] = info.unpack_uint() self.target_info["Unknow13"] = info.unpack_uint() self.target_info["Unknow14"] = info.unpack_uint() self.target_info["Unknow15"] = info.unpack_uint() return self.target_info def get_target_info(self): info_packet = RPCReq() / WdbGetInfoReq() info_packet[RPCReq].Procedure = 0x7b rsp = self.send_receive_wdb_packet(info_packet) info = rsp.load[4:] target_info = self._unpack_info(info) return target_info def _write_memory(self, address, data): """ :param address: offset of target memory :param data: data need to write to target :return: target response packet """ address = int(address) pkt = RPCReq() / WdbMemWriteReq(Offset=address, Buff=data) pkt[RPCReq].Procedure = 0xb print('start writing memory at 0x', struct.pack("!I", address).encode('hex')) return self.send_receive_wdb_packet(pkt) def write_target_memory(self, address, data): """ :param address: offset of memory :param data: data need to write :return: None """ address = int(address) if len(data) < 4: print("data length can't less than 4 byte") else: if len(data) % 4 != 0: data += '\x00' * (len(data) % 4) for i in range(0, len(data), 4): buff = data[i:i + 4] res = self._write_memory(address, buff) if res is None: print("can't write memory at 0x", struct.pack("!I", address).encode('hex')) return address += 4 def _read_memory(self, address, length): """ :param address: offset of target memory :param length: length of memory to be read :return: Memory Data """ address = int(address) pkt = RPCReq() / WdbMemReadReq(Offset=address, Length=length) pkt[RPCReq].Procedure = 0xa rsp = self.send_receive_wdb_packet(pkt) if rsp.WdbErrorState != 0x0: self.logger.error("Can't read memory from %s with length %s" % (address, length)) self.logger.error("Error Code %s" % rsp.WdbErrorState) return None buff_length = struct.unpack('!i', rsp.load[12:16])[0] buff = rsp.load[16:16 + buff_length] return buff def read_target_memory(self, address, length): self.mem_dump = '' address = int(address) if length < self._mem_buff_size: temp_length = length else: temp_length = self._mem_buff_size for offset in range(address, address + length, temp_length): self.logger.info('Dumping memory at %s / %s' % (offset, address + length)) self.mem_dump += self._read_memory(offset, temp_length) return self.mem_dump
class Exploit(Base): __info__ = { 'name': 'clients/cip', 'display_name': 'CIP Client', 'description': '', 'authors': [ 'D0ubl3G <d0ubl3g[at]protonmail.com>', ], 'references': [ '', ], 'devices': [ 'Multi', ], } target = Option('', 'Target IP address') port = Option(44818, 'Target port') timeout = Option(2, 'Connection timeout') def __init__(self, name=""): if name is not None: super(Exploit, self).__init__(name) else: super(Exploit, self).__init__('CipClient') self._connection = None self._target_info = {} self._session = 0x0 self.target_info = {} def run(self): self.connect() def connect(self): sock = socket.socket() sock.settimeout(self.timeout) sock.connect((self.target, self.port)) self._connection = StreamSocket(sock, Raw) packet_1 = ENIPHeader(Command=0x65) / RegisterSession() rsp_1 = self.send_receive_cip_packet(packet_1) try: if rsp_1.haslayer(ENIPHeader): self._session = rsp_1.Session except Exception as err: self.logger.error(err) return def reconnect(self): self.connect() def _fix_session(self, packet): try: packet.Session = self._session return packet except Exception as err: self.logger.error(err) return packet def send_packet(self, packet): if self._connection: try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_packet(self, packet): if self._connection: try: rsp = self._connection.sr1(packet, timeout=self.timeout) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_packet(self): if self._connection: try: rsp = self._connection.recv() return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") def send_cip_packet(self, packet): if self._connection: packet = self._fix_session(packet) try: self._connection.send(packet) except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def send_receive_cip_packet(self, packet): if self._connection: packet = self._fix_session(packet) # packet.show2() try: rsp = self._connection.sr1(packet, timeout=self.timeout) if rsp: rsp = ENIPHeader(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before send packet!") def receive_cip_packet(self): if self._connection: try: rsp = self._connection.recv() if rsp: rsp = ENIPHeader(str(rsp)) return rsp except Exception as err: self.logger.error(err) return None else: self.logger.error("Please create connect before receive packet!") def get_target_info(self, port=0x01, port_segment=0x00): product_name = '' device_type = '' vendor = '' revision = '' serial_number = '' info_packet = ENIPHeader(Command=0x6f) / CIPCommandSpecificData() / \ CIPHeader(Type="Request", Service=0x52, ) / \ CIPConnectionManager() info_packet[CIPCommandSpecificData].Items = [ NullAddressItem(), UnconnectedDataItem() ] info_packet[CIPHeader].RequestPath = [ CIPRequestPath(PathSegmentType=1, InstanceSegment=0x06), CIPRequestPath(PathSegmentType=1, LogicalSegmentType=0x01, InstanceSegment=0x01) ] info_packet[CIPConnectionManager].MessageRequest = CIPHeader( Type="Request", Service=0x01, RequestPath=[ CIPRequestPath(PathSegmentType=1, InstanceSegment=0x01), CIPRequestPath(PathSegmentType=1, LogicalSegmentType=0x01, InstanceSegment=0x01) ]) info_packet[CIPRoutePath].Port = port info_packet[CIPRoutePath].PortSegment = port_segment rsp = self.send_receive_cip_packet(info_packet) if rsp.haslayer(CIPHeader): if rsp[CIPHeader].GeneralStatus == 0x00: try: if rsp.haslayer(GetAttributesAll): product_name = rsp[GetAttributesAll].ProductName device_type = rsp[GetAttributesAll].DeviceType if device_type in DEVICE_TYPES.keys(): device_type = DEVICE_TYPES[device_type] else: device_type = "%s (%s)" % (product_name, hex(device_type)) vendor = rsp[GetAttributesAll].VendorID if vendor in VENDOR_IDS.keys(): vendor = VENDOR_IDS[vendor] else: vendor = "%s (%s)" % (product_name, hex(vendor)) revision = str(rsp[GetAttributesAll].MajorRevision) + '.' \ + str(rsp[GetAttributesAll].MinorRevision) serial_number = hex(rsp[GetAttributesAll].SerialNumber) except Exception as err: pass else: self.logger.warning( "Got Error Code:%s when get target info with port:%s and port_segment:%s" % (port, port_segment, rsp[CIPHeader].GeneralStatus)) return product_name, device_type, vendor, revision, serial_number