0x00:简介
渗透测试信息搜集阶段,在发现目标服务器所开放端口时,扫描方式可分为三种,分别是:隐蔽扫描,全链接扫描和僵尸扫描。
全链接扫描:即正常的请求,过程包含了三次握手,判断端口开放的依据是第三次握手返回的信息是否为 FIN/ACK。
隐蔽扫描:即只发送第一次握手的 SYN 包,判断端口开放的依据是返回的信息是否为 SYN/ACK,如果为 SYN/ACK 则端口开放,否则为未开放状态。未开放时会返回 RST/ACK 的信息。
僵尸扫描:即用僵尸机(非自己的机器)对目标服务器进行端口探测,因为使用的为僵尸机,所以其隐蔽性高,追查不到自己的 IP,利用程度比隐蔽扫描和全链接扫描要大。
0x01:原理
僵尸扫描可分为自己的机器、僵尸机和目标服务器。
第一步:扫描者直接向僵尸机发送第二次握手包,即 SYN/ACK,类似于隐蔽扫描,直接发送 SYN/ACK,僵尸机会返回一个 RST 包,RST 包中包含了 ipid 参数,此参数为数据包的序列号,记录此序列号,例如为 x。
第二步:扫描者伪造自己的 ip 为僵尸机的 ip,向目标服务器发送第一次握手包 SYN,发送后就关闭了链接,这时的目标服务器收到的是一个不正常的 SYN 包。
第三步:端口开放的情况,当目标服务器端口开放时,收到 SYN 后会返回一个 SYN/ACK,这个包是返回给僵尸机的,因为 SYN 是自己伪造僵尸机发的,所以僵尸机会直接返回一个 RST,此时 RST 的序号为 x+1.
第四步:端口关闭的情况,当目标服务器只收到一个 SYN 时,因为没有完整的后续握手,所以会返回一个 RST 包。因为这时的僵尸机直接收到了一个 RST 包,所以后面不会再有发送其他握手包的情况,此时包序号还停留在 x。
第五步:扫描者再次直接向僵尸机发送 SYN/ACK 包,不完整的握手,还是类似于隐蔽扫描,所以僵尸机会直接返回 RST 包,此时 RST 的 ipid 序号如果是 x+1,则证明目标端口是未开放的状态。如果是 x+2,则证明收到过目标机的 SYN/ACK 包,目标端口是开放状态。
以上为僵尸扫描的流程和原理,整个过程需要一个基础就是僵尸机是一个足够空闲的状态,不会产生其他的数据包通信且 ipid 序号是递增的。因为这一点,僵尸扫描的难度大,且难利用,ipid 递增的机器有 xp、2000、2003 等。
僵尸扫描特点为:极度隐蔽、实施条件苛刻、可伪造源地址、需要一个僵尸机、僵尸机为空闲系统、僵尸机的 ipid 为递增。
0x02:实验
环境用来扫描的机器是 kali(ip 为 103),win2003 为僵尸机(ip 为 105),metasploitable 为目标机(ip 为 104)。首先用 scapy 库来实现僵尸扫描发现端口这个过程。
kali 命令行输入 scapy 进入 scapy 环境,构造一个 SYN/ACK 包发送给僵尸机。其命令如下:
其中通过 IP 头和 TCP 头组合了一个请求包,dst 为目标 ip 地址,也就是僵尸机的 ip 地址。dport 为目标端口,这里使用的是 445,445 为 windows 中的文件共享服务,一般都是开放的状态。最后通过 display 来查看组合好的请求包。
有了僵尸机的请求包后,需要再有一个给目标机发送的请求包。其命令如下:
其中 src 指定的就是发送的源地址,这里就是伪造自己为僵尸机的 ip,检测的是目标服务器的 21 端口。请求包都配置完毕后,按照僵尸扫描流程,首先发送的是僵尸机,其次是目标服务器,最后需要再发一次僵尸机,在 scapy 中,发包命令使用 sr1,三次发包执行如下:
可以通过僵尸扫描流程图然后对比上面的命令执行结果图来更清晰的去理解整个过程。首先使用 sr1(rz) 命令发送了僵尸机包,可以看到 id 为 108,其返回的 flags 为 SA,即 SYN/ACK。其次使用 sr1(rt) 发送了目标服务器包,最后再一次向僵尸机发送了请求包。这里需要注意的是,最后一次请求包的 id 为 110,差为 2,证明目标服务器 21 端口为开放状态。
上面为目标端口的开放状态,下面看一下端口未开放时 scapy 的执行过程,发送给僵尸机的包不变,需要改的是发送给目标服务器的端口号,如下图所示:
检测目标服务器的 33 端口,33 端口为关闭状态。发送过程同上,执行过程如下:
第一次给僵尸机发送的数据包返回结果中 id 为 146,然后向目标服务器发送的数据包返回结果中 flags 不再是 SA,而变成了 RA。最后再一次像僵尸机发送的数据包其返回结果中 id 为 147,与第一次的差值比为 1,则证明目标端口为关闭状态。
scapy 通过僵尸扫描方式去发现目标服务器端口可以通过 python 脚本来实现批量检测,其示例脚本如下:
#!/usr/bin/python
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import *
def ipid(zombie):
reply1 = sr1(IP(dst=zombie)/TCP(flags="SA"),timeout=2,verbose=0)
send(IP(dst=zombie)/TCP(flags="SA"),verbose=0)
reply2 = sr1(IP(dst=zombie)/TCP(flags="SA"),timeout=2,verbose=0)
if reply2[IP].id == (reply1[IP].id + 2):
print "IPID sequence is incremental and target appears to be idle. zombie located"
reponse = raw_input("Do you want to use this zombie to perform a scan? (Y or N):")
if reponse == "Y":
target = raw_input("Enter the IP address of the target system:")
zombiescan(target,zombie)
else:
print "Either the IPID sequence is not incremental or the target is not idle. not a good zombie"
def zombiescan(target,zombie):
print "\nScanning target " + target + " with zombie " + zombie
print "\n----------Open Ports on Target----------\n"
for port in range(1,100):
try:
start = sr1(IP(dst=zombie)/TCP(flags="SA",dport=port),timeout=2,verbose=0)
send(IP(src=zombie,dst=target)/TCP(flags="S",dport=port),verbose=0)
end = sr1(IP(dst=zombie)/TCP(flags="SA"),timeout=2,verbose=0)
if end[IP].id == (start[IP].id + 2):
print port
except:
pass
print "----------Zombie Scan Suite----------\n"
print "1 - Identify Zombie Host\n"
print "2 - Perform Zombie Scan\n"
ans = raw_input("Select an Option (1 or 2): ")
if ans == "1":
zombie = raw_input("Enter IP address to test IPID sequence: ")
ipid(zombie)
else:
if ans == "2":
zombie = raw_input("Enter IP address for zombie system: ")
target = raw_input("Enter IP address for scan target: ")
zombiescan(target,zombie)
以上脚本中 ipid 函数是检测一个机器是否是一个合格的僵尸机,检测方式是通过 id 的序号来进行判断。zombiescan 函数为僵尸扫描函数,和前面的 scapy 命令行执行流程一样,脚本中指定了扫描端口的范围为 1-100。
使用过程:运行脚本首先会打印 1 为僵尸机检测,2 为僵尸扫描,然后根据需求选择 1 或 2,1 需要输入僵尸机的 ip,2 则需要输入僵尸机 ip 和目标机器的 ip。在僵尸机检测完后如果合格,则会告诉你僵尸机合格,是否要进行扫描,输入 Y 则会跳到 zombiescan 函数,其执行过程如下:
运行脚本后,目标服务器的 1-100 范围的端口如果在线,则会被打印出来。为了更清晰的了解僵尸扫描的过程,其脚本执行时通过 wireshark 抓包部分过程如下:
对于僵尸扫描,除了使用 scapy 外,nmap 也具有此检测功能,且使用更为简单和方便。在检测一个机器是否为合格僵尸机时,nmap 提供了 ipidseq 脚本,其使用方法如下:
扫描结果除列出端口的开放情况和端口的服务外,也有主机的 mac 信息和系统信息,最后给出了是否可以作为一个僵尸机,ipidseq 给出的值为 incremental,incremental 翻译为递增的意思,即表示 ipid 为递增,代表其合格。有了合格的僵尸机后,再次使用 nmap 进行端口发现,主要用到的参数是 sI,sI 用来指定僵尸机的 ip 地址,参数说明如下:
命令格式为:nmap 192.168.123.104 -sI 192.168.123.105 -Pn -p 0-100,执行结果如下:
其结果和上一个 scapy 检测结果一致。nmap 僵尸检测端口 wireshark 抓包如下:
0x03:总结
在端口扫描的三种方式中,僵尸扫描是最隐蔽的一种方式,但执行难度也较高。
确认目标端口的开放情况时,不建议使用一种扫描方式的结果为标准,建议使用多种不同的发现方式,每一种方式都会存在多多少少的误差,并非准确无误,在多种方式的扫描结果下,最后做总结,确定端口的开放情况。
公众号推荐:aFa攻防实验室
分享关于信息搜集、Web安全、内网安全、代码审计、红蓝对抗、Java、Python等方面的东西。