*本文原创作者:addadd,本文属FreeBuf原创奖励计划,未经许可禁止转载
freebuf中有一篇文章,讲述了基本的扫描原理并给出了简易的python代码,几种扫描方式中我发现SYN的扫描准确率高返回的信息明确,而且不会留下握手的痕迹,但是速度有些慢,因此我们可以使用无状态的扫描,提升扫描速度。
Scapy 是一个python的库,是一个强大的操纵报文的交互程序。它可以伪造或者解析多种协议的报文,还具有发送、捕获、匹配请求和响应这些报文以及更多的功能。所以我们使用scapy编写扫描程序。
#! /usr/bin/python import logging logging.getLogger("scapy.runtime").setLevel(logging.ERROR) from scapy.all import * dst_ip = "10.0.0.1" src_port = RandShort() dst_port=80 stealth_scan_resp = sr1(IP(dst=dst_ip)/TCP(sport=src_port,dport=dst_port,flags="S"),timeout=10) if(str(type(stealth_scan_resp))==""): print "Filtered" elif(stealth_scan_resp.haslayer(TCP)): if(stealth_scan_resp.getlayer(TCP).flags == 0x12): send_rst = sr(IP(dst=dst_ip)/TCP(sport=src_port,dport=dst_port,flags="R"),timeout=10) print "Open" elif (stealth_scan_resp.getlayer(TCP).flags == 0x14): print "Closed" elif(stealth_scan_resp.haslayer(ICMP)): if(int(stealth_scan_resp.getlayer(ICMP).type)==3 and int(stealth_scan_resp.getlayer(ICMP).code) in [1,2,3,9,10,13]): print "Filtered"
这是那篇文章中给出的syn扫描的代码,可以看到对dst_ip的dport端口发送了SYN,然后对返回的数据包进行了详细的处理。
代码中发送数据包的函数均为scapy包中的sr*发包函数,他们会等待服务器的回复,所以要设置timeout参数,当进行大量扫描时,这个等待的时间会成为提高扫描速度的瓶颈,不论timeout -1s还是减了几秒,还是使用多线程也还是很慢。因此考虑用异步的无状态的扫描提升速度。
前面我们知道了提升扫描速度的瓶颈在于发包后等待回复的时间,所以去掉这个等待时间我们就可以提升扫描速度,同时因为去掉了等待,我们需要另加一个收包的模块,用来收集扫描后返回的信息。
在无状态扫描中,收发是异步的,发包的模块不关心收包模块会不会收到回复、收包模块也不知道发包模块向谁发送了什么,也就是收发包模块间没有交互,发包的函数只负责发送,收包的模块接收特定tcp flags字段的数据包就好,这样就没有了等待回复的时间。
这也导致了:
1. 得到扫描结果的顺序和发包的顺序不一致,因为不同的目标可能有不同的响应时间。
2. 扫描的速度取决于带宽,我们可以一次发送大量的包出去,所以需要根据你的网络环境,选择合适的发包速度 (寝室的路由器就被搞崩过)
3. 使用扫描器时本机的网络环境需要很安静,因为收包的模块不知道这个数据包是被探测的服务器返回的,还是本机的程序进行的通信,比如mac会进行各种请求。。kali就是完全安静的。
4. 由于发包后不需要等待回复,所以可以用scapy包中的send函数,发包模块发包后拍拍屁股就可以走了。
5. 网络不好的时候,可能出现同一个目标的ip出现多次,所以必要时需要对结果进行去重,并且降低扫描速度。
from scapy.all import * import netaddr ip = ['1.34.0.0/16', '1.33.0.0/16'] port = [80, 81, 82] ipArray = [] portArray = [] for i in ip: ipArray.extend([str(i) for i in netaddr.IPNetwork(i).subnet(24)]) portArray = [port[i:i+3] for i in range(0, len(port), 3)] for i in ipArray: for j in portArray: send(IP(dst=i)/TCP(dport=j, flags=2), verbose=False) print i, j
发包模块使用了scapy和netaddr包,ip 和port 两个列表定义了要被扫描的部分
netaddr包用于处理ip,由于scapy的send发包函数可以传入一个IP段为目的ip,而且实践证明这样比一个for循环一个一个发快的多的多,测试了几次之后发现一次探测一个c段比较好,能兼顾速度和准确率。所以将字符串的ip段”1.34.0.0/16″初始化一个IPNetwork类,并使用subnet函数分割为c段,返回一个列表,再将这些列表合并,就得到了由c段组成的所有需要扫描的ip地址。
同理端口一次只扫描三个,将端口分为三个一组,存进portArray中。
最后发包过程中,可以选择先遍历ip或先遍历端口,注意send函数verbose参数为False避免输出很多东西,构造的数据包TCP首部flags为2,也就是flags字段只有SYN标志。如下图
from scapy.all import * iface = 'eth0' userIP = '192.168.205.160' def prn(pkt): print pkt.sprintf("%IP.src%:%IP.sport% %TCP.flags%") sniff(iface=iface, filter='tcp and dst %s and tcp[13:1] & 18==18'%userIP, prn=prn)
收包模块部分也需要导入scapy包,定义了用户的网卡名iface和本机ip userIP,传入本机ip的目的是过滤到目标为本机的数据包,在虚拟机上使用时需要格外注意。
sniff函数为scapy包的嗅探函数,用途为将iface网卡上的、符合filter的数据包传给prn回调函数进行处理,首先注意嗅探需要root权限,然后是filter函数,他也可以写成这样
sniff(iface=iface, lfilter=lambda x:x.haslayer(TCP) and x[IP].dst==userIP and x.flags==18, prn=prn)
lfilter参数传入一个函数,返回True则留下这个数据包,反之False则丢弃它, filter 传入的是tcpdump的过滤语法,有更高的效率,所以推荐使用filter
至于tcp[13:1] & 18==18是怎么来的,因为在syn扫描中,我们向目标端口发送SYN,如果它开放的话会回复SYN+ACK,也就是SYN ACK位均为1,在上面tcp首部的图中,ACK为高位,SYN为低位,2(SYN) + 16(ACK) = 18。
此外tcp[13:1]是tcpdump里的一个高级语法,意为取tcp数据包的下标为13的字节(也就是第14个字节)开始的1个字节,也就是上面图中flags所在的字节,这样用其值与18与一下,就过滤掉了别的包。
在回调函数prn中,可以对扫描结果进行处理,可以打印出来,也可以存入文件中。(sprintf是scapy包中的格式化输出函数)
上面实际是两个文件,可以用多线程,主线程发包,另开一个线程sniff嗅探达到整合的目的。
当然也可以粗暴的开两个命令行,分别执行两个文件,在收包那里,就可以看到扫描结果很快的出来啦。
*本文原创作者:addadd,本文属FreeBuf原创奖励计划,未经许可禁止转载