基于TCP的简易端口扫描器

端口扫描是网络侦察和信息收集的基础手段之一。在多种扫描技术中,TCP全连接扫描因其实现简单、结果可靠而成为初学者的理想选择。其核心原理是尝试与目标主机的指定端口完成完整的TCP三次握手。本文将简要介绍其工作原理,并使用

Python/Go/你使用的语言\] 一步步实现一个轻量级的端口扫描器。

一、定义扫描函数

python 复制代码
#定义一个开始扫描的函数
def star_scan(ip,port):
    #异常处理
    try:
        s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        # 创建一个使用tcp的套接字
        s.settimeout(1.0)      #设置超时时间
        s.connect((ip,port))
    except Exception as e:
        # print(e)   #防止超时提醒过多
        pass
    else:
        #避免因未接收到返回数据而报错误判,所以注释掉了
        # res = s.recv(3096).decode('utf-8').encode()
        # print(res)
        print("[+]{}:{} \topen".format(ip,port))
    finally:
        s.close()

二、定义创建扫描列表函数

python 复制代码
def make_port_list(ports):
    #接收的列表
    new_port_list = []
  
    # 判断并处理传入的ports的值,判断间隔为 "," or "-"
    # 情况一:22,80,443
    if "," in ports:
        #以","为分隔符生成列表
        temp_list = ports.split(",")
        #在temp_list列表中循环遍历
        for port in temp_list:
            #判断是否有"-"在列表内
            if "-" in port:
                #根据传入的值从首位遍历值末位
                for p in range(int(port.split("-")[0]),int(port.split("-")[1]) + 1):
                    new_port_list.append(p)
            # 如果在"-"则代表传入的为1-100格式
            # 否则就是20,22,23,80这种格式,直接传入遍历的值即可
            else:
                new_port_list.append(port)
    # 情况二:1-100
    elif "-" in ports:
         # 从第一个数通过 +1 一直遍历至最后一个数
         for p in range(int(port.split("-")[0]),int(port.split("-")[1]) + 1):
            new_port_list.append(p)
  
    # 情况三:使用者输入的内容为单一端口
    else:
        new_port_list.append(ports)

    # 返回端口列表的值
    return new_port_list

三、加入协程函数

python 复制代码
import socket
import sys
# sys 导入系统相关的功能,通常用于访问命令行参数、退出程序等
from queue import Queue
# 导入线程安全的队列数据结构,用于在多线程/协程间安全地传递数据
from gevent import monkey;monkey.patch_socket()
# gevent是一个基于协程的Python网络库
# monkey模块提供了"猴子补丁"功能
# monkey.patch_socket()将标准库的socket模块替换为gevent的异步版本
# 效果:使普通的同步socket代码变成异步非阻塞的,无需修改原有代码
import gevent
# 导入gevent主模块,用于创建和管理协程
  
# socket套接字基本格式
# socket.socket(family.type)
  
print('''
 ____            _   ____                  
|  _ \ ___  _ __| |_/ ___|  ___ __ _ _ __  
| |_) / _ \| '__| __\___ \ / __/ _` | '_ \
|  __/ (_) | |  | |_ ___) | (_| (_| | | | |
|_|   \___/|_|   \__|____/ \___\__,_|_| |_|

''')
  
  
#定义生成扫描列表的函数
def make_port_list(ports):
    #接收的列表
    new_port_list = []
  
    # 判断并处理传入的ports的值,判断间隔为 "," or "-"
    # 情况一:22,80,443
    if "," in ports:
        #以","为分隔符生成列表
        temp_list = ports.split(",")
        #在temp_list列表中循环遍历
        for port in temp_list:
            #判断是否有"-"范围类参数在列表内
            if "-" in port:
                #根据传入的值从首位遍历值末位
                for p in range(int(port.split("-")[0]),int(port.split("-")[1]) + 1):
                    new_port_list.append(p)
            # 如果在"-"则代表传入的为1-100格式
            # 否则就是20,22,23,80这种格式,直接传入遍历的值即可
            else:
                new_port_list.append(port)
    # 情况二:1-100
    elif "-" in ports:
         # 从第一个数通过 +1 一直遍历至最后一个数
         for p in range(int(port.split("-")[0]),int(port.split("-")[1]) + 1):
            new_port_list.append(p)
  
    # 情况三:使用者输入的内容为单一端口
    else:
        new_port_list.append(ports)
  
    # 返回端口列表的值
    return new_port_list
  
