def setup(self): self.log("Connecting to CIP... ") self.cip = CIP(Config['cumulus']['url'], Config['cumulus']['port'], Config['cumulus']['user'], Config['cumulus']['password'], Config['cumulus']['location']) self.cip.load_layout(Config['cumulus']['layout'], Config['cumulus']['layout']) self.log("OK.")
def fuzz_classid(client, instanceid): status = {} for classid in range(0x64, 0xc8): data = "\x01\x00" # Symbol Instanc Addressing cippkt = CIP(service=0x4c, path=CIP_Path.make(class_id=classid, instance_id=instanceid, word_size=3)) / data print("class id: " + str(hex(classid)) + " | instance id: " + str(hex(instanceid)), end='\r') try: client.send_unit_cip(cippkt) except: pass # Show the response only if it does not contain data resppkt = client.recv_enippkt() if resppkt is not None: stat = str(resppkt[CIP].status) if stat in status: status.get(stat).append(str(hex(classid))) else: status[stat] = [str(hex(classid))] # print all status for key, value in status.items(): print("Status: " + key) for v in value: print(" " + v)
def read_full_tag(self, class_id, instance_id, total_size): """Read the content of a tag which can be quite big""" data_chunks = [] offset = 0 remaining_size = total_size while remaining_size > 0: cippkt = CIP(service=0x4c, path=CIP_Path.make(class_id=class_id, instance_id=instance_id)) cippkt /= CIP_ReqReadOtherTag(start=offset, length=remaining_size) self.send_rr_cm_cip(cippkt) if self.sock is None: return resppkt = self.recv_enippkt() cipstatus = resppkt[CIP].status[0].status received_data = str(resppkt[CIP].payload) if cipstatus == 0: # Success assert len(received_data) == remaining_size elif cipstatus == 6 and len(received_data) > 0: # Partial response (size too big) pass else: logger.error("Error in Read Tag response: %r", resppkt[CIP].status[0]) return # Remember the chunk and continue data_chunks.append(received_data) offset += len(received_data) remaining_size -= len(received_data) return b''.join(data_chunks)
def main(): # Connect to PLC client = plc.PLCClient('192.168.9.227') if not client.connected: sys.exit(1) # Creating Connections Through the Connection Manager Object if not client.forward_open(): sys.exit(1) # Get_Instance_Attribute_List # Set initial instance to 0x0 instanceid = 0x0 # status status = '' # Number of attributes to retrieve (2 bytes) + Attribute 1 - Symbol Name (2 bytes) + Attribute 2 - Symbol Type (2 bytes) data = "\x02\x00\x01\x00\x02\x00" while ("Success" not in status): cippkt = CIP(service=0x55, path=CIP_Path.make(class_id=0x6b, instance_id=instanceid, word_size=3)) / data client.send_unit_cip(cippkt) resppkt = client.recv_enippkt() status = str(resppkt[CIP].status) instanceid = parse_attributes(resppkt[CIP].load) + 1 client.forward_close()
def fuzz_timeout(client): for i in range(0xff): # i = 0x1 print("Fuzzing timeout: " + str(hex(i))) # Construct an enip packet from raw enippkt = ENIP_TCP(session=client.session_id) # Symbol Instanc Addressing cippkt = CIP(service=0x4c, path=CIP_Path.make(class_id=0x6b, instance_id=0x227)) # interface handle, timeout, count, items enippkt /= ENIP_SendUnitData( timeout=i, items=[ # type_id, length, connection id ENIP_SendUnitData_Item() / ENIP_ConnectionAddress(connection_id=client.enip_connid), # type_id, length, sequence ENIP_SendUnitData_Item() / ENIP_ConnectionPacket(sequence=client.sequence) / cippkt ]) client.sequence += 1 if client.sock is not None: client.sock.send(str(enippkt)) # Show the response only if it does not contain data resppkt = client.recv_enippkt() if resppkt is not None: print("Status: " + str(resppkt[ENIP_TCP].status)) print("TImeout: " + str(hex(resppkt[ENIP_SendUnitData].timeout)))
def fuzz_instanceid(client, classid): status = {} data = "\x01\x00" for instanceid in range(0xffff): # Symbol Instanc Addressing cippkt = CIP(service=0x4c, path=CIP_Path.make(class_id=classid, instance_id=instanceid, word_size=3)) / data # print("class id: " + str(hex(classid)) + " | instance id: " + str(hex(instanceid)), end='\r') try: client.send_unit_cip(cippkt) except: pass # Receive the response and show it resppkt = client.recv_enippkt() # print("class id: " + str(hex(classid)) + " | instance id: " + str(hex(instanceid)) + " Status: " + str(resppkt[CIP].status)) if resppkt is not None: stat = str(resppkt[CIP].status) if stat in status: status.get(stat).append(str(hex(instanceid))) else: status[stat] = [str(hex(instanceid))] # print all status for key, value in status.items(): print("Status: " + key) for v in value: print(" " + v)
def simple_read_tag(client, pathsize, classid, instanceid): # Symbol Instanc Addressing data = "\x01\x00" cippkt = CIP(service=0x4c, path=CIP_Path.make(class_id=classid, instance_id=instanceid, word_size=pathsize)) / data # Construct an enip packet from raw enippkt = ENIP_TCP(session=client.session_id) # interface handle, timeout, count, items enippkt /= ENIP_SendUnitData( interface_handle=0x0, items=[ # type_id, length, connection id ENIP_SendUnitData_Item() / ENIP_ConnectionAddress(connection_id=client.enip_connid), # type_id, length, sequence ENIP_SendUnitData_Item() / ENIP_ConnectionPacket(sequence=client.sequence) / cippkt ]) client.sequence += 1 if client.sock is not None: client.sock.send(str(enippkt)) enippkt.show() # Show the response only if it does not contain data resppkt = client.recv_enippkt() if resppkt is not None: print("Status: " + str(resppkt[CIP].status))
def forward_open(self): """Send a forward open request""" cippkt = CIP(service=0x54, path=CIP_Path(wordsize=2, path=b'\x20\x06\x24\x01')) cippkt /= CIP_ReqForwardOpen( path_wordsize=10, path= b"\x01\x00\x34\x04\x00\x00\x00\x00\x00\x00\x00\x00\x91\x06\x54\x6f\x43\x4e\x43\x34" ) #FOR CREATING PACKETS #Attempt to change Ethernet protocol #ToCell path (9):\x34\x04\x01\x00\x0e\x00\xb2\x00\x00\x01\x91\x06\x54\x6f\x43\x65\x6c\x6c #associated error: 0x0115 #To CNC4 path (10): \x01\x00\x34\x04\x00\x00\x00\x00\x00\x00\x00\x00\x91\x06\x54\x6f\x43\x4e\x43\x34 #associated error: 0x031e #j path (8): \x01\x01\x34\x04\x00\x00\x00\x00\x00\x00\x00\x00\x91\x01\x6a\x00 #associated error: None!!!!!! #print(cippkt['TCP']) #End of attempt self.send_rr_cip(cippkt) resppkt = self.recv_enippkt() if self.sock is None: return cippkt = resppkt[CIP] if cippkt.status[0].status != 0: logger.error("Failed to Forward Open CIP connection: %r", cippkt.status[0]) return False assert isinstance(cippkt.payload, CIP_RespForwardOpen) self.enip_connid = cippkt.payload.OT_network_connection_id return True
def get_list_of_instances(self, class_id): """Use CIP service 0x4b to get a list of instances of the specified class""" start_instance = 0 inst_list = [] while True: cippkt = CIP(service=0x4b, path=CIP_Path.make(class_id=class_id, instance_id=start_instance)) self.send_rr_cm_cip(cippkt) if self.sock is None: return resppkt = self.recv_enippkt() # Decode a list of 32-bit integers data = str(resppkt[CIP].payload) for i in range(0, len(data), 4): inst_list.append(struct.unpack('<I', data[i:i + 4])[0]) cipstatus = resppkt[CIP].status[0].status if cipstatus == 0: return inst_list elif cipstatus == 6: # Partial response, query again from the next instance start_instance = inst_list[-1] + 1 else: logger.error("Error in Get Instance List response: %r", resppkt[CIP].status[0]) return
def setup(self): self.log("Connecting to CIP and loading layout...") self.cip = CIP(Config['cumulus']['url'], Config['cumulus']['port'], Config['cumulus']['user'], Config['cumulus']['password'], Config['cumulus']['location']) self.cip.load_layout(Config['cumulus']['layout'], Config['cumulus']['layout']) self.log("OK.") self.log("Creating index of transcribed...") self.transcribed = {} for erindring in self.cip.searchall( Config['cumulus']['catalog'], view=Config['cumulus']['catalog'], querystring= "Offentlig == true && 'Related Master Assets' * && Samlingsnavn == 'Erindring'" ): if "Erindringsnummer" in erindring: self.transcribed[erindring['Erindringsnummer']] = erindring self.log(f"OK. Created index of {len(self.transcribed)} transcribed.")
def forward_close(self): """Send a forward close request""" cippkt = CIP(service=0x4e, path=CIP_Path(wordsize=2, path=b'\x20\x06\x24\x01')) cippkt /= CIP_ReqForwardClose(path_wordsize=3, path=b"\x01\x00\x20\x02\x24\x01") self.send_rr_cip(cippkt) if self.sock is None: return resppkt = self.recv_enippkt() cippkt = resppkt[CIP] if cippkt.status[0].status != 0: logger.error("Failed to Forward Close CIP connection: %r", cippkt.status[0]) return False return True
def set_attribute(self, class_id, instance, attr, value): """Set the value of attribute class/instance/attr""" path = CIP_Path.make(class_id=class_id, instance_id=instance) # User CIP service 4: Set_Attribute_List cippkt = CIP(service=4, path=path) / scapy_all.Raw(load=struct.pack('<HH', 1, attr) + value) self.send_rr_cm_cip(cippkt) if self.sock is None: return resppkt = self.recv_enippkt() cippkt = resppkt[CIP] if cippkt.status[0].status != 0: logger.error("CIP set attribute error: %r", cippkt.status[0]) return False return True
def forward_open(self): """Send a forward open request""" cippkt = CIP(service=0x54, path=CIP_Path(wordsize=2, path=b'\x20\x06\x24\x01')) cippkt /= CIP_ReqForwardOpen(path_wordsize=3, path=b"\x01\x00\x20\x02\x24\x01") self.send_rr_cip(cippkt) resppkt = self.recv_enippkt() if self.sock is None: return cippkt = resppkt[CIP] if cippkt.status[0].status != 0: logger.error("Failed to Forward Open CIP connection: %r", cippkt.status[0]) return False assert isinstance(cippkt.payload, CIP_RespForwardOpen) self.enip_connid = cippkt.payload.OT_network_connection_id return True
def fuzz_pathsize(client, classid, instanceid): data = "\x01\x00" for pathsize in range(0xff): # Symbol Instanc Addressing cippkt = CIP(service=0x4c, path=CIP_Path.make(class_id=classid, instance_id=instanceid, word_size=pathsize)) / data try: client.send_unit_cip(cippkt) except: pass # Show the response only if it does not contain data resppkt = client.recv_enippkt() if resppkt is not None: print("Status: " + str(resppkt[CIP].status))
def get_attribute(self, class_id, instance, attr): """Get an attribute for the specified class/instance/attr path""" # Get_Attribute_Single does not seem to work properly # path = CIP_Path.make(class_id=class_id, instance_id=instance, attribute_id=attr) # cippkt = CIP(service=0x0e, path=path) # Get_Attribute_Single path = CIP_Path.make(class_id=class_id, instance_id=instance) cippkt = CIP(path=path) / CIP_ReqGetAttributeList(attrs=[attr]) self.send_rr_cm_cip(cippkt) if self.sock is None: return resppkt = self.recv_enippkt() cippkt = resppkt[CIP] if cippkt.status[0].status != 0: logger.error("CIP get attribute error: %r", cippkt.status[0]) return resp_getattrlist = str(cippkt.payload) assert resp_getattrlist[:2] == b'\x01\x00' # Attribute count must be 1 assert struct.unpack('<H', resp_getattrlist[2:4])[0] == attr # First attribute assert resp_getattrlist[4:6] == b'\x00\x00' # Status return resp_getattrlist[6:]
def scan_one(class_name, instance_id, attribute_id=None): success_service = set() class_id = CLASS_CODES[class_name] for service_id in CLASS_SERVICE_MAP[class_name]: plc_client = plc.PLCClient(PLC_HOST) if not plc_client.connected: logging.error(("Cannot connect to server")) sys.exit(1) # Make packet detail cippkt = CIP(service=service_id, path=CIP_Path.make(class_id=class_id, instance_id=instance_id, attribute_id=attribute_id)) # Send a CIP request plc_client.send_rr_cip(cippkt) # Receive the response resppkt = plc_client.recv_enippkt() #resppkt.show() try: enip_tcp_status = resppkt["ENIP_TCP"].status cip_tcp_status = resppkt["CIP_ResponseStatus"].status except: cip_tcp_status = None if enip_tcp_status == 0x0 and cip_tcp_status == 0x0: # SUCCESS success_service.add(service_id) logging.debug(("Class " + str(class_name) + " supports serives " + str(success_service))) return success_service
class_id = CLASS_CODES[class_name] for instance_id in range(INSTANCE_ID_RANGE[0], INSTANCE_ID_RANGE[1]): logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.DEBUG) # Connect to PLC client = plc.PLCClient(PLC_HOST) if not client.connected: sys.exit(1) print("Established session {}".format(client.session_id)) # Send a CIP ReadTag request cippkt = CIP(service=service_id, path=CIP_Path.make(class_id=int(class_id), instance_id=instance_id)) client.send_rr_cip(cippkt) # Receive the response and show it resppkt = client.recv_enippkt() enip_tcp_status = resppkt["ENIP_TCP"].status service_info = { "name": service_name + class_name + str(instance_id), "service_name": service_name, "class_name": class_name, "service_id": service_id, "class_id": class_id, "instance_id": instance_id }
def send_rr_cm_cip(self, cippkt): """Encapsulate the CIP packet into a ConnectionManager packet""" cipcm_msg = [cippkt] cippkt = CIP(path=CIP_Path.make(class_id=6, instance_id=1)) cippkt /= CIP_ReqConnectionManager(message=cipcm_msg) self.send_rr_cip(cippkt)
import logging import sys from cip import CIP, CIP_Path import plc logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.DEBUG) # Connect to PLC client = plc.PLCClient('192.168.6.70') if not client.connected: sys.exit(1) print("Established session {}".format(client.session_id)) if not client.forward_open(): sys.exit(1) # Send a CIP ReadTag request cippkt = CIP(service=0x4c, path=CIP_Path.make_str("Raymond_INT")) client.send_unit_cip(cippkt) # Receive the response and show it resppkt = client.recv_enippkt() resppkt[CIP].show() # Close the connection client.forward_close()
class ErindringerIndexer(IndexerBase): def __init__(self): super().__init__() self.progress_threshold = 0.1 self.progress_threshold_next = self.progress_threshold self.commit_threshold = 100 def collection_id(self): return 18 def collection_info(self): return "Erindringer" def setup(self): self.log("Connecting to CIP and loading layout...") self.cip = CIP(Config['cumulus']['url'], Config['cumulus']['port'], Config['cumulus']['user'], Config['cumulus']['password'], Config['cumulus']['location']) self.cip.load_layout(Config['cumulus']['layout'], Config['cumulus']['layout']) self.log("OK.") self.log("Creating index of transcribed...") self.transcribed = {} for erindring in self.cip.searchall( Config['cumulus']['catalog'], view=Config['cumulus']['catalog'], querystring= "Offentlig == true && 'Related Master Assets' * && Samlingsnavn == 'Erindring'" ): if "Erindringsnummer" in erindring: self.transcribed[erindring['Erindringsnummer']] = erindring self.log(f"OK. Created index of {len(self.transcribed)} transcribed.") def get_total(self): search_result = self.cip.search( Config['cumulus']['catalog'], view=Config['cumulus']['catalog'], querystring= "Offentlig == true && 'Related Master Assets' !* && Samlingsnavn == 'Erindring'", maxreturned=1) return search_result['totalcount'] def get_entries(self): return self.cip.searchall( Config['cumulus']['catalog'], view=Config['cumulus']['catalog'], querystring= "Offentlig == true && 'Related Master Assets' !* && Samlingsnavn == 'Erindring'", chunk=50) def handle_entry(self, erindring): jsonObj = {} jsonObj['id'] = "%d-%d" % (self.collection_id(), erindring['ID']) jsonObj['org_id'] = "%d" % erindring['ID'] jsonObj['collection_id'] = self.collection_id() if "Fornavne" in erindring: jsonObj['firstnames'] = erindring['Fornavne'] elif "Navn" in erindring and len(erindring['Navn'].split(',')) > 1: jsonObj['firstnames'] = erindring['Navn'].split(',')[1].strip() if "Efternavn" in erindring: jsonObj['lastname'] = erindring['Efternavn'] elif "Navn" in erindring and len(erindring['Navn'].split(',')) > 0: jsonObj['lastname'] = erindring['Navn'].split(',')[0].strip() if "Stilling hovedperson" in erindring: jsonObj['position'] = erindring['Stilling hovedperson'] if u"Stilling forældre" in erindring: jsonObj['position_parent'] = erindring[u'Stilling forældre'] if u"Stilling ægtefælle" in erindring: jsonObj['position_spouse'] = erindring[u'Stilling ægtefælle'] if "Periode" in erindring: jsonObj['period'] = erindring['Periode'] if u"Fødselsår" in erindring: jsonObj[ 'yearOfBirth'] = erindring[u'Fødselsår'].year if isinstance( erindring[u'Fødselsår'], datetime.date) else erindring[u'Fødselsår'] if "Description" in erindring: jsonObj['description'] = erindring['Description'] if "Erindringsnummer" in erindring: jsonObj['erindring_number'] = erindring['Erindringsnummer'] if u"Indsamlingsår" in erindring: jsonObj['collectedYear'] = erindring[u'Indsamlingsår'] if "Omfang" in erindring: jsonObj['extent'] = erindring['Omfang'] if u"Håndskrevne/maskinskreven" in erindring: jsonObj['writeMethod'] = erindring[u'Håndskrevne/maskinskreven'] if "Document Name" in erindring: jsonObj['filename'] = erindring['Document Name'] if "Transkriberet" in erindring: jsonObj['transcribed'] = erindring['Transkriberet'] jsonObj[ 'transcribed_filename'] = erindring['Document Name'].replace( ".pdf", "_transcribed.pdf" ) if erindring['Transkriberet'] and erindring[ 'Document Name'] else None if "Civilstand" in erindring: jsonObj['civilstatus'] = erindring['Civilstand'] if "Keywords" in erindring: jsonObj['keywords'] = erindring['Keywords'].split(",") if u"Køn" in erindring: jsonObj['sex'] = erindring[u'Køn'] if "Erindringsnummer" in erindring and erindring[ "Erindringsnummer"] in self.transcribed: jsonObj['transcribed_id'] = self.transcribed[ erindring["Erindringsnummer"]]['ID'] jsonObj['containsPhotos'] = 'Foto' in erindring and erindring['Foto'] self.documents.append({ 'id': "%d-%d" % (self.collection_id(), erindring['ID']), 'task_id': -1, 'post_id': -1, 'entry_id': -1, 'user_id': -1, 'user_name': ' ', 'unit_id': -1, 'page_id': -1, 'jsonObj': json.dumps(jsonObj), 'collection_id': self.collection_id(), 'collection_info': self.collection_info(), 'firstnames': erindring['Fornavne'] if 'Fornavne' in erindring else (erindring['Navn'].split(',')[1].strip() if 'Navn' in erindring and len(erindring['Navn'].split(',')) > 1 else None), 'lastname': erindring['Efternavn'] if 'Efternavn' in erindring else (erindring['Navn'].split(',')[0].strip() if 'Navn' in erindring and len(erindring['Navn'].split(',')) > 0 else None), 'sex': erindring.get('Køn'), 'civilstatus': erindring.get('Civilstatus'), 'yearOfBirth': jsonObj.get('Fødselsår'), "erindring_position": erindring.get('Stilling hovedperson'), "erindring_parent_position": erindring.get(u'Stilling forældre'), "erindring_spouse_position": erindring.get(u'Stilling ægtefælle'), "erindring_handwritten_typed": erindring.get(u'Håndskrevne/maskinskreven'), "erindring_description": erindring.get('Description'), "erindring_number": erindring['Erindringsnummer'] if "Erindringsnummer" in erindring else None, "erindring_period": erindring.get('Periode'), "collected_year": erindring.get(u"Indsamlingsår"), "erindring_extent": erindring.get('Omfang'), "erindring_photos": 'Foto' in erindring and erindring['Foto'], "erindring_keywords": erindring['Keywords'].split(',') if 'Keywords' in erindring and erindring['Keywords'] is not None else None, "erindring_document_text": erindring.get('Document Text'), "erindring_transcribed": "Transkriberet" in erindring and erindring['Transkriberet'] }) if len(self.documents) >= 100: self.solr.add(self.documents, commit=True) self.documents = [] def wrapup(self): self.solr.add(self.documents, commit=True)
def send_rr_mr_cip(self, cippkt): """Encapsulate the CIP packet into a MultipleServicePacket to MessageRouter""" cipcm_msg = [cippkt] cippkt = CIP(path=CIP_Path(wordsize=2, path=b'\x20\x02\x24\x01')) cippkt /= CIP_MultipleServicePacket(packets=cipcm_msg) self.send_rr_cip(cippkt)
class EfterretningerIndexer(IndexerBase): def __init__(self): super().__init__() self.commit_threshold = 100 def collection_id(self): return 19 def collection_info(self): return "Politiets efterretninger" def setup(self): self.log("Connecting to CIP... ") self.cip = CIP(Config['cumulus']['url'], Config['cumulus']['port'], Config['cumulus']['user'], Config['cumulus']['password'], Config['cumulus']['location']) self.cip.load_layout(Config['cumulus']['layout'], Config['cumulus']['layout']) self.log("OK.") def get_total(self): result = self.cip.search( Config['cumulus']['catalog'], view=Config['cumulus']['catalog'], querystring= "Samlingsnavn == 'Politiets Efterretninger' && Offentlig == true", maxreturned=1) return result['totalcount'] def get_entries(self): return self.cip.searchall( Config['cumulus']['catalog'], view=Config['cumulus']['catalog'], querystring= "Samlingsnavn == 'Politiets Efterretninger' && Offentlig == true", chunk=50) def handle_entry(self, efterretning): jsonObj = {} jsonObj['id'] = "%d-%d" % (self.collection_id(), efterretning['ID']) jsonObj['org_id'] = "%d" % efterretning['ID'] jsonObj['collection_id'] = self.collection_id() jsonObj['number'] = efterretning.get("Nummer") jsonObj['date'] = efterretning.get(u"Indsamlingsår").isoformat( ) if u"Indsamlingsår" in efterretning else None jsonObj['fileName'] = efterretning.get("Record Name") jsonObj['efterretning_type'] = efterretning.get(u"Description") self.documents.append({ 'id': "%d-%d" % (self.collection_id(), efterretning['ID']), 'task_id': -1, 'post_id': -1, 'entry_id': -1, 'user_id': -1, 'user_name': ' ', 'unit_id': -1, 'page_id': -1, 'jsonObj': json.dumps(jsonObj), 'collection_id': self.collection_id(), 'collection_info': self.collection_info(), 'collected_year': efterretning.get(u"Indsamlingsår").year if u"Indsamlingsår" in efterretning else None, 'efterretning_number': efterretning.get("Nummer"), 'efterretning_date': efterretning.get(u"Indsamlingsår"), 'efterretning_fileName': efterretning.get("Record Name"), 'efterretning_type': efterretning.get(u"Description"), 'erindring_document_text': efterretning.get('Document Text') })