本文主要是利用scapy包編寫了一個簡易掃描工具,支持ARP、ICMP、TCP、UDP發現掃描,支持TCP SYN、UDP端口掃描,如下:
1 usage: python scan.py <-p ping掃描類型> <-s 端口發現類型> [-t target] [--port ports] 2 3 簡單掃描工具,可以進行存活掃描及端口掃描. 4 存活掃描包括:ARP掃描、ICMP掃描、TCP掃描、UDP掃描. 5 端口掃描包括:TCP SYN掃描、TCP ACK掃描、TCP FIN掃描. 6 7 optional arguments: 8 -h, --help show this help message and exit 9 -v, --version show program's version number and exit 10 11 target group: 12 用於設置IP、PORT參數 13 14 --target TARGET target為IP或IP段,如192.168.1.1,192.168.1.x,或1 15 92.168.1.1-254 16 --port PORT port為待掃描的端口,如21,80,...或21-80 17 18 ping group: 19 用於開啟存活掃描相關選項 20 21 -p 開啟存活掃描 22 --ARP 啟動ARP掃描 23 --ICMP 啟動ICMP掃描 24 --TCP 啟動TCP掃描 25 --UDP 啟動UDP掃描 26 27 port scan group: 28 用於開啟端口掃描相關選項 29 30 -s 開啟端口掃描 31 --SYN 開啟SYN掃描 32 --ACK 開啟ACK掃描 33 --FIN 開啟FIN掃描 34 --UPORT 開啟UDP端口掃描 35 36 utils group: 37 用於開啟掃描過程中的一些實用選項 38 39 --timeout TIMEOUT 設置發包超時時間,默認0.5秒 40 --retry RETRY 設置發包重試次數,默認不重試 41 42 以上做為說明,祝好運!
一、發現掃描
1.首先進行ARP掃描
python scan.py -p --target 192.168.1.1-254 --ARP [+]IP: 192.168.1.1 => MAC: 14:75:90:xx:xx:xx [+]IP: 192.168.1.111 => MAC: c6:36:55:xx:xx:xx [+]總共耗時9.84091806412秒.
通過retry參數增加發包嘗試次數,如下:
1 python scan.py -p --target 192.168.1.1-254 --ARP --retry 2 2 [+]IP: 192.168.1.1 => MAC: 14:75:90:xx:xx:xx 3 [+]IP: 192.168.1.111 => MAC: c6:36:55:xx:xx:xx 4 [+]IP: 192.168.1.102 => MAC: 58:1f:28:xx:xx:xx 5 [+]IP: 192.168.1.114 => MAC: 6c:8d:c1:xx:xx:xx 6 [+]IP: 192.168.1.103 => MAC: 84:38:38:xx:xx:xx 7 [+]總共耗時20.429942131秒.
2.使用ICMP掃描,若沒有指定任何掃描類型參數,默認會啟用ICMP掃描,如下:
1 python scan.py -p --target 192.168.1.1-254 2 [+]沒有指定任何ping掃描方式,默認選擇ICMP掃描 3 [+]IP:主機192.168.1.1 echo-reply. 4 [+]IP:主機192.168.1.111 echo-reply. 5 [+]總共耗時10.7177450657秒.
通過timeout參數,設置較長的超時,可以防止網絡狀況不好造成的丟包,如下:
1 python scan.py -p --target 192.168.1.1-254 --timeout 2 2 [+]沒有指定任何ping掃描方式,默認選擇ICMP掃描 3 [+]IP:主機192.168.1.1 echo-reply. 4 [+]IP:主機192.168.1.111 echo-reply. 5 [+]IP:主機192.168.1.114 echo-reply. 6 [+]總共耗時10.7566649914秒.
3.使用TCP掃描
1 python scan.py -p --target 192.168.1.100-120 --TCP --timeout 1 2 [+]請稍等,時間較長! 3 [!]掃描... 192.168.1.100 4 [!]掃描... 192.168.1.101 5 [!]掃描... 192.168.1.102 6 [!]掃描... 192.168.1.103 7 [!]掃描... 192.168.1.104 8 [!]掃描... 192.168.1.105 9 [!]掃描... 192.168.1.106 10 [!]掃描... 192.168.1.107 11 [!]掃描... 192.168.1.108 12 [!]掃描... 192.168.1.109 13 [!]掃描... 192.168.1.110 14 [!]掃描... 192.168.1.111 15 [!]掃描... 192.168.1.112 16 [!]掃描... 192.168.1.113 17 [!]掃描... 192.168.1.114 18 [!]掃描... 192.168.1.115 19 [!]掃描... 192.168.1.116 20 [!]掃描... 192.168.1.117 21 [!]掃描... 192.168.1.118 22 [!]掃描... 192.168.1.119 23 [!]掃描... 192.168.1.120 24 [+]正在處理掃描信息. 25 26 27 ==================== 28 [+]主機 192.168.1.102 在線. 29 [+]主機 192.168.1.103 在線. 30 [+]主機 192.168.1.111 在線. 31 [+]主機 192.168.1.114 在線. 32 [+]總共耗時16.4359779358秒.
4.使用UDP掃描
1 python scan.py -p --target 192.168.1.100-120 --UDP --retry 3 2 [+]請稍等,時間較長! 3 [!]掃描... 192.168.1.100 4 [!]掃描... 192.168.1.101 5 [!]掃描... 192.168.1.102 6 [!]掃描... 192.168.1.103 7 [!]掃描... 192.168.1.104 8 [!]掃描... 192.168.1.105 9 [!]掃描... 192.168.1.106 10 [!]掃描... 192.168.1.107 11 [!]掃描... 192.168.1.108 12 [!]掃描... 192.168.1.109 13 [!]掃描... 192.168.1.110 14 [!]掃描... 192.168.1.111 15 [!]掃描... 192.168.1.112 16 [!]掃描... 192.168.1.113 17 [!]掃描... 192.168.1.114 18 [!]掃描... 192.168.1.115 19 [!]掃描... 192.168.1.116 20 [!]掃描... 192.168.1.117 21 [!]掃描... 192.168.1.118 22 [!]掃描... 192.168.1.119 23 [!]掃描... 192.168.1.120 24 [+]正在處理掃描信息. 25 26 27 ==================== 28 [+]主機 192.168.1.102 在線. 29 [+]主機 192.168.1.103 在線. 30 [+]主機 192.168.1.111 在線. 31 [+]主機 192.168.1.114 在線. 32 [+]總共耗時33.5198891163秒.
二、端口掃描
1、TCP SYN端口掃描,不設置端口參數,則默認掃描1-1024端口
1 python scan.py --target 192.168.1.110-115 -s --SYN 2 [+]沒有指定任何掃描端口,默認掃描1-1024 3 [!]掃描... 192.168.1.110 4 [!]掃描... 192.168.1.111 5 [!]掃描... 192.168.1.112 6 [!]掃描... 192.168.1.113 7 [!]掃描... 192.168.1.114 8 [!]掃描... 192.168.1.115 9 [+]正在處理掃描信息. 10 11 12 ==================== 13 [+]主機 192.168.1.111 開放的TCP端口有:[80] 14 [+]總共耗時165.125555992秒.
掃描指定端口:
1 python scan.py --target 192.168.1.1-254 -s --SYN --port 80 --timeout 1 2 [!]掃描... 192.168.1.1 3 [!]掃描... 192.168.1.2 4 [!]掃描... 192.168.1.3 5 [!]掃描... 192.168.1.4 6 ... 7 [!]掃描... 192.168.1.253 8 [!]掃描... 192.168.1.254 9 [+]正在處理掃描信息. 10 11 12 ==================== 13 [+]主機 192.168.1.111 開放的TCP端口有:[80] 14 [+]主機 192.168.1.1 開放的TCP端口有:[80] 15 [+]總共耗時9.72222185135秒.
2、掃描UDP端口
1 python scan.py --target 192.168.1.1 -s --UPORT --timeout 1 2 [+]沒有指定任何掃描端口,默認掃描1-1024 3 [!]掃描... 192.168.1.1 4 [+]正在處理掃描信息. 5 6 7 ==================== 8 [+]主機 192.168.1.1 開放的UDP端口有:[520] 9 [+]總共耗時27.4742250443秒.
也可同時進行發現掃描與端口掃描,如下:
1 python scan.py --target 192.168.1.1-254 -p --ARP -s --SYN --port 80 --timeout 1 --retry 2 2 [+]IP: 192.168.1.1 => MAC: 14:75:90:xx:xx:xx 3 [+]IP: 192.168.1.102 => MAC: 58:1f:28:xx:xx:xx 4 [+]IP: 192.168.1.114 => MAC: 6c:8d:c1:xx:xx:xx 5 [+]IP: 192.168.1.103 => MAC: 84:38:38:xx:xx:xx 6 [+]IP: 192.168.1.101 => MAC: 5c:f7:e6:xx:xx:xx 7 [!]掃描... 192.168.1.1 8 [!]掃描... 192.168.1.2 9 ... 10 [!]掃描... 192.168.1.253 11 [!]掃描... 192.168.1.254 12 [+]正在處理掃描信息. 13 14 15 ==================== 16 [+]主機 192.168.1.1 開放的TCP端口有:[80] 17 [+]主機 192.168.1.111 開放的TCP端口有:[80] 18 [+]總共耗時45.2775988579秒.
OK,最后附上源碼:
1 import argparse 2 import re 3 import time 4 import threading 5 from scapy.all import * 6 7 import logging 8 logging.getLogger('scapy.runtime').setLevel(logging.ERROR) 9 10 11 class Discovery_Scan(object): 12 ''' 13 說明:用於發現掃描 14 ''' 15 16 def __init__(self,args,timeout=0.5,retry=0): 17 self.targets = parse_target(args) 18 self.timeout = timeout 19 self.retry = retry 20 21 def arp_scan(self,pdst): 22 #ARP發現掃描 23 ans = sr1(ARP(pdst=pdst),timeout=self.timeout,retry=self.retry,verbose=False) 24 if ans: 25 if ans[ARP].op == 2: #操作碼為2是is-at,是ARP響應 26 print '[+]IP: %s => MAC: %s' % (pdst,ans[ARP].hwsrc) 27 28 def icmp_scan(self,dst): 29 #ICMP發現掃描 30 ans = sr1(IP(dst=dst)/ICMP(),timeout=self.timeout,retry=self.retry,verbose=False) 31 if ans: 32 if ans[ICMP].type == 0: #ICMP type為0表示是ICMP echo-reply 33 print '[+]IP:主機%s echo-reply.' % dst 34 35 tcp_info = {} 36 def tcp_scan(self,dst,port): 37 #TCP SYN,發送TCP SYN包,有響應表示端口開放 38 ans,unans = sr(IP(dst=dst)/TCP(sport=RandShort(),dport=port,flags='S'), 39 timeout=self.timeout,retry=self.retry,verbose=False) 40 if ans.res: 41 if ans.res[0][0][IP].dst not in Discovery_Scan.tcp_info: 42 Discovery_Scan.tcp_info[ans.res[0][0][IP].dst] = True 43 44 udp_info = {} 45 def udp_scan(self,dst,port): 46 #UDP,發送UDP包,有響應表示端口開放 47 ans,uans = sr(IP(dst=dst)/UDP(sport=RandShort(),dport=port), 48 timeout=self.timeout,retry=self.retry,verbose=False) 49 if ans.res: 50 if ans.res[0][0][IP].dst not in Discovery_Scan.udp_info: 51 Discovery_Scan.udp_info[ans.res[0][0][IP].dst] = True 52 53 class Port_Scan(object): 54 ''' 55 說明:用於進行端口掃描,判斷端口是否開放 56 ''' 57 def __init__(self,args,timeout=0.5,retry=0): 58 self.targets = parse_target(args) 59 self.timeout = timeout 60 self.retry = retry 61 62 syn_port_dict = {} 63 def syn_port_scan(self,dst,port): 64 #TCP SYN端口掃描,若SYN包返回攜帶SYN、ACK(即TCP.flags=18)標志的包,則表明此端口打開。 65 ans,uans = sr(IP(dst=dst)/TCP(sport=RandShort(),dport=port,flags='S'), 66 timeout=self.timeout,retry=self.retry,verbose=False) 67 if ans: 68 first_respons_pkt = ans.res[0][1] 69 if first_respons_pkt[TCP] and first_respons_pkt[TCP].flags == 18: 70 if first_respons_pkt[IP].src not in Port_Scan.syn_port_dict: 71 Port_Scan.syn_port_dict[first_respons_pkt[IP].src] = [first_respons_pkt[TCP].sport] 72 else: 73 Port_Scan.syn_port_dict[first_respons_pkt[IP].src].append(first_respons_pkt[TCP].sport) 74 75 udp_port_dict = {} 76 def udp_port_scan(self,dst,port): 77 #UDP端口掃描,若UDP端口返回ICMP port-unreachable,則表示端口打開。(排除某些主機對任何UDP端口的探測都響應為ICMP port-unrechable) 78 ans,uans = sr(IP(dst=dst)/UDP(sport=RandShort(),dport=port), 79 timeout=self.timeout, retry=self.retry, verbose=False) 80 if ans.res and ans.res[0][1].haslayer(UDPerror): 81 first_respons_pkt = ans.res[0][1] 82 if first_respons_pkt[IP].src not in Port_Scan.udp_port_dict: 83 Port_Scan.udp_port_dict[first_respons_pkt[IP].src] = [first_respons_pkt[UDPerror].dport] 84 else: 85 Port_Scan.udp_port_dict[first_respons_pkt[IP].src].append(first_respons_pkt[UDPerror].dport) 86 87 def parse_opt(): 88 ''' 89 @說明:通過argparse模塊解析程序傳入的參數 90 @return:args 91 ''' 92 usage = 'python %(prog)s <-p ping掃描類型> <-s 端口發現類型> [-t target] [--port ports]' 93 description = '簡單掃描工具,可以進行存活掃描及端口掃描.\n' \ 94 '存活掃描包括:ARP掃描、ICMP掃描、TCP掃描、UDP掃描.\n' \ 95 '端口掃描包括:TCP SYN掃描、TCP ACK掃描、TCP FIN掃描.' 96 epilog = '以上做為說明,祝好運!' 97 parser = argparse.ArgumentParser(usage=usage,description=description,epilog=epilog,version='v1.0') 98 99 target_group = parser.add_argument_group('target group',description='用於設置IP、PORT參數') 100 target_group.add_argument('--target',dest='target',action='store', 101 help='target為IP或IP段,如192.168.1.1,192.168.1.x,或192.168.1.1-254') 102 target_group.add_argument('--port',dest='port',action='store', 103 help='port為待掃描的端口,如21,80,...或21-80') 104 105 ping_group = parser.add_argument_group('ping group',description='用於開啟存活掃描相關選項') 106 ping_group.add_argument('-p',dest='ping',action='store_true',help='開啟存活掃描') 107 ping_group.add_argument('--ARP',dest='ARP',action='store_true',help='啟動ARP掃描') 108 ping_group.add_argument('--ICMP',dest='ICMP',action='store_true',help='啟動ICMP掃描') 109 ping_group.add_argument('--TCP',dest='TCP',action='store_true',help='啟動TCP掃描') 110 ping_group.add_argument('--UDP',dest='UDP',action='store_true',help='啟動UDP掃描') 111 112 port_scan_group = parser.add_argument_group('port scan group',description='用於開啟端口掃描相關選項') 113 port_scan_group.add_argument('-s',dest='scan',action='store_true',help='開啟端口掃描') 114 port_scan_group.add_argument('--SYN',dest='SYN',action='store_true',help='開啟SYN掃描') 115 port_scan_group.add_argument('--ACK',dest='ACK',action='store_true',help='開啟ACK掃描') 116 port_scan_group.add_argument('--FIN',dest='FIN',action='store_true',help='開啟FIN掃描') 117 port_scan_group.add_argument('--UPORT', dest='UPORT', action='store_true', help='開啟UDP端口掃描') 118 119 utils_group = parser.add_argument_group('utils group',description='用於開啟掃描過程中的一些實用選項') 120 utils_group.add_argument('--timeout',dest='timeout',action='store',type=float,help='設置發包超時時間,默認0.5秒') 121 utils_group.add_argument('--retry',dest='retry',action='store',type=int,help='設置發包重試次數,默認不重試') 122 123 args = parser.parse_args() 124 if not args.ping and not args.scan: 125 print '[-]必須通過-p/-s選項開啟一種掃描' 126 print '\n' 127 parser.print_help() 128 exit(1) 129 elif not args.target: 130 print '[-]必須通過--target選項指定掃描的對象' 131 print '\n' 132 parser.print_help() 133 exit(1) 134 if args.ping: 135 if not args.ARP and not args.ICMP and not args.TCP and not args.UDP: 136 args.ICMP = True #若沒有指定任何ping掃描方式,則默認選擇ICMP掃描 137 print '[+]沒有指定任何ping掃描方式,默認選擇ICMP掃描' 138 if args.scan: 139 if not args.SYN and not args.ACK and not args.FIN and not args.UPORT: 140 args.SYN = True #若沒有指定任何端口掃描方式,則默認選擇SYN掃描 141 print '[+]沒有指定任何端口掃描方式,默認選擇SYN掃描' 142 if not args.port: 143 args.port = '1-1024' #若沒有指定任何掃描端口,則默認掃描1-1024 144 print '[+]沒有指定任何掃描端口,默認掃描1-1024' 145 146 return args 147 148 def parse_target(args): 149 ''' 150 @說明:用於解析如'192.168.1.1,192.168.1.x,...或192.168.1.1-254'格式的IP為單獨的IP,用於解析如'21,80,...或21-80'格式的端口為單獨的端口 151 @param: args,一個namespace對象 152 @return: (ip_list,port_list) 153 ''' 154 pattern1 = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$' 155 pattern2 = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}-\d{1,3}$' 156 pattern3 = r'\d{1,5}$' 157 pattern4 = r'\d{1,5}-\d{1,5}$' 158 ip_list,port_list = None,None 159 if args.target: 160 if re.search(pattern1,args.target): 161 ip_list = args.target.split(',') 162 elif re.match(pattern2,args.target): 163 _split = args.target.split('-') 164 first_ip = _split[0] 165 ip_split = first_ip.split('.') 166 ipdot4 = range(int(ip_split[3]), int(_split[1]) + 1) 167 ip_list = [ip_split[0] + '.' + ip_split[1] + '.' + ip_split[2] + '.' + str(p) for p in ipdot4] 168 else: 169 print '[-]target格式輸入有誤,請查看幫助!' 170 exit(1) 171 if args.port: 172 if re.match(pattern4,args.port): 173 _split = args.port.split('-') 174 port_list = range(int(_split[0]),int(_split[1])+1) 175 elif re.search(pattern3,args.port): 176 port_list = args.port.split(',') 177 else: 178 print '[-]port格式輸入有誤,請查看幫助!' 179 exit(1) 180 return ip_list,port_list 181 182 183 def main(): 184 ''' 185 @說明:掃描的主程序,首先根據條件創建Ping掃描或端口掃描對象,然后調用相關的掃描方法進行掃描。 186 ''' 187 args = parse_opt() 188 if args.ping: #是否啟動Ping掃描 189 if not args.timeout and not args.retry: 190 obj_ping = Discovery_Scan(args) 191 elif args.timeout and not args.retry: 192 obj_ping = Discovery_Scan(args,timeout=args.timeout) 193 elif not args.timeout and args.retry: 194 obj_ping = Discovery_Scan(args,retry=args.retry) 195 else: 196 obj_ping = Discovery_Scan(args,args.timeout,args.retry) 197 ip_list = obj_ping.targets[0] 198 if ip_list: 199 #ARP掃描 200 if args.ARP: 201 for pdst in ip_list: 202 t = threading.Thread(target=obj_ping.arp_scan,args=(pdst,)) 203 t.start() 204 while threading.activeCount() != 1: #避免線程還沒有運行完就提前輸出不全的結果 205 time.sleep(1) 206 #ICMP掃描 207 elif args.ICMP: 208 for dst in ip_list: 209 t = threading.Thread(target=obj_ping.icmp_scan,args=(dst,)) 210 t.start() 211 while threading.activeCount() != 1: #避免線程還沒有運行完就提前輸出不全的結果 212 time.sleep(1) 213 #TCP掃描 214 elif args.TCP: 215 port_list = [80,443,21,22,23,25,53,135,139,137,445,1158,1433,1521,3306,3389,7001,8000,8080,9090] 216 print '[+]請稍等,時間較長!' 217 for dst in ip_list: 218 print '[!]掃描...',dst 219 for port in port_list: 220 t = threading.Thread(target=obj_ping.tcp_scan,args=(dst,port)) 221 t.start() 222 223 print '[+]正在處理掃描信息.' 224 while threading.activeCount() != 1: #避免線程還沒有運行完就提前輸出不全的結果 225 time.sleep(1) 226 227 if not obj_ping.tcp_info: 228 print '\n' 229 print '=' * 20 230 print '[+]未發現在線主機.' 231 else: 232 print '\n' 233 print '=' * 20 234 for ip_a in sorted(obj_ping.tcp_info.keys()): 235 print '[+]主機 %s 在線.' % ip_a 236 #UDP掃描 237 elif args.UDP: 238 port_list = [7,9.13,15,37,53,67,68,69,135,137,138,139,445,520] 239 print '[+]請稍等,時間較長!' 240 for dst in ip_list: 241 print '[!]掃描...',dst 242 for port in port_list: 243 t = threading.Thread(target=obj_ping.udp_scan,args=(dst,port)) 244 t.start() 245 246 print '[+]正在處理掃描信息.' 247 while threading.activeCount() != 1: #避免線程還沒有運行完就提前輸出不全的結果 248 time.sleep(1) 249 250 if not obj_ping.udp_info: 251 print '\n' 252 print '=' * 20 253 print '[+]未發現在線主機.' 254 else: 255 print '\n' 256 print '=' * 20 257 for ip_a in sorted(obj_ping.udp_info.keys()): 258 print '[+]主機 %s 在線.' % ip_a 259 if args.scan: #是否啟動端口掃描 260 if not args.timeout and not args.retry: 261 obj_port = Port_Scan(args) 262 elif args.timeout and not args.retry: 263 obj_port = Port_Scan(args,timeout=args.timeout) 264 elif not args.timeout and args.retry: 265 obj_port = Port_Scan(args,retry=args.retry) 266 else: 267 obj_port = Port_Scan(args,args.timeout,args.retry) 268 269 ip_list,port_list = obj_port.targets 270 if ip_list and port_list: 271 if args.SYN: 272 for dst in ip_list: 273 print '[!]掃描...',dst 274 for port in port_list: 275 t = threading.Thread(target=obj_port.syn_port_scan,args=(dst,int(port))) 276 t.start() 277 278 print '[+]正在處理掃描信息.' 279 while threading.activeCount() != 1: #避免線程還沒有運行完就提前輸出不全的結果 280 time.sleep(1) 281 282 if not obj_port.syn_port_dict: 283 print '\n' 284 print '=' * 20 285 print '[+]未發現開放TCP端口.' 286 else: 287 print '\n' 288 print '=' * 20 289 for k,v in obj_port.syn_port_dict.items(): 290 print '[+]主機 %s 開放的TCP端口有:%s' % (k,str(v)) 291 elif args.ACK: 292 pass #基本不能使用 293 elif args.FIN: 294 pass #基本不能使用 295 elif args.UPORT: 296 for dst in ip_list: 297 print '[!]掃描...',dst 298 for port in port_list: 299 t = threading.Thread(target=obj_port.udp_port_scan,args=(dst,int(port))) 300 t.start() 301 302 print '[+]正在處理掃描信息.' 303 while threading.activeCount() != 1: #避免線程還沒有運行完就提前輸出不全的結果 304 time.sleep(1) 305 306 if not obj_port.udp_port_dict: 307 print '\n' 308 print '=' * 20 309 print '[+]未發現開放UDP端口.' 310 else: 311 print '\n' 312 print '=' * 20 313 for k,v in obj_port.udp_port_dict.items(): 314 print '[+]主機 %s 開放的UDP端口有:%s' % (k,str(v)) 315 316 if __name__ == '__main__': 317 try: 318 start_time = time.time() 319 main() 320 stop_time = time.time() 321 print '[+]總共耗時'+str(stop_time-start_time)+'秒.' 322 except Exception,e: 323 print '[-]執行出錯,具體錯誤見下面信息.' 324 print e