├── README.md └── cipscan.py /README.md: -------------------------------------------------------------------------------- 1 | # SCADA-CIP-Discovery 2 | Common Industrial Protocol based device scanner over the internet 3 | This program needs more refinement. The response packets are not displayed as it should in a refined manner. 4 | Use wireshark when running this script with the filter set to enip to view the response data for analysis 5 | Run using "python cipscan.py 127.0.0.0/24" 6 | A usual response packet will contain information like this 7 | 8 | Vendor ID: Rockwell Automation/Allen-Bradley (0x0001) 9 | Device Type: Programmable Logic Controller (14) 10 | Product Code: XX 11 | Revision: 2.11 12 | Status: 0x0004 13 | Serial Number: 0xXXXXXXdX 14 | Product Name Length: XX 15 | Product Name: XXXX-LXXBXB B/XX.XX 16 | State: 0x00 17 | 18 | In addition to this the private IP addresses of the system will also be included like 192.168.0.17 19 | -------------------------------------------------------------------------------- /cipscan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | File: cipscan.py 4 | Desc: Common Industrial Protocol Scanner UDP 5 | Version: 1.0 6 | Copyright (c) 2016 Ayushman Dutta 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation version either version 3 of the License, 10 | or (at your option) any later version. 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | 19 | import socket 20 | import struct 21 | import optparse 22 | from IPy import IP 23 | import sys 24 | from multiprocessing import Process,Queue 25 | class CipScan(Process): 26 | 27 | def __init__(self,iprange,options): 28 | Process.__init__(self) 29 | self.iprange=iprange 30 | self.options=options 31 | def run(self): 32 | for ip in self.iprange: 33 | try: 34 | s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 35 | #s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # For TCP based queries 36 | s.settimeout(float(self.options.timeout)/float(100)) 37 | msg = str(ip)+":"+str(self.options.port) 38 | print("Scanning"+" "+msg+"\n") 39 | conn=s.connect((str(ip),self.options.port)) 40 | packet=struct.pack('24B',0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) 41 | #packet=struct.pack('73B',0x70, 0x00, 0x31, 0x00, 0xc9, 0x74, 0xb8, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xa1, 0x00, 0x04, 0x00, 0x0d, 0x00, 0xfe, 0x80, 0xb1, 0x00, 0x1d, 0x00, 0xf9, 0x39, 0xcb, 0x00, 0x00, 0x00, 0x07, 0x4d, 0x00, 0x04, 0x02, 0x5c, 0x0b, 0x4f, 0x00, 0xce, 0xf0, 0x00, 0x07, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff) #PCCC communication TCP based 42 | except socket.error: 43 | msg="Failed to Connect\n" 44 | print(msg+"\n") 45 | s.close() 46 | break 47 | try: 48 | s.send(packet) 49 | print('Sent'+' '+packet) 50 | except socket.error: 51 | msg="Failed to Send\n" 52 | print(msg) 53 | s.close() 54 | break 55 | try: 56 | recv=s.recvfrom(1024) 57 | print(recv) 58 | except socket.error: 59 | msg="Failed to Receive\n" 60 | print(msg+"\n") 61 | s.close() 62 | #break 63 | s.close() 64 | print("Scan has completed"+"\n") 65 | 66 | 67 | 68 | def main(): 69 | p = optparse.OptionParser( description=' Finds CIP devices in IP range and determines Vendor Specific Information along with Internal private IP.\nOutputs in ip:port sid format.', 70 | prog='CipScan', 71 | version='CIP Scan 1.0', 72 | usage = "usage: %prog [options] IPRange") 73 | p.add_option('--port', '-p', type='int', dest="port", default=44818, help='CIP port DEFAULT:44818') 74 | p.add_option('--timeout', '-t', type='int', dest="timeout", default=500, help='socket timeout (mills) DEFAULT:500') 75 | options, arguments = p.parse_args() 76 | if len(arguments) == 1: 77 | print("Starting Common Industrial Protocol Scan"+"\n") 78 | i="" 79 | i=arguments[0] 80 | iprange=IP(i) 81 | q = Queue() 82 | for ip in iprange: 83 | print("Starting Multithreading"+"\n") 84 | p = CipScan(ip,options).start() 85 | q.put(p,False) 86 | else: 87 | p.print_help() 88 | if __name__ == '__main__': 89 | try: 90 | main() 91 | except KeyboardInterrupt: 92 | print "Scan canceled by user." 93 | print "Thank you for using CIP Scan" 94 | except : 95 | sys.exit() 96 | --------------------------------------------------------------------------------