python scapy的半连接端口扫描

原理简述:

端口扫描常见的一般有以下两种
  • 基于socket的connect全连接扫描(速度快,但是会在对方系统日志留下痕迹)
  • 基于scapy的SYN半连接扫描(不会被扫描的主机发现,速度慢。现在好像防火墙会有痕迹)

注意

  • connect全连接扫描,基于socket套接字的connect方法,它的参数是一个元组(ip, port),此扫描试图与每一个TCP端口进行“三次握手”通信。如果能够成功建立接连,则证明端口开发,否则为关闭。准确度很高。缺点:最容易被防火墙和IDS检测到,并且在目标主机的日志中会记录大量的连接请求以及错误信息。优点:另一优点是扫描速度快。如果对每个目标端口以线性的方式,使用单独的connect()调用,可以通过同时打开多个套接字,从而加速扫描。

  • SYN半连接扫描,扫描IP段的全部端口部分的实现,基于scapy构造SYN数据包向目标主机的一个端口发送连接请求,当我们收到SYN/ACK包后,不发送的ACK应答而是发送RST包请求断开连接。这样,三次握手就没有完成,无法建立正常的TCP连接,因此,这次扫描就不会被记录到系统日志中,但是可能会在防火墙上留下痕迹。

  • 1、Client发送SYN
    2、Server端发送SYN/ACK
    3、Client发送RST断开(只需要前两步就可以判断端口开放)

  • 1、Client发送SYN
    2、Server端回复RST(表示端口关闭)
    输入:
    是一个ip的list文件(按行存储的都可以),例如下面这样:

104.168.0.0/16,pcapname
104.28.0.0/16,pcapname
134.29.0.0/16,pcapname
21.61.127.231,pcapname_port
221.17.0.0/16,pcapname
23.25.36.58,pcapname_port

输出:
对应的IP和开放的PORT及其pcapname和扫描时间插入mysql,日志输出

  1. 单个ip进行全端口扫描或指定端口扫描(日志输出,mysql插入,和传入的pcap包的name解析)
# 单个IP全端口扫描
import time  
import socket  
import threading 
from scapy.all import *
  

class SingelIP_Scan:
    def connScan(self, tgtHost, tgtPort, name):  
        try:  
            # SYN扫描
            syn = IP(dst=tgtHost)/TCP(dport=tgtPort, flags=2)
            result_raw = sr(syn, iface='enp6s0f0', timeout=1, verbose=False)  # Linux下少了iface参数,需要加上
            result_list = result_raw[0].res
            for i in range(len(result_list)):
                if result_list[i][1].haslayer(TCP):
                    TCP_fields = result_list[i][1].getlayer(TCP).fields
                    if TCP_fields['flags'] == 18:
                        port = TCP_fields['sport']
                        # print('[+]', port, ' : is open', name)
                        n = News(tgtHost, port, name)
                        n.insert()
                        logger.warning('from: ' + name +  ' insert [+] ' + tgtHost + ' [+] ' + str(port) + ' : is open')

        except:   
            # print("[-]%d/tcp close" % tgtPort)  
            pass
      
    def portScan(self, tgtHost, name):  
        try:  
            tgtIP = socket.gethostbyname(tgtHost)  
        except:  
            # print("[-]cannot connect %s" % tgtIP)  
            logger.warning(' [-] ' + tgtIP + ' can not connect')
            return  

        # print("\n[+]scan results for:" + tgtIP)   
        if '_' in name:
            result = name.split('_')
            name = result[0]
            if result[1].isdigit():
                port = int(result[1])
                self.connScan(tgtHost, port, name)
            else:
                for port in range(0, 65535):  
                    time.sleep(1)
                    print("scanning port:" + str(port)) 
                    t2 = threading.Thread(target=self.connScan, args=(tgtHost, port, name))
                    # t.daemon = True
                    t2.start() 
                    # self.connScan(tgtHost,int(port), name)   
        else:
            for port in range(0, 65535):  
                time.sleep(1)
                # print("scanning port:" + str(port))  
                t3 = threading.Thread(target=self.connScan, args=(tgtHost, port, name))
                # t.daemon = True
                t3.start()
  1. 一个IP段解进行全端口扫描或者指定端口扫描(log输出,mysql插入,pcap包name解析)
