Создание сканера портов

КибербезопасностьBeginner
Практиковаться сейчас

Введение

Добро пожаловать в лабораторную работу по созданию сканера портов.

Сканер портов — это фундаментальный инструмент в сетевой безопасности, используемый для идентификации открытых портов и служб, работающих на целевой машине. Такие инструменты, как Nmap, невероятно мощны, но понимание того, как они работают «под капотом», необходимо любому начинающему специалисту по кибербезопасности или сетевому инженеру.

В этой лабораторной работе вы создадите свой собственный TCP-сканер портов на Python с нуля. Вы начнете с обработки аргументов командной строки, затем реализуете основную логику подключения через сокеты для проверки открытости порта. После этого вы добавите обработку исключений, чтобы сделать инструмент надежным, и, наконец, внедрите многопоточность, чтобы значительно ускорить процесс сканирования. К концу работы у вас будет функциональный, быстрый сканер портов и глубокое понимание сетевого программирования на Python.

Образовательная цель состоит не в том, чтобы заменить зрелые инструменты вроде Nmap, а в том, чтобы показать внутренние механизмы, которые эти инструменты автоматизируют: обработку ввода, попытки подключения, тайм-ауты, интерпретацию результатов и компромиссы в производительности. Как только вы сами создадите базовый сканер, профессиональные инструменты перестанут казаться чем-то загадочным.

Обработка аргументов командной строки

На этом этапе вы заложите основу своего сканера портов, создав Python-скрипт, способный принимать аргументы командной строки. Аргументы командной строки позволяют пользователям указывать целевой IP-адрес и диапазон портов для сканирования без необходимости изменения кода.

Это пример отделения логики инструмента от пользовательского ввода. Хорошие инструменты безопасности пригодны для повторного использования, так как код остается неизменным, а оператор меняет параметры прямо из командной строки.

Мы будем использовать встроенный модуль Python argparse, который упрощает создание удобных интерфейсов командной строки.

Сначала убедитесь, что вы находитесь в директории проекта. Именно здесь вы будете создавать и запускать свой Python-скрипт.

cd ~/project

Создайте новый файл с именем scanner.py с помощью текстового редактора nano:

nano scanner.py

Скопируйте и вставьте следующий код в редактор. Этот код импортирует модуль argparse и определяет функцию для обработки ввода целевого IP-адреса и диапазона портов.

import argparse
import sys

def get_arguments():
    parser = argparse.ArgumentParser(description="A simple TCP port scanner")
    parser.add_argument("-t", "--target", dest="target", help="Target IP address", required=True)
    parser.add_argument("-p", "--ports", dest="ports", help="Port range to scan (e.g., 1-1024)", default="1-1024")

    options = parser.parse_args()
    return options

if __name__ == "__main__":
    options = get_arguments()

    ## Extract the target and ports from the user's input
    target_ip = options.target
    ports_input = options.ports

    print(f"[*] Target set to: {target_ip}")
    print(f"[*] Port range set to: {ports_input}")

Сохраните файл, нажав Ctrl+O, а затем Enter. Если вы хотите сразу перейти к следующему шагу, можете остаться в nano и продолжить редактирование того же файла. Используйте Ctrl+X только тогда, когда захотите вернуться в терминал.

Теперь протестируйте скрипт, запустив его с помощью Python. Мы передадим IP-адрес локального хоста (127.0.0.1) и небольшой диапазон портов (7990-8010).

python3 scanner.py -t 127.0.0.1 -p 7990-8010

Вы должны увидеть вывод, похожий на этот:

[*] Target set to: 127.0.0.1
[*] Port range set to: 7990-8010

Если вы попробуете запустить скрипт без аргумента -t, argparse автоматически сгенерирует сообщение об ошибке и покажет правильный вариант использования, что сделает ваш инструмент более удобным для работы в терминале.

Реализация логики подключения через сокеты

На этом этапе вы реализуете основную функциональность сканера портов: подключение через сокеты.

На уровне протоколов базовое TCP-сканирование просто: нужно попытаться открыть TCP-соединение с целевым портом и посмотреть, успешно ли оно завершилось. Одной этой идеи достаточно, чтобы объяснить поведение целого класса сканеров портов.

Сокет — это конечная точка для отправки или получения данных по компьютерной сети. Чтобы проверить, открыт ли порт, ваш сканер попытается установить TCP-соединение с целевым IP-адресом на определенном порту. Если соединение успешно, порт открыт; если оно завершается с ошибкой или по тайм-ауту, порт закрыт или отфильтрован.

Если scanner.py все еще открыт в nano, продолжайте редактирование. В противном случае откройте его снова:

nano ~/project/scanner.py

Обновите код, включив модуль socket и добавив логику сканирования. Замените все содержимое файла следующим кодом:

import argparse
import sys
import socket

def get_arguments():
    parser = argparse.ArgumentParser(description="A simple TCP port scanner")
    parser.add_argument("-t", "--target", dest="target", help="Target IP address", required=True)
    parser.add_argument("-p", "--ports", dest="ports", help="Port range to scan (e.g., 1-1024)", default="1-1024")
    return parser.parse_args()

