使用python实现端口扫描的脚本,使用到optparse模块解析命令
大部分解释已写入脚本注释。文件名为portscan.py
'''
命令解析,多线程,端口扫描
----------author:Bluecap-------------
'''
#命令行参数解析模块
from optparse import OptionParser
import queue
import socket
import re
import threading
parser=OptionParser('Usage: %prog -i<target ip>')
#添加命令,这里可以选定类型,当然后面的正则可能没有必要
parser.add_option('-i',dest='ip',type='string',help='specify target ip')
#端口为范围,或者单个。单个单线程,多个多线程
parser.add_option('-p',dest='port',type='string',help='specify target port')
parser.add_option('--thread',dest='thread',type='int',help='specify thread num')
#接上--brief只显示开放端口,store_true为加上--brief时brief为True,否则为False
parser.add_option('--brief',dest='brief',action='store_true',help='brief mode')
#全局参数控制输出
# flag=False
#解析命令
opts,argv=parser.parse_args()
# print('opts:',opts)
# print('argv:',argv)
# print(type(opts))
# print(opts.ip)
# print(opts.port)
# print(type(opts.thread))
'''
opts: {'ip': None, 'port': None, 'thread': None}
E:\python作业2>python portscan.py -i 120.0.0.1 -p 21 --thread 4
opts: {'ip': '120.0.0.1', 'port': '21', 'thread': 4}
argv: []
'''
def print_help():
print('Usage: %prog -i<target ip> -p<target port> --thread<thread num> --brief<show detail>')#至少要输入ip和端口
#单线程扫描 单个端口或者端口范围
def singleThread(ip,ports):
try:
port=int(ports)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
if s.connect_ex((ip,port))==0:
print('|{:^16}|{:^16}|{:^16}|'.format(ip,port,'开放'))
else:
print('|{:^16}|{:^16}|{:^16}|'.format(ip,port,'关闭'))
except:
pre_port=int(opts.port.split('-')[0])
suf_port=int(opts.port.split('-')[1])
if pre_port==suf_port:
port=pre_port
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#连接成功返回0,失败返回编码
if s.connect_ex((ip,port))==0:
print('|{:^16}|{:^16}|{:^16}|'.format(ip,port,'开放'))
else:
print('|{:^16}|{:^16}|{:^16}|'.format(ip,port,'关闭'))
elif pre_port<suf_port:
try:
for port in range(pre_port,suf_port+1):
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#连接成功返回0,失败返回编码
if s.connect_ex((ip,port))==0:
print('|{:^16}|{:^16}|{:^16}|'.format(ip,port,'开放'))
else:
#flag=opts.brief
if opts.brief:
pass#加上breif后不输出扫描信息
else:
print('|{:^16}|{:^16}|{:^16}|'.format(ip,port,'关闭'))
except Exception as e:
print(e)
else:
print('端口范围错误')
exit(1)
#面向对象的方式进行调用
# ip=opts.ip
# ports=opts.port.split('-')
def scanport(ip ,port):
#给线程上锁,防止取出重复端口
with threading.Lock():#线程锁,自动加自动解
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#TCP连接
if s.connect_ex((ip,port))==0:
print('|{:^16}|{:^16}|{:^16}|'.format(ip,port,'开放'))
else:
if opts.brief:
pass
else:
print('|{:^16}|{:^16}|{:^16}|'.format(ip,port,'关闭'))
#多线程端口扫描
def MultiThread(ip,port,threads):
pre_port=int(opts.port.split('-')[0])
suf_port=int(opts.port.split('-')[1])
#print('pre_port:',pre_port)
if pre_port==suf_port:
singleThread(ip,pre_port)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
banner()
#连接成功返回0,失败返回编码
if s.connect_ex((ip,port))==0:
print('|{:^16}|{:^16}|{:^16}|'.format(ip,port,'开放'))
else:
print('|{:^16}|{:^16}|{:^16}|'.format(ip,port,'关闭'))
elif pre_port<suf_port:
port_num=suf_port-pre_port
#print('port_num:',port_num)
#把端口放入队列中,每个线程结束后取出一个
queue_ports=queue.Queue(port_num+1)
for port in range(pre_port,suf_port+1):
queue_ports.put(port)
#print('queue_ports size:',queue_ports.qsize())
#print('queue_ports size:',queue_ports.qsize())
#扫描的端口数小于用户输入的线程数,让线程数等于端口数
if threads>0 and threads<=20:
if threads>=port_num:
#print('多线程第一种情况')
threads=port_num
for t in range(threads):
try:
thread=threading.Thread(target=scanport,args=(ip,pre_port+t),daemon=True)#传递参数,设置为守护线程
thread.start()
#print(f'线程{t+1}开始启动...')
except threading.ThreadError as e:
print(f'线程{t+1}执行异常')
else:
#print('多线程第二种情况')
#初始时创建四个线程
for th in range(threads):
try:
thread=threading.Thread(target=scanport,args=(ip,queue_ports.get()))#如果设置成守护线程可能有几个端口就有几个线程
thread.start()
#print(f'线程{th+1}开始启动...')
except threading.ThreadError as e:
print(f'线程{th+1}执行异常')
else:#执行完for后执行else
while True:
# if queue_ports.empty():
# break
thread_number=threading.active_count()#等同于threading.enumerate()
surplus_ports=queue_ports.qsize()#当前未扫描端口数
#print('surplus_ports:',surplus_ports)
if surplus_ports==0:
break
#print('未扫描端口数:',surplus_ports)
#保证有4个进程在运行,当扫描的端口少于4个时,且线程够用,就不再增加线程
if thread_number<(threads+1):
#补齐
gap_thread=threads-thread_number#差的线程数
for i in range(gap_thread):
thread=threading.Thread(target=scanport,args=(ip,queue_ports.get()))
thread.start()
#print(f'当前线程数为:{len(threading.enumerate())}')
else:
#等线程结束后再增加线程
continue
else:
print('线程数最大为20')
exit(1)
if queue_ports.empty():
#print('扫描完成...')
exit(1)
def banner():
print('+'+'-'*50+'+')
print('|{:^16}|{:^16}|{:^16}|'.format('ip','port','status'))
print('+'+'-'*50+'+')
if __name__=='__main__':
#None为python空类型
if opts.ip is None or opts.port is None:
print_help()
else:
#检查IP格式
ip=re.match('^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$',opts.ip)#匹配成功返回ip,不成功返回None
if ip is None:
print('ip格式错误')
exit(1)
else:
ip=ip.group()
try:#检查端口格式
port=int(opts.port)
except:
#端口格式报错,说明输入是端口范围
ports=re.match('^\d{1,5}-\d{1,5}$',opts.port)
if ports is None:
print('端口格式错误')
exit(1)
else:
#print('端口格式正确')
ports=ports.group()
threads=opts.thread
#print('threads:',type(threads))
if threads is None:#不输入线程数默认使用单线程
print('单线程扫描...')
banner()
singleThread(ip,ports)
else:
if threads>0 and threads<=20:
print('多线程扫描...')
banner()
MultiThread(ip,ports,threads)
print('-'*50)
print('扫描完成...')
else:
print('线程数错误,最大为20')
exit(1)
else:
banner()
#为单端口
singleThread(ip,port)
#只剩下主线程,结束程序
if threading.enumerate()==1:
exit(1)
'''
使用示例:
python portscan.py -i 127.0.0.1 -p 440-500 --thread 10 --brief使用10个线程扫描,只显示开放端口
python portscan.py -i 127.0.0.1 -p 440-500 --thread 10 使用10个线程扫描,显示所有端口
python portscan.py -i 127.0.0.1 -p 440-500不加线程参数,默认使用单线程
python portscan.py -i 127.0.0.1 -p 440-440只扫描440端口
python portscan.py -i 127.0.0.1 -p 445只扫描445端口
'''
练习使用python optparse写的一个作业,粗糙还望见谅!目前还存在Bug,也暂时还找不出问题所在。有需求的大佬可以进一步更改以实现更多的功能,比如说扫描ip段。