# 定义多协程函数
def coroutines(batch_size=None):
    #开启多协程
    cos = []
    #空列表cos用于存储所有的协程对象
    # 如果指定了batch_size,就使用它,否则使用队列中的所有任务
    if batch_size is None:
        num = ip_port.qsize()
    else:
        num = min(batch_size, ip_port.qsize())  
        # 确保不超过队列中剩余的任务数
    # ip_port.qsize():获取队列 ip_port 中的任务数量
    # num:保存任务总数
    # print(num):打印任务数量,用于调试    
    for i in range(num):
        # 循环任务数量,为每个任务创建协程
        # 调用工作函数,
        cor = gevent.spawn(star_scan)
        # gevent.spawn()函数用于创建一个新的协程,并调用star_scan函数
        # spawn方法不会立即执行函数,而是创建协程对象
        # 将返回的协程对象保存至变量cor
        cos.append(cor)
        #将cor中的协程对象添加至cor列表中
    gevent.joinall(cos)
    #等待列表中的所有协程执行完毕
  
#定义一个开始扫描的函数
def star_scan():
    #动态任务分配
    sockets = ip_port.get()
    ip = sockets[0]
    port = int(sockets[1])
    #异常处理
    try:
        s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        # 创建一个使用tcp的套接字
        s.settimeout(2.0)      #设置超时时间
        s.connect((ip,port))
    except Exception as e:
        # print(e)   #防止超时提醒过多
        pass
    else:
        #避免因未接收到返回数据而报错误判,所以注释掉了
        # res = s.recv(3096).decode('utf-8').encode()
        # print(res)
        print("[+]{}:{} \topen".format(ip,port))
    finally:
        s.close()
  
ip_port = Queue()
#创建队列
ip = "113.44.158.248"
port = "1,2-2400"
# 将make_port_list返回的值传入port_list
port_list = make_port_list(port)
print("total port num:{}".format(len(port_list)))
for port in port_list:
    ip_port.put([ip,port])
  
coroutines()
#开启协程
#将协程设置为每50个创建一次
num = 50
if len(port_list)%num == 0:
#如果port_list中的数量能被50整除
    turn = int(len(port_list)/num)
    #计算需要多少批次
    for i in range(turn):
        coroutines(num)
        #每批处理50个端口
else:
    turn = int(len(port_list)/num)+1
    # 因为不能被整除,所以 批次 = 整数部分 + 1
    for i in range(turn):
        if i == turn-1:
            #如果是最后一批
            coroutines(len(port_list)%num)
            #取余,处理剩余端口
        else:
            coroutines(num)
            #正常处理50个端口

四、命令行传入

python 复制代码
def arguments():
    # 创建对象
    parser = argparse.ArgumentParser(description='python PortScanner.py [IP] [Port]')
    # 保存定义的命令行参数信息
    parser.add_argument('IP', type=str, help='待扫描IP地址')
    parser.add_argument('Port', type=str, help='指定端口')
    '''
    add_argument方法:用于添加命令行参数
    'input'指定的 name:指定命令行参数的名称
    type:指定参数的类型
    help:用于描述该参数的作用
    '''
    # 如果参数是可选参数,使用 - -- 前缀来定义
  
    # parser.add_argument('-P', '--Port', action='store_true', help='待扫描端口' \
    # '[20,21,22] or [1-999]')
    '''
    --output 为可选参数,类型为字符串
    -v 和 --verbose 是同一个可选参数的两种类型,类型为布尔值
    如果用户指定了该参数时,值为 True ,否则为 False
    action='store_true' 表示当指定这个参数时,将其值设置为 True
    '''
  
    args = parser.parse_args()
    # parse_args():解析命令行参数,返回命名空间
    IP = args.IP
    Port = args.Port
    return(IP,Port)

五、最后整理一下

python 复制代码
import socket
import sys
# sys 导入系统相关的功能,通常用于访问命令行参数、退出程序等
from queue import Queue
# 导入线程安全的队列数据结构,用于在多线程/协程间安全地传递数据
from gevent import monkey;monkey.patch_socket()
# gevent是一个基于协程的Python网络库
# monkey模块提供了"猴子补丁"功能
# monkey.patch_socket()将标准库的socket模块替换为gevent的异步版本
# 效果:使普通的同步socket代码变成异步非阻塞的,无需修改原有代码
import gevent
# 导入gevent主模块,用于创建和管理协程
import argparse
  
# socket套接字基本格式
# socket.socket(family.type)
  
print('''
 ____            _   ____                  
|  _ \ ___  _ __| |_/ ___|  ___ __ _ _ __  
| |_) / _ \| '__| __\___ \ / __/ _` | '_ \
|  __/ (_) | |  | |_ ___) | (_| (_| | | | |
|_|   \___/|_|   \__|____/ \___\__,_|_| |_|
  
''')
  
