Существует множество различных сканеров портов, но настоящий тру хакер должен знать, как работают подобные инструменты. Так что в этой статье я покажу, как написать сканер портов на Python.
Еще по теме: Сканер уязвимостей из Nmap с помощью Vulscan
Взаимодействие устройств в сети
Для взаимодействие подключенных к сети устройств используются сокеты, которые легче изобразить ввиде трубы. Где концы трубы — это и есть наши сокеты. Один выход распологается на одном компе, другой — на другом, а приложения помещают или читают данные из этой «трубы».
Есть два вида сокетов:
- TCP-сокет гарантирует, что пакеты будут без потерь.
- UDP-сокеты работают на скорость, но с возможной потерей пакетов.
Кроме этого TCP можно сказать незащищенный, а UDP защишенный, но это не имеет значения в данном случае.
При передачи видеопотока испульзуется UDP. Если с каналом звязи будут какие-то временные проблемы, просто потеряются пакеты, а оставшиеся будут переданы в срок, и все равно будет хоть какая-то картинка .
Как написать сканер портов на Python на сокетах
Данный сканер портов — самый очевидный и простой. Чтобы определить, какие порты открыты, надо просто попытаться к ним поочередно подключиться и посмотреть результат.
Для этого примера будут использовать Python 3.10 и мой любимый PyCharm.
Создадим приложение и добавим основные модули:
1 2 3 |
import socket from datetime import datetime import sys |
Запоминаем время запуска приложения — в будущем это понадобится для определения времени сканирования.
1 |
start = datetime.now() |
Сохранят пары из названий сервисов и портов можно прямо в коде. Если будет желании можете улучшить данный метод, до файлов JSON.
1 2 3 4 5 6 7 8 9 10 |
ports = { 20: "FTP-DATA", 21: "FTP", 22: "SSH", 23: "Telnet", 25: "SMTP", 43: "WHOIS", 53: "DNS", 80: "http", 115: "SFTP", 123: "NTP", 143: "IMAP", 161: "SNMP", 179: "BGP", 443: "HTTPS", 445: "MICROSOFT-DS", 514: "SYSLOG", 515: "PRINTER", 993: "IMAPS", 995: "POP3S", 1080: "SOCKS", 1194: "OpenVPN", 1433: "SQL Server", 1723: "PPTP", 3128: "HTTP", 3268: "LDAP", 3306: "MySQL", 3389: "RDP", 5432: "PostgreSQL", 5900: "VNC", 8080: "Tomcat", 10000: "Webmin" } |
Преобразовываем переданный аргумент в IP-адрес. Для этого подсунем первый аргумент командной строки сканера портов функции socket.gethostbyname(string) — как бонус получим разрешение DNS, если передан не IP-адрес, а доменное имя.
1 2 |
host_name = sys.argv[1] ip = socket.gethostbyname(host_name) |
Далее в цикле обходим все порты из списка и проверяем возможность подключения к ним. В случае если порт закрыт, будет вызвано исключение, которое мы перехватим, и приложение не вылетит.
В завершении сохраним время окончания отобразим время сканирования.
1 2 3 |
ends = datetime.now() print("<Time:{}>".format(ends - start)) input("Press Enter to the exit....") |
Теперь, для проверки работы сканера портов, открываем терминал, переходим в папку со сканером и выполняем команду:
1 |
python.exe socket.py 45.33.32.156 |
Разумеется, вместо IP-адреса можно указать любой хост.
Давайте посмотрим, как это выглядит со стороны сервера. Для этого заюзаем Netcat. Скачиваем и запускаем его таким образом:
1 |
ncat.exe -lnvp 123 |
В соседнем терминале запускаем сканер портов:
1 |
ipconfig /all |
В другом в окне с Netcat произойдет это.

