本文主要是利用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