def scan_port(ip, port):
    ## Create a new socket using the IPv4 address family and TCP protocol
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    ## Set a timeout of 1 second so the scanner doesn't hang on closed ports
    sock.settimeout(1)

    ## connect_ex returns 0 if the connection was successful
    result = sock.connect_ex((ip, port))
    if result == 0:
        print(f"[+] Port {port} is OPEN")

    ## Always close the socket after using it
    sock.close()

if __name__ == "__main__":
    options = get_arguments()
    target_ip = options.target

    ## Parse the start and end ports from the input string
    start_port, end_port = map(int, options.ports.split('-'))

    print(f"[*] Scanning target {target_ip} from port {start_port} to {end_port}...\n")

    ## Loop through the specified range and scan each port sequentially
    for port in range(start_port, end_port + 1):
        scan_port(target_ip, port)

    print("\n[*] Scan completed.")

Сохраните файл с помощью Ctrl+O и Enter. Вы можете остаться в nano и перейти к следующему шагу, так как скрипт пока не обязательно тестировать, если вы не хотите проверить прогресс.

В этом коде socket.socket(socket.AF_INET, socket.SOCK_STREAM) создает TCP-сокет. Метод connect_ex() особенно полезен для сканеров, так как он возвращает индикатор ошибки (например, 0 при успехе) вместо вызова исключения при сбое подключения, что делает наш код чище.

Запустите обновленный скрипт против вашей локальной машины. В лабораторной среде на порту 8000 работает скрытый веб-сервер, поэтому давайте просканируем диапазон, включающий его:

python3 scanner.py -t 127.0.0.1 -p 7990-8010

Вы должны увидеть вывод, похожий на следующий, где порт 8000 успешно определен как открытый:

[*] Scanning target 127.0.0.1 from port 7990 to 8010...

[+] Port 8000 is OPEN

[*] Scan completed.

В зависимости от лабораторной среды, вы также можете увидеть другие открытые порты. Важный результат для этого шага — появление порта 8000 как открытого.

Обработка исключений при подключении

На этом этапе вы сделаете сканер более надежным, добавив обработку исключений.

Инструменты безопасности большую часть времени работают в несовершенных условиях: неверный ввод, недоступные хосты, медленные сети и прерванные процессы. Обработка исключений — это не украшение, а часть обеспечения работоспособности инструмента в реальных операциях.

Сетевые операции непредсказуемы. Пользователь может ввести неверное имя хоста, сеть может разорваться, или пользователь может захотеть отменить сканирование, нажав Ctrl+C. Без надлежащей обработки исключений ваш Python-скрипт аварийно завершится и выведет пользователю некрасивую трассировку ошибки.

Мы будем использовать блоки try-except для перехвата этих специфических ошибок и их корректной обработки.

Если вы все еще редактируете scanner.py, продолжайте работу в той же сессии nano. В противном случае откройте его снова:

nano ~/project/scanner.py

Обновите код, добавив обработку исключений. Замените содержимое следующей обновленной версией:

import argparse
import sys
import socket

def get_arguments():
    parser = argparse.ArgumentParser(description="A simple TCP port scanner")
    parser.add_argument("-t", "--target", dest="target", help="Target IP address", required=True)
    parser.add_argument("-p", "--ports", dest="ports", help="Port range to scan (e.g., 1-1024)", default="1-1024")
    return parser.parse_args()

def scan_port(ip, port):
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(1)
        result = sock.connect_ex((ip, port))
        if result == 0:
            print(f"[+] Port {port} is OPEN")
        sock.close()
    except socket.gaierror:
        ## This exception is raised for address-related errors, like an invalid hostname
        print("\n[-] Hostname could not be resolved.")
        sys.exit(1)
    except socket.error:
        ## This catches general socket errors
        print("\n[-] Could not connect to server.")
        sys.exit(1)

if __name__ == "__main__":
    options = get_arguments()
    target_ip = options.target

    try:
        start_port, end_port = map(int, options.ports.split('-'))
    except ValueError:
        print("[-] Invalid port format. Please use start-end (e.g., 1-1024)")
        sys.exit(1)

    print(f"[*] Scanning target {target_ip} from port {start_port} to {end_port}...\n")

    try:
        for port in range(start_port, end_port + 1):
            scan_port(target_ip, port)
    except KeyboardInterrupt:
        ## This catches the Ctrl+C signal from the user
        print("\n[-] Scan canceled by user. Exiting...")
        sys.exit(1)

    print("\n[*] Scan completed.")

Сохраните файл с помощью Ctrl+O и Enter. Если вы хотите немедленно протестировать обработку исключений, выйдите с помощью Ctrl+X и выполните команды ниже. Если нет, можете продолжить работу до последнего шага и выйти из nano только тогда, когда будете готовы запустить готовый сканер.