На скрине видно, что было установлено полноценное соединение, скрытое сканирование не получится.
Также давайте теперь попробуем просканировать сервер scanme.nmap.org:
Сканер портов выявил два открытых порта — 22 и 80. Но вам придет идет просканировать данный хост с помощью Nmap, вы увидите большее количество открытых портов.
Почему так происходит? Популярный Nmap проходит по большему списку портов, чем наш сканер портов на Python, и, поэтому, находит больше. Если добавить эти порты в наш сканер, он тоже их отыщет.
Понятно, что такой сканер вряд ли применим в реальных условиях, кроме совсем уж экзотических: например, когда сканировать надо с машины, на которую невозможно доставить полноценный сканер, но на которой уже есть Python.
Давайте лучше попробуем сделать сканер, который не станет так палиться и работать будет куда быстрее.
Оптимизация сканера портов на Python
Чтобы сделать наш сканер более эффективным и скрытным, нужно не устанавливать соединение, ведь это требует целых три пакета (трехэтапное рукопожатие) плюс дополнительное время, чтобы закрыть соединение. Но как узнать, открыт ли порт, не пытаясь подключиться?
Оказывается, метод есть. Заключается он в том, что необязательно завершать все три этапа рукопожатия — тогда соединение не будет установлено, а значит, не нужно будет и закрывать его.
Узнать, открыт ли порт, можно в ответе на первый же пакет, отправленный серверу, так что здорово сэкономите время, если искусственно делать неполное рукопожатие.
Бонусом получаем скрытность: если соединение не установлено, то программы на целевом хосте и не узнают, что их кто‑то сканирует.
Как работает трехэтапное рукопожатие? Сначала инициатор соединения отправляет другой машине на интересующий порт пакет с флагом SYN. Если порт открыт, должен прийти ответ с флагами SYN и ACK. Если порт закрыт, поведение может быть разным, но в нормальном случае в ответе должен быть флаг RST.
Если инициатор еще не раздумал открывать соединение, он должен отправить еще один пакет, но уже только с флагом ACK, после чего соединение будет считаться установленным.
Ключевой момент тут в том, что отправлять финальный ACK совершенно не обязательно, так как узнаем статус порта мы уже после второго пакета, а третий заберет у нас драгоценное время и лишит скрытности.
Чтобы закрыть соединение, нужно отправить пакет с флагами FIN и ACK. Эта информация нам сегодня не понадобится и нужна только для более полного понимания, что происходит с TCP-соединением на разных этапах его жизни.
SYN-сканер на Python
Создаем новый скрипт на Python и импортируем нужные модули:
1 2 3 |
from scapy.layers.inet import ICMP, IP, TCP, sr1 import socket from datetime import datetime |
Если вы получили ошибку вида:
1 |
ModuleNotFoundError: No module named 'scapy' |
Значит, нужно доустановить модуль scapy из PyPI:
1 |
pip install scapy |
В этот раз перед сканированием неплохо бы проверить, доступна ли целевая машина. Для этого можно отправить ICMP Echo Request (в простонародье — пинг):
1 2 3 4 5 6 7 |
start = datetime.now() # Здесь проверяется, в сети ли сервер def icmp_probe(ip): icmp_packet = IP(dst=ip) / ICMP() # Отправка и прием одного пакета resp_packet = sr1(icmp_packet, timeout=10) return resp_packet is not None |
Теперь напишем саму функцию сканирования. Она будет обходить все порты, слать SYN-пакеты и ожидать ответа.
1 2 3 4 5 6 7 8 9 10 11 |
def syn_scan(ip, ports): # Проходимся по каждому порту for port in ports: # Флаг S означает SYN-пакет syn_packet = IP(dst=ip) / TCP(dport=port, flags="S") # Время ожидания пакета можно ставить свое resp_packet = sr1(syn_packet, timeout=10) if resp_packet is not None: if resp_packet.getlayer('TCP').flags & 0x12 != 0: print(f"{ip}:{port} is open/{resp_packet.sprintf('%TCP.sport%')}") ends = datetime.now() |
Порт будет считаться открытым, если в ответном пакете установлены флаги SYN и ACK (или хотя бы один из них).
Чтобы узнать, какой сервис соответствует какому порту, в этот раз мы не будем изобретать свой список, а воспользуемся готовым, который предоставляет сам Scapy. Можно использовать функцию resp_packet.show(), в выводе которой указывается сервис, соответствующий целевому порту.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
###[ IP ]### version = 4 ihl = 5 tos = 0x0 len = 44 id = 0 flags = DF frag = 0 ttl = 57 proto = tcp chksum = 0x71c4 src = ip_цели dst = ip_атакующего \options \ ###[ TCP ]### sport = ssh dport = ftp_data seq = 986259409 ack = 1 dataofs = 6 reserved = 0 flags = SA window = 65535 chksum = 0x6d61 urgptr = 0 options = [('MSS', 1436)] |
Осталось всего ничего: скормить функции список портов для проверки и реализовать ввод информации о цели. Этим и займется функция main. В этот раз сделаем интерактивный ввод адреса цели и предварительную проверку доступности цели перед сканированием.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
if __name__ == "__main__": name = input("Hostname / IP Address: ") # Узнаем IP цели ip = socket.gethostbyname(name) # Обозначаем порты для сканирования ports = [20, 21, 22, 23, 25, 43, 53, 80, 115, 123, 143, 161, 179, 443, 445, 514, 515, 993, 995, 1080, 1194, 1433, 1723, 3128, 3268, 3306, 3389, 5432, 5060, 5900, 8080, 10000] # Перехватываем исключения в момент, когда заканчивается кортеж try: # Если не удалось подключиться к серверу, выводим ошибку if icmp_probe(ip): syn_ack_packet = syn_scan(ip, ports) syn_ack_packet.show() else: print("Failed to send ICMP packet") except AttributeError: print("Scan completed!") print("<Time:{}>".format(ends - start)) |
В массив ports нужно записать порты, которые вы собираетесь сканировать. Необязательно перебирать все: даже Nmap по умолчанию сканирует только топ-1000 самых популярных, а остальные ускользают и от него, если явно не затребовать полное сканирование ( -p-).
На последней итерации цикла происходит ошибка (массив заканчивается), а значит, можно использовать это событие, чтобы сообщить о завершении работы.
Поскольку мы теперь запрашиваем адрес в самом скрипте, его можно запускать без всяких параметров.
1 |
python.exe synscaner.py |
Если снова открыть Netcat и просканировать его, то сканер покажет открытый порт, но никакого подключения не случится. Проверка на скрытность пройдена!

Теперь давайте просканим какой‑нибудь публичный сервер (тот же scanme.nmap.org) и с помощью Wireshark посмотрим, какие пакеты ходят. Запустим Wireshark, а затем и сканирование.
Чтобы не запутаться и не утонуть в потоке информации, который извергает Wireshark, давайте применим фильтр. Достаточно будет показать пакеты, которые отправлены или получены от хоста с IP 45.33.32.156.
1 |
ip.addr == 45.33.32.156 |
После применения фильтра окно Wireshark выглядит так.

Первым в списке видим ICMP-пакет (пинг), который отправлялся для проверки доступности. Дальше идут пакеты с флагами RST/ACK (закрытые порты) и SYN/ACK (открытые порты).
Заключение
В статье рассмотрели два метода сканирования: с установлением соединения и без него. За рамками статьи осталось сканирование портов UDP, так как оно намного сложнее и нужно реже. Тем не менее Scapy позволит реализовать и его, если вам это вдруг понадобится.
Не забывайте, что подобные программы — это в первую очередь опыт, а не замена готовым инструментам. Именно знание того, как все устроено внутри, делает вас настоящим тру хакером!
РЕКОМЕНДУЕМ:
Hello . I need minute your attention.
Круто