Python в ИБ. Часть 10. Многопоточный сканер портов
Сегодня мы добавим новые возможности сканеру портов, который мы написали на прошлом уроке.
В теории опишем какие изменения будем производить, какие библиотеки нам понадобятся и что мы в итоге хотим получить.
Теперь мы добавим мультипоточность, то есть наш сканер будет работать не в одном основном потоке, сканирую один порт за другим, а сразу в нескольких потоках (как будто мы запускаем сканирование для каждого порта отдельно в другой консоли). Конечно, если спускаться на уровень ниже, то будет ясно, что многопоточность – это лишь абстракция, но нам это не особо нужно сейчас.
Для добавления многопоточности нам потребуется:
- Подключить библиотеку threading
- Перенести процесс сканирования одного порта в отдельную функцию
- Из библиотеки queue взять класс для простой очереди.
- Добавить функцию, осуществляющую запуск функции сканирования
- Добавить код генерации потоков.
Перейдём к написанию кода. Сразу поменяем секцию импортов, а также добавим несколько новых глобальных переменных.
TCP_TIMEOUT – обозначает максимальное количество секунд, которое сокет будет пытаться установить соединение с портом на целевом сервере.
hostPort_queue – очередь состоящая из кортежей вида (host, port), из неё будут браться данные для запуска функции сканирования.
Идём дальше. Вынесем код сканирования в отдельную функцию.
По сути тот-же самый код, только теперь в функции, которая принимает в качестве аргументов ip-адрес и порт.
Теперь нам нужно сделать функцию, которая будет запускаться в отдельном потоке, брать не отсканированную пару хост-порт из очереди и запускать их сканирования. Назовём такую функцию runner, можно и по другому, обычно такие функции принято называть worker.
Эта функция забирает значения из очереди, после чего запускает их сканирование. После завершения сканирования, поток посылает сигнал, что он был завершён с помощью метода task_done.
Теперь осталось добавить код запуска потоков и инициализации очереди.
Тут всё достаточно просто, создаётся 50 потоков, которые будут принимать значения из создаваемой очереди. Метод join() блокирует ход основного выполнения программы (то есть основной поток), пока не будут завершены все задачи которые есть в очереди, то есть пока не отработает сканирование.
Вот, что получилось в результате:
import socket
import argparse
import threading
from queue import Queue
cheks = {}
TCP_TIMEOUT = 5
hostPort_queue = None
def parsePorts( buf ):
res = []
if "-" in buf:
res = [int(i) for i in range( int( buf.split( '-' )[ 0 ] ), int( buf.split( '-' )[ 1 ] ) + 1 ) ]
else:
res = [ int( buf ) ]
return res
def init_parser():
parser = argparse.ArgumentParser()
parser.add_argument( "ip", type = str,
help = "target ip-addr" )
parser.add_argument( "-p", type=str,
help="set port or range with separator '-'" )
return parser
def scanPort( host, port ):
sock = socket.socket()
sock.settimeout( TCP_TIMEOUT )
try:
sock.connect( ( host, port ) )
cheks[ host + ":" + str( port ) ] = 'up'
except:
cheks[ host + ":" + str( port ) ] = 'down'
sock.close()
def runner():
while 1:
host, port = hostPort_queue.get()
scanPort( host, port )
hostPort_queue.task_done()
if __name__ == "__main__":
args = init_parser().parse_args()
target_ip = args.ip
ports = parsePorts( args.p )
hostPort_queue = Queue()
for _ in range( 50 ):
thread = threading.Thread( target = runner )
thread.daemon = True
thread.start()
for port in ports:
hostPort_queue.put( (target_ip, port) )
hostPort_queue.join()
for i in cheks:
if cheks[ i ] == 'up':
print i, cheks[ i ]
Протестируем.
Запустим сканирование 5000 портов на старом сканере, который делал это в 1 поток и на новом, который делает это в 50 потоков.
Новый сканер справился за 1.6 секунды.
А у старого сканера это заняло бы 19 секунд.
Разница очевидна.