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 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 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 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 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 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 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 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
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)
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 }