import logging
from scapy.all import *
from random import randint
import threading
import time
from config import *

logging.getLogger('scapy.runtime').setLevel(logging.ERROR)


class SegmentIP_Scan:
	def ping_one(self, host):
		ip_id = randint(1, 65535)
		icmp_id = randint(1, 65535)
		icmp_seq = randint(1, 65535)

		packet = IP(dst=host, ttl=64, id=ip_id)/ICMP(id=icmp_id, seq=icmp_seq)/b'hello'
		ping = sr1(packet, iface='enp6s0f0', timeout=2, verbose=False)  # Linux下少了iface参数,指定网卡
		if ping:
			return 0
		else:
			return -1

	def loop_addrs(self, addrs, pcapname):
		for addr in addrs:
			if self.ping_one(addr) == -1:
				# 说明对方关机,或者没有这个地址,设置了ICMP数据包过滤
				# print(addr, ': 不可达')  # 不能有中文
				logger.warning(' [-] ' + addr + ' can not connect')
			else:
				t4 = threading.Thread(target=self.syn_scan, args=(addr, pcapname))  # , 100, 500
				# t.daemon = True  # 全开守护线程快是快,但是开100个线程就占用大概3G的运存
				t4.start()

	def syn_scan(self, hostname, pcapname):  # , lport, hport
		# print('scan: ', hostname, pcapname)  # , str(lport), '-', str(hport)
		if '_' in pcapname:
			result = pcapname.split('_')
			pcapname = result[0]
			if result[1].isdigit():
				port = int(result[1])
				syn = IP(dst=hostname)/TCP(dport=port, flags=2)
				result_raw = sr(syn, iface='enp6s0f0', timeout=1, verbose=False)  # iface参数
				result_list = result_raw[0].res
				for i in range(len(result_list)):
					if result_list[i][1].haslayer(TCP):
						TCP_fields = result_list[i][1].getlayer(TCP).fields
						if TCP_fields['flags'] == 18:
							port = TCP_fields['sport']
							# print('[+]', port, ' : is open')
							n = News(hostname, port, pcapname)
							n.insert()
							logger.warning('from: ' + pcapname +  ' insert [+] ' + hostname + ' [+] ' + str(port) + ' : is open')
			else:
				for port in range(0, 65535):
					time.sleep(1)
					syn = IP(dst=hostname)/TCP(dport=port, flags=2)
					result_raw = sr(syn, iface='enp6s0f0', timeout=1, verbose=False)
					result_list = result_raw[0].res
					for i in range(len(result_list)):
						if result_list[i][1].haslayer(TCP):
							TCP_fields = result_list[i][1].getlayer(TCP).fields
							if TCP_fields['flags'] == 18:
								port = TCP_fields['sport']
								# print('[+]', port, ' : is open')
								n = News(hostname, port, pcapname)
								n.insert()
								logger.warning('from: ' + pcapname +  ' insert [+] ' + hostname + ' [+] ' + str(port) + ' : is open')
		else:
			for port in range(0, 65535):
				time.sleep(1)
				syn = IP(dst=hostname)/TCP(dport=port, flags=2)
				result_raw = sr(syn, iface='enp6s0f0', timeout=1, verbose=False)  # iface参数
				result_list = result_raw[0].res
				for i in range(len(result_list)):
					if result_list[i][1].haslayer(TCP):
						TCP_fields = result_list[i][1].getlayer(TCP).fields
						if TCP_fields['flags'] == 18:
							port = TCP_fields['sport']
							# print('[+]', port, ' : is open')
							n = News(hostname, port, pcapname)
							n.insert()
							logger.warning('from: ' + pcapname +  ' insert [+] ' + hostname + ' [+] ' + str(port) + ' : is open')

  1. 启动函数,函数的入口