Теперь протестируем обработку исключений. Попробуйте запустить скрипт с неверным форматом портов, чтобы вызвать ValueError:

python3 scanner.py -t 127.0.0.1 -p 8000,8001

Вы должны увидеть чистое сообщение об ошибке вместо аварийного завершения Python:

[-] Invalid port format. Please use start-end (e.g., 1-1024)

Затем запустите сканирование широкого диапазона портов и во время его выполнения нажмите Ctrl+C на клавиатуре, чтобы отменить его:

python3 scanner.py -t 127.0.0.1 -p 1-5000

Когда вы нажмете Ctrl+C, скрипт перехватит KeyboardInterrupt и корректно завершится:

[*] Scanning target 127.0.0.1 from port 1 to 5000...

[-] Scan canceled by user. Exiting...

Если сканирование завершится раньше, чем вы успеете его прервать, запустите его снова с более широким диапазоном, например 1-9000, а затем нажмите Ctrl+C во время работы.

Добавление многопоточности для скорости

На этом последнем этапе вы решите главную проблему вашего текущего сканера: скорость.

Сейчас ваш сканер проверяет порты по одному. Если порт отфильтрован и на тайм-аут уходит 1 секунда, сканирование 1000 портов займет 1000 секунд. Чтобы исправить это, мы будем использовать многопоточность. Многопоточность позволяет программе выполнять несколько задач одновременно. Вместо того чтобы ждать завершения проверки одного порта перед началом следующего, мы будем создавать новый поток для каждого порта.

Если вы оставили scanner.py открытым, продолжайте редактирование. В противном случае откройте его снова:

nano ~/project/scanner.py

Импортируйте модуль threading и обновите основной цикл для использования потоков. Замените содержимое этой финальной версией:

import argparse
import sys
import socket
import threading

def get_arguments():
    parser = argparse.ArgumentParser(description="A simple TCP port scanner")
    parser.add_argument("-t", "--target", dest="target", help="Target IP address", required=True)
    parser.add_argument("-p", "--ports", dest="ports", help="Port range to scan (e.g., 1-1024)", default="1-1024")
    return parser.parse_args()

def scan_port(ip, port):
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(1)
        result = sock.connect_ex((ip, port))
        if result == 0:
            print(f"[+] Port {port} is OPEN")
        sock.close()
    except (socket.gaierror, socket.error):
        pass

if __name__ == "__main__":
    options = get_arguments()
    target_ip = options.target

    try:
        start_port, end_port = map(int, options.ports.split('-'))
    except ValueError:
        print("[-] Invalid port format. Please use start-end (e.g., 1-1024)")
        sys.exit(1)

    print(f"[*] Scanning target {target_ip} from port {start_port} to {end_port}...\n")

    try:
        ## Create a list to keep track of active threads
        threads = []

        for port in range(start_port, end_port + 1):
            ## Create a new thread for each port scan
            thread = threading.Thread(target=scan_port, args=(target_ip, port))
            threads.append(thread)
            thread.start()

        ## Wait for all threads to complete before exiting
        for thread in threads:
            thread.join()

    except KeyboardInterrupt:
        print("\n[-] Scan canceled by user. Exiting...")
        sys.exit(1)

    print("\n[*] Scan completed.")

Сохраните файл (Ctrl+O, Enter) и выйдите (Ctrl+X).

Обратите внимание, как мы добавили threading.Thread(target=scan_port, args=(target_ip, port)). Это указывает Python запускать функцию scan_port в фоновом режиме. Цикл thread.join() гарантирует, что основная программа дождется завершения всех фоновых сканирований перед выводом сообщения "Scan completed."

Теперь запустите сканирование большого диапазона портов. Вы заметите, что оно завершается гораздо быстрее, чем предыдущая последовательная версия.

python3 scanner.py -t 127.0.0.1 -p 1-9000

Вывод будет выглядеть примерно так:

[*] Scanning target 127.0.0.1 from port 1 to 9000...

[+] Port 8000 is OPEN

[*] Scan completed.

В зависимости от служб, работающих в лабораторной среде, вы можете увидеть дополнительные открытые порты помимо 8000. Это нормально.

Вы успешно создали быстрый многопоточный TCP-сканер портов на Python.

Резюме

В этой лабораторной работе вы успешно создали собственный многопоточный TCP-сканер портов на Python с нуля. Вы начали с изучения того, как анализировать аргументы командной строки с помощью модуля argparse, чтобы сделать ваш инструмент гибким. Затем вы погрузились в сетевое программирование, реализовав подключения через сокеты для обнаружения открытых портов. Чтобы сделать инструмент надежным, вы добавили обработку исключений для корректного управления сетевыми ошибками и прерываниями со стороны пользователя. Наконец, вы внедрили многопоточность, превратив медленный последовательный скрипт в высокоскоростной параллельный сканер.

Этот практический опыт раскрывает внутреннее устройство профессиональных инструментов сетевого сканирования и создает прочный фундамент для дальнейшего изучения сетевой безопасности и разработки на Python.