端口扫描的工具已经有很多了,比如最出名的就是Nmap神器,出于学习以及比如担心引入外部工具对网络扫描造成不可控等一些可能的原因,这里研究一下通过python进行端口扫描的方法,主要测试了TCP Connection全连接扫描、TCP半连接扫描以及TCP FIN扫描。

一、TCP报文及连接过程

        这里引用教材(具体是哪本教材记不清楚了,百度找的)上的一张经典图片,这里着重关注flags标志位,简要说明如下:

Python进行TCP端口扫描_scapy

URG(Urgent,紧急标志):当设置此位值时,数据优先于其它数据

SYN(Synchronization,同步标志):表示建立连接

FIN(Finish,完成标志):表示关闭连

ACK(Acknowledgment,确认标志):表示响应

PSH(Push,推送标志):表示有 DATA数据需要立即传输给应用程序,而不是缓存

RST(Reset,重置标志):表示中止连接,对连接重置。

TCP三次握手和四次握手过程如下(图片摘自网络https://www.cnblogs.com/huyingsakai/p/9268468.html):

Python进行TCP端口扫描_scapy_02

几种扫描介绍如下:

#TCP Connect全连接扫描:扫描机需要与目标机建立完整的TCP连接,完成三次握手后,扫描机主动关闭连接。可借助Python的socket实现。

#TCP SYN 扫描:扫描机向目标机发起TCP连接,标志位SYN置1,如果目标机端口打开,返回SYN/ACK标志,扫描机向目标机回复RST报文,否则返回RST报文。该方式未建立完整的三次握手,也称为TCP半开放扫描。可借助raw socket或者scapy实现。

#TCP FIN扫描:扫描机向目标机发起TCP连接,其中F位置1,如果目标主机端口开放,则无响应;如果未开放,则目标机返回RST报文。可借助scapy实现。

二、代码实现及测试

1、TCP Connect全连接扫描代码实现:

def test():

    socket.setdefaulttimeout(2)

    host = '118.*.*.*'

    port = [80, 443, 22, 8888]

    try:

        tgtIP = socket.gethostbyname(host)

    except:

        print("Cannot resolve '%s':unkown host" % host)

        return

    try:

        tgtName = socket.gethostbyaddr(tgtIP)

        print("scan results for:" + tgtName[0])

    except:

        print("scan results for:" + tgtIP)

    for i in range(len(port)):

        try:

            conn = socket.socket()

            conn.connect((host, port[i]))

            print('[+] %d/tcp open' % port[i])

        except Exception as e:

            print(e)

            print('[-] %d/tcp closed' % port[i])

        finally:

            conn.close()

if __name__ == '__main__':

    test()

扫描结果:

Python进行TCP端口扫描_TCP端口扫描_03

抓包分析:

Python进行TCP端口扫描_Python_04

可以看到这是一个完整的TCP 连接建立过程。

2、TCP SYN扫描代码实现:

def test01():

    ip = '118.*.*.*'

    ports = [8888, 80, 443, 22]

    for n in range(len(ports)):

        try:

            # 发送设置了SYN标志的数据包

            temp = sr(IP(dst=ip) / TCP(dport=(int(ports[n])), flags='S'), timeout=2, verbose=False)

            if temp[0].res[0][1].payload.flags == 'SA':

                print('[+] %d is open ! flags is: %s ' % (ports[n], temp[0].res[0][1].payload.flags))

            else:

                print('[-] %d is down !' % ports[n])

        except Exception as e:

            print(e)

            print('[-] %d is down !' % ports[n])

if __name__ == '__main__':

    test01()

扫描结果:

Python进行TCP端口扫描_scapy_05

抓包分析:

发送的TCP连接首包SYN置1,如下:

Python进行TCP端口扫描_TCP端口扫描_06

8888端口未打开,目标机回复标志位为RST/ACK,也就是RA,如下:

Python进行TCP端口扫描_TCP端口扫描_07

80端口为打开的,目标机回复SYN/ACK,也就是SA,如下:

Python进行TCP端口扫描_scapy_08

仔细观察报文情况,可以看到22号端口没有返回报文,在扫描的时候报的错误为:list index out of range,因此我们使用try ……except捕获异常。

3、TCP FIN扫描实现:

def test01():

    ip = '192.168.2.6'

    ports = [8888, 80, 443, 22, 1234]

    for n in range(len(ports)):

        try:

            # 发送设置了SYN标志的数据包

            temp = sr(IP(dst=ip) / TCP(dport=(int(ports[n])), flags='S'), timeout=2, verbose=False)

            if temp[0].res[0][1].payload.flags == 'SA':

                print('[+] %d is open ! flags is: %s ' % (ports[n], temp[0].res[0][1].payload.flags))

            else:

                print('[-] %d is down !' % ports[n])

        except Exception as e:

            print(e)

            print('[-] %d is down !' % ports[n])

if __name__ == '__main__':

    test01()

这种方式在实际使用中发现,本地windows作为目标机,所有端口均返回RST报文,而测试了一个公网地址,全部无响应。如下:

Python进行TCP端口扫描_Python_09

通过Nmap再测试了几个地址,均显示:open|filtered,所以该方式扫描可信度不高。

Python进行TCP端口扫描_Python_10

三、总结

        以上使用Python脚本测试了TCP Connection全连接扫描、TCP 半连接扫描以及TCP FIN扫描,其中TCP FIN扫描效果不是很好,另外两种方式都还不错,如果觉得扫描太慢,可以改造为多线程来使用。如果在真实环境使用,可以在添加了多线程后,再通过sleep修改等待时间,控制并发数以及发送间隔,尽量降低影响。