from ip_List_All import *
from ip_scan_all import *
from config import *
import re
import threading


def ip_match(line):
    p = re.compile(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(\/{0,1}\d{0,3})(.\w*\_{0,1}\w*)')
    s = p.match(line)
    name = s.group(3)[1:]
    result = []
    if s:
        if s.group(2) == '/16':  # and '_' not in s.group(3)[1:]:
            q = re.compile(r'(\d{1,3}\.\d{1,3}\.)')
            a = q.match(s.group(1))
            h = a.group(1)
            ips = []
            for i in range(256):
                ip1 = h + str(i)
                for j in range(256):
                    ip2 = ip1 + '.' + str(j)
                    ips.append(ip2)
            result.append(2)
            result.append(ips)
            result.append(name)
            # print(result)
            return result
        elif s.group(2) == '/24':  # and '_' not in s.group(3)[1:]:
            q = re.compile(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.)')
            a = q.match(s.group(1))
            h = a.group(1)
            ips = []
            for i in range(256):
                ip1 = h + str(i)
                ips.append(ip1)
            result.append(2)
            result.append(ips)
            result.append(name)
            # print(result)
            return result
        elif '/' not in s.group(2):  # and '_' not in s.group(3)[1:]:
            result.append(4)
            result.append(s.group(1))
            result.append(name)
            # print(result)
            return result


def read_ip_list(filename):
    # 返回值为2: 走ip_list全端口扫描或指定端口
    # 返回值为4: 走单个ip全端口扫描或指定端口
    with open(filename, 'r') as f:
        lines = f.readlines()

    for line in lines:
        a = ip_match(line)
        if a[0] == 2:
            scan = SegmentIP_Scan()
            # scan.loop_addrs(a[1], a[2])
            t = threading.Thread(target=scan.loop_addrs, args=(a[1], a[2]))
            t.start()
        elif a[0] == 4:
            scan = SingelIP_Scan()
            # scan.portScan(a[1], a[2])
            t1 = threading.Thread(target=scan.portScan, args=(a[1], a[2]))
            t1.start()


if __name__ == '__main__':
    try:
        read_ip_list('ip.list')
    except Exception as e:
        print(e)
    finally:
        logger.warning('scaner finished!')

    # 注意在Linux下跑的时候需要sudo权限,
    # 使用scapy的sr发包的时候需要添加上,选用的网卡信息 iface参数


  1. 配置文件
    注意: 代码不能直接跑,需要配置自己的mysql数据库和log日志输出的文件路径
import logging
from contextlib import closing
import datetime
import pymysql as Mdb

logger = logging.getLogger("scan_port")
formatter = logging.Formatter('%(name)s %(asctime)s %(levelname)-5s %(lineno)d: %(message)s')

file_handler = logging.FileHandler("log/scan_port.log")
file_handler.setFormatter(formatter)
file_handler.setLevel(logging.WARNING)

console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
console_handler.setLevel(logging.DEBUG)

logger.addHandler(console_handler)
logger.addHandler(file_handler)

logger.setLevel(logging.INFO)


class News(object):
    def __init__(self, scan_ip, open_port, pcapname):
        self.__scan_ip = scan_ip
        self.__open_port = open_port
        self.__pcapname = pcapname

    def insert(self):
        with closing(Mdb.connect("127.0.0.1", "root", "password", "db01",charset="utf8")) as db:
            with closing(db.cursor()) as cursor:
                now_time = datetime.datetime.strftime(datetime.datetime.now(), "%Y-%m-%dT%H:%M:%S")

                cursor.execute("INSERT INTO tablename(`scan_ip`, `open_port`, `pcapname`, `updated_time`) VALUES (%s,%s,%s,%s)", \
                               (self.__scan_ip, self.__open_port, self.__pcapname, now_time))
                db.commit()

BUG: 这个代码有个问题就是发送SYN给端口时对于防火墙返回SYN/ACK的时候无法判断是否可连接(防火墙对65535个端口都返回),可以完成握手但是,不能通信

你可能感兴趣的:(python,scapy,端口扫描)