#定义生成扫描列表的函数
def make_port_list(ports):
    #接收的列表
    new_port_list = []
  
    # 判断并处理传入的ports的值,判断间隔为 "," or "-"
    # 情况一:22,80,443
    if "," in ports:
        #以","为分隔符生成列表
        temp_list = ports.split(",")
        #在temp_list列表中循环遍历
        for port in temp_list:
            #判断是否有"-"范围类参数在列表内
            if "-" in port:
                #根据传入的值从首位遍历值末位
                for p in range(int(port.split("-")[0]),int(port.split("-")[1]) + 1):
                    new_port_list.append(p)
            # 如果在"-"则代表传入的为1-100格式
            # 否则就是20,22,23,80这种格式,直接传入遍历的值即可
            else:
                new_port_list.append(port)
    # 情况二:1-100
    elif "-" in ports:
         # 从第一个数通过 +1 一直遍历至最后一个数
         for p in range(int(port.split("-")[0]),int(port.split("-")[1]) + 1):
            new_port_list.append(p)
  
    # 情况三:使用者输入的内容为单一端口
    else:
        new_port_list.append(ports)
  
    # 返回端口列表的值
    return new_port_list
  
# 定义多协程函数
def coroutines(batch_size=None):
    #开启多协程
    cos = []
    #空列表cos用于存储所有的协程对象
    # 如果指定了batch_size,就使用它,否则使用队列中的所有任务
    if batch_size is None:
        num = ip_port.qsize()
    else:
        num = min(batch_size, ip_port.qsize())  
        # 确保不超过队列中剩余的任务数
    # ip_port.qsize():获取队列 ip_port 中的任务数量
    # num:保存任务总数
    # print(num):打印任务数量,用于调试    
    for i in range(num):
        # 循环任务数量,为每个任务创建协程
        # 调用工作函数,
        cor = gevent.spawn(star_scan)
        # gevent.spawn()函数用于创建一个新的协程,并调用star_scan函数
        # spawn方法不会立即执行函数,而是创建协程对象
        # 将返回的协程对象保存至变量cor
        cos.append(cor)
        #将cor中的协程对象添加至cor列表中
    gevent.joinall(cos)
    #等待列表中的所有协程执行完毕
  
#定义一个开始扫描的函数
def star_scan():
    #动态任务分配
    sockets = ip_port.get()
    ip = sockets[0]
    port = int(sockets[1])
    #异常处理
    try:
        s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        # 创建一个使用tcp的套接字
        s.settimeout(2.0)      #设置超时时间
        s.connect((ip,port))
    except Exception as e:
        # print(e)   #防止超时提醒过多
        pass
    else:
        #避免因未接收到返回数据而报错误判,所以注释掉了
        # res = s.recv(3096).decode('utf-8').encode()
        # print(res)
        print("[+]{}:{} \topen".format(ip,port))
    finally:
        s.close()
  
def arguments():
    # 创建对象
    parser = argparse.ArgumentParser(description='python PortScanner.py [IP] [Port]')
    # 保存定义的命令行参数信息
    parser.add_argument('IP', type=str, help='待扫描IP地址')
    parser.add_argument('Port', type=str, help='指定端口')
    '''
    add_argument方法:用于添加命令行参数
    'input'指定的 name:指定命令行参数的名称
    type:指定参数的类型
    help:用于描述该参数的作用
    '''
    # 如果参数是可选参数,使用 - -- 前缀来定义
  
    # parser.add_argument('-P', '--Port', action='store_true', help='待扫描端口' \
    # '[20,21,22] or [1-999]')
    '''
    --output 为可选参数,类型为字符串
    -v 和 --verbose 是同一个可选参数的两种类型,类型为布尔值
    如果用户指定了该参数时,值为 True ,否则为 False
    action='store_true' 表示当指定这个参数时,将其值设置为 True
    '''
  
    args = parser.parse_args()
    # parse_args():解析命令行参数,返回命名空间
    IP = args.IP
    Port = args.Port
    return(IP,Port)
IP,Port = arguments()
ip_port = Queue()
#创建队列
ip = IP
port = Port
# 将make_port_list返回的值传入port_list
port_list = make_port_list(port)
print("total port num:{}".format(len(port_list)))
for port in port_list:
    ip_port.put([ip,port])
  
coroutines()
#开启协程
#将协程设置为每50个创建一次
num = 50
if len(port_list)%num == 0:
#如果port_list中的数量能被50整除
    turn = int(len(port_list)/num)
    #计算需要多少批次
    for i in range(turn):
        coroutines(num)
        #每批处理50个端口
else:
    turn = int(len(port_list)/num)+1
    # 因为不能被整除,所以 批次 = 整数部分 + 1
    for i in range(turn):
        if i == turn-1:
            #如果是最后一批
            coroutines(len(port_list)%num)
            #取余,处理剩余端口
        else:
            coroutines(num)
            #正常处理50个端口

六、效果展示

: (

相关推荐
blasit6 小时前
笔记:Qt C++建立子线程做一个socket TCP常连接通信
c++·qt·tcp/ip
BingoGo2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack4 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo4 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack5 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理5 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
DianSan_ERP5 天前
电商API接口全链路监控:构建坚不可摧的线上运维防线
大数据·运维·网络·人工智能·git·servlet