Анализ TCP-хендшейка в Wireshark: почему Python-сканер теряет пакеты при сканировании

Полгода назад мой SYN-сканер на Python нашёл 37 открытых портов из 100 реально существующих. Nmap на той же машине, в той же подсети — все 100. Первая мысль: фаервол режет, IDS блокирует, nmap «знает какой-то трюк». Реальная причина стала видна только в Wireshark — анализаторе сетевых пакетов с графическим интерфейсом: скрипт отправлял SYN-пакеты быстрее, чем ядро ОС обрабатывало входящие ответы, и операционная система сбрасывала их RST-пакетами ещё до того, как Python-код успевал прочитать хоть один SYN-ACK. Эта статья — пошаговый анализ TCP-хендшейка в Wireshark: от эталонного захвата до диагностики конкретных потерь и сравнения вашего скрипта с nmap на уровне отдельных пакетов.
TCP three-way handshake — разбор трёх пакетов, на которых держится сканирование
TCP (Transmission Control Protocol) — протокол транспортного уровня, который гарантирует доставку данных через установление соединения. Прежде чем передать хоть один байт полезной информации, TCP выполняет three-way handshake — «рукопожатие из трёх шагов». Открываете веб-страницу, подключаетесь по SSH, сканируете порт — за кулисами одна и та же процедура.
Шаг 1 — SYN. Клиент отправляет пакет с флагом SYN (synchronize — «синхронизация»). В пакете указан начальный sequence number (ISN, Initial Sequence Number) — случайное число, от которого будет вестись нумерация байтов. Пакет говорит серверу: «хочу установить соединение, моя нумерация начинается с N».
Шаг 2 — SYN-ACK. Если порт открыт и сервер готов принять соединение, он отвечает пакетом с двумя флагами: SYN (свой ISN) и ACK (acknowledgment — подтверждение). Поле acknowledgment number равно ISN клиента + 1: «твой SYN я получил, моя нумерация начинается с M».
Шаг 3 — ACK. Клиент подтверждает получение серверного SYN-ACK. Соединение установлено, обе стороны знают начальные sequence numbers друг друга. Можно передавать данные.
Три альтернативных сценария, которые критичны для сканирования:
- Порт закрыт — сервер отвечает пакетом с флагом RST (reset — сброс): «на этом порту никто не слушает».
- Порт фильтруется — ответа нет вообще, пакет уходит «в пустоту», клиент ждёт до таймаута.
- SYN scan (полуоткрытое сканирование) — клиент отправляет SYN, получает SYN-ACK, но вместо завершающего ACK шлёт RST. Соединение не устанавливается до конца, зато информацию о состоянии порта мы получаем. Именно так работает
nmap -sS.
Эти три сценария — SYN-ACK, RST, тишина — определяют результат любого порт-сканера. Nmap, Masscan, самописный скрипт на Scapy (Python-библиотека для создания и отправки произвольных сетевых пакетов) — все отправляют SYN и классифицируют ответ. Разница в том, как они обрабатывают ответы, когда тех приходят сотни в секунду.
С точки зрения пентеста захват и анализ сетевых пакетов — техника Network Sniffing (T1040) по классификации MITRE ATT&CK (открытая база тактик и техник атак; T-коды вроде T1040 — её идентификаторы). Техника входит в тактики Credential Access и Discovery. Здесь мы используем её не для перехвата чужих данных, а для отладки собственного инструмента — но tcpdump (утилита захвата пакетов из командной строки) и Wireshark те же самые. Кстати, запуск tcpdump детектируется: в SigmaHQ (открытый репозиторий правил обнаружения) есть правило lnx_auditd_network_sniffing.yml, которое срабатывает через аудит-логи Linux. Полезно знать, если вы работаете в инфраструктуре с мониторингом.
Разбор TCP-соединения в Wireshark на эталонном захвате
Прежде чем искать баги в собственном скрипте, стоит увидеть, как выглядит нормальный хендшейк. Сделаем эталонный захват: подключимся к известному открытому порту стандартным инструментом и изучим результат.
Захват трафика tcpdump для анализа
Что нужно: Linux-машина (Kali, Ubuntu, Parrot) с установленными tcpdump и Wireshark. Права sudo или root. Сетевой интерфейс, через который идёт трафик (узнать имя: ip a).
sudo tcpdump -i eth0 -nn -w /tmp/handshake.pcap \
"host 93.184.216.34 and port 80" &
curl -s -o /dev/null http://example.com
kill %1
По шагам. Первая строка запускает tcpdump в фоне: -i eth0 — интерфейс (замените на свой), -nn — не резолвить DNS и номера портов (без этого tcpdump сам создаёт DNS-запросы и засоряет захват), -w — писать в файл формата pcap. BPF-фильтр "host 93.184.216.34 and port 80" отсекает весь посторонний трафик. Вторая строка — curl делает HTTP-запрос к example.com, создавая полноценное TCP-соединение. Третья — останавливает фоновый tcpdump.
Как проверить результат: файл /tmp/handshake.pcap должен весить 2–10 КБ. Пустой файл — проверяйте имя интерфейса и IP.
Открываем: wireshark /tmp/handshake.pcap. В списке пакетов первые три строки — three-way handshake. Колонка Info покажет:
- Пакет 1: ваш IP → 93.184.216.34 —
[SYN] Seq=0 - Пакет 2: 93.184.216.34 → ваш IP —
[SYN, ACK] Seq=0 Ack=1 - Пакет 3: ваш IP → 93.184.216.34 —
[ACK] Seq=1 Ack=1
Wireshark по умолчанию показывает относительные sequence numbers (начиная с нуля), а не реальные 32-битные ISN. Для наших целей так удобнее.
Дальше идут HTTP-данные (GET-запрос, ответ 200 OK) и завершение через FIN или RST. Запомните эту картину — с ней будете сравнивать «сломанный» трафик своего скрипта.
Фильтры Wireshark для TCP SYN ACK и диагностики
Wireshark работает с двумя типами фильтров. Capture filters применяются при записи (синтаксис BPF, такой же как в tcpdump). Display filters применяются к уже записанному дампу и имеют более гибкий синтаксис — их вводят в поле фильтра в верхней части окна Wireshark. Для анализа пакетов при пентесте нужны именно display filters.
Фильтры для разбора TCP-хендшейка:
tcp.flags.syn==1 && tcp.flags.ack==0— только начальные SYN (запросы на соединение). Каждый такой пакет — одна попытка «постучаться» в порт. Количество видно в строке «Displayed» внизу окна.tcp.flags.syn==1 && tcp.flags.ack==1— только SYN-ACK (ответы открытых портов). Сравните количество с числом SYN: если SYN отправлено 1000, а SYN-ACK пришло 300, значит 700 пакетов потеряны или отфильтрованы.tcp.flags.rst==1— RST-ответы. Много RST от вашего IP — ядро ОС сбрасывает чужие ответы (об этом ниже).tcp.analysis.retransmission— пакеты, отправленные повторно из-за отсутствия подтверждения. Массовые ретрансмиссии — первый признак перегрузки.tcp.analysis.lost_segment— Wireshark обнаружил разрыв в sequence numbers, часть данных не дошла. Флаг выставляется, когда ожидаемый следующий sequence number не совпадает с реальным.
Для изоляции трафика конкретного хоста: ip.addr == 192.168.1.100 && tcp.flags.syn==1. Логические операторы: && (и), || (или), ! (отрицание).
Ещё одна полезная метрика — RTT (Round-Trip Time, время прохождения пакета «туда-обратно»). Начальный RTT определяется по разнице timestamps между SYN и SYN-ACK: если SYN ушёл в 14:00:00.001, а SYN-ACK пришёл в 14:00:00.045 — RTT равен 44 мс. Для получения начального RTT достаточно поставить Time Reference (правый клик → Set Time Reference) на SYN-пакет и прочитать относительное время у ACK. Для анализа RTT на протяжении всей сессии: Statistics → TCP Stream Graphs → Round Trip Time.
Python сканер портов теряет пакеты: причины и диагностика в Wireshark
Теперь откройте Wireshark и запустите собственный скрипт. Разница с эталонным захватом будет видна в первые секунды.
Причины потери пакетов при сканировании сети
Из опыта отладки собственных сканеров — четыре типичных причины потерь, каждая оставляет свой след в Wireshark:
1. Слишком агрессивная скорость отправки. Скрипт рассылает тысячи SYN за секунду, ядро ОС не успевает обработать входящие SYN-ACK. В Wireshark картина характерная: плотная пачка исходящих SYN без пауз, входящие SYN-ACK появляются позже и в меньшем количестве. Фильтр tcp.analysis.lost_segment подсветит пропуски в нумерации, tcp.analysis.retransmission покажет повторные попытки, которые ядро генерирует автоматически.
2. Короткий таймаут ожидания. Скрипт ждёт ответ 0.3 секунды, а RTT до цели через два хопа составляет 150 мс с джиттером до 500 мс — часть ответов не успевает прийти. Nmap по умолчанию замеряет RTT первых ответов и адаптирует таймауты. Самописный скрипт обычно использует жёсткое timeout=0.5.
3. Ядро ОС отправляет RST за вас. Самый коварный баг для тех, кто только начинает писать сканеры. Если скрипт использует raw sockets (через Scapy или socket с SOCK_RAW), ядро Linux не знает о вашем «соединении». Приходит SYN-ACK — ядро видит ответ на несуществующее подключение и автоматически генерирует RST. В Wireshark это выглядит как цепочка SYN → SYN-ACK → RST, где RST уходит через десятки микросекунд — быстрее любого пользовательского кода.
Решение: заблокировать исходящие RST правилом iptables — sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST -d <target> -j DROP. Без этого raw-socket сканер систематически «не видит» открытые порты.
4. Rate limiting на стороне цели. Фаерволы и IDS/IPS ограничивают поток SYN-пакетов с одного IP. В Wireshark: SYN без ответа — ни SYN-ACK, ни RST. Фильтр для обнаружения: tcp.flags.syn==1 && tcp.flags.ack==0 && !tcp.analysis.retransmission покажет «первичные» SYN, а отсутствие ответных пакетов от цели подтвердит фильтрацию на дальнем конце.
TCP retransmission — анализ повторных передач
TCP-стек повторно отправляет сегмент, если не получил подтверждение (ACK) в ожидаемое время. Wireshark различает несколько типов ретрансмиссий, и для диагностики важно понимать разницу:
- TCP Retransmission — стандартная повторная передача по истечении RTO (Retransmission Timeout). Десятки таких пакетов — цель не успевает отвечать или сеть перегружена.
- TCP Fast Retransmission — повторная передача, вызванная тремя дублирующими ACK. Более быстрый механизм восстановления. По документации Wireshark, этот флаг приоритетнее обычного Retransmission и Out-Of-Order.
- TCP Spurious Retransmission — повторная отправка сегмента, который уже был подтверждён. Часто возникает на нестабильных каналах.
- TCP Out-Of-Order — сегмент пришёл не в той последовательности. Характерен для маршрутов с асимметрией.
Комбинированный фильтр для всех проблемных пакетов: tcp.analysis.retransmission || tcp.analysis.fast_retransmission || tcp.analysis.lost_segment. В строке состояния Wireshark поле «Displayed» покажет их число — соотнесите с общим количеством пакетов в захвате.
Ещё один полезный инструмент — Statistics → Conversations → TCP. Здесь видно каждый TCP-поток отдельно: сколько байтов в каждом направлении, есть ли ответ. Для сканера большинство «разговоров» должны состоять из 2–3 пакетов (SYN + SYN-ACK + RST или SYN + RST). Если видите conversations с одним пакетом — это SYN без ответа, потенциально потерянный пакет.
nmap vs scapy — сравнение сканирования в Wireshark
Чтобы конкретно увидеть, чего не хватает самописному сканеру, сравните два захвата бок о бок: nmap и ваш скрипт на одной цели.
Nmap SYN scan (nmap -sS -T4 <target>). Откройте захват и обратите внимание на интервалы между SYN-пакетами. Nmap использует адаптивный timing: замеряет RTT первых ответов, подстраивает скорость и параллельность. Даже в «агрессивном» режиме -T4 пакеты идут равномерным потоком с интервалом в единицы миллисекунд. Почти для каждого SYN в захвате есть парный SYN-ACK или RST. В колонке Time видно стабильные дельты между пакетами — признак контролируемого потока.
Деталь, которую стоит заметить: nmap в режиме SYN scan сам отправляет RST после получения SYN-ACK, завершая полуоткрытое соединение. В Wireshark это видно как чёткая тройка SYN → SYN-ACK → RST с одинаковым stream index.
Наивный Python-скрипт (socket или scapy без контроля скорости). Типичная картина: пачка из сотен SYN за доли секунды, затем разрозненные SYN-ACK с нарастающей задержкой, множество ретрансмиссий. В Statistics → Conversations → TCP видно: у nmap завершённость диалогов близка к 100%, у наивного скрипта — 30–60%.
| Параметр | nmap -sS | Наивный Python-скрипт |
|---|---|---|
| Контроль скорости | Адаптивный, на основе RTT | Отсутствует |
| Повторные попытки | До 10, с увеличением интервала | 0-1 |
| Обработка RST от ядра | Использует libpcap напрямую | Требует правило iptables |
| Таймаут ожидания | Адаптивный (100 мс — 10 с) | Фиксированный |
| Результат на 1000 портов | Близок к 100% | 30-60% без тюнинга |
| Ретрансмиссии в захвате | Единицы | Десятки-сотни |
Три принципа nmap, которые стоит воспроизвести: адаптивный таймаут на основе измеренного RTT, контроль количества одновременных незавершённых соединений и повторные попытки для портов без ответа с увеличивающимся интервалом.
Диагностика сетевых пакетов пентест-скрипта: делай раз, делай два, делай три
Пошаговая инструкция для отладки самописного сканера через анализ пакетов. Что нужно: Linux-машина с tcpdump и Wireshark, Python 3 с вашим скриптом, sudo-доступ, цель для сканирования в подконтрольной сети (не сканируйте чужие хосты без разрешения).
Делай раз — захвати трафик сканера и nmap
Создайте два эталонных захвата для одной и той же цели и диапазона портов.
Первый захват — nmap. В терминале: sudo tcpdump -i eth0 -nn -w /tmp/nmap.pcap host <target>. Во втором терминале: sudo nmap -sS -T4 -p 1-1000 <target>. После завершения nmap остановите tcpdump (Ctrl+C).
Второй захват — ваш скрипт. Аналогично: sudo tcpdump -i eth0 -nn -w /tmp/script.pcap host <target>, затем ваш скрипт. Остановите tcpdump.
Ожидаемый результат: два pcap-файла. Размер зависит от числа портов — для 1000 портов это обычно 100–500 КБ.
Делай два — сравни статистику
Откройте оба файла в Wireshark (два окна). Для каждого — четыре замера:
- Фильтр
tcp.flags.syn==1 && tcp.flags.ack==0— считайте отправленные SYN (поле «Displayed» в строке состояния). - Фильтр
tcp.flags.syn==1 && tcp.flags.ack==1— считайте полученные SYN-ACK. - Фильтр
tcp.analysis.retransmission— считайте ретрансмиссии. - Statistics → Conversations → TCP — сколько потоков содержат пакеты в обоих направлениях.
Что должно получиться: у nmap отношение SYN к SYN-ACK+RST близко к 1:1, ретрансмиссий единицы. Если у вашего скрипта SYN значительно больше, чем ответов, или ретрансмиссий больше 5% от общего числа пакетов — проблема найдена.
Делай три — найди причину и исправь
На основе замеров из второго шага определите тип проблемы.
Много ретрансмиссий — скорость отправки слишком высока. Добавьте паузу между пакетами. Минимальный сканер на socket с контролем скорости:
import socket, time
def scan_port(target, port, timeout=1.0):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
try:
return port if s.connect_ex((target, port)) == 0 else None
finally:
s.close()
for p in range(1, 1001):
if scan_port("192.168.1.100", p): print(f"Open: {p}")
time.sleep(0.01) # 10 мс — ~100 портов/с
Это connect-scan: connect_ex выполняет полный хендшейк и возвращает 0, если порт открыт, или код ошибки в противном случае. timeout=1.0 — секунда на ожидание. time.sleep(0.01) — 10 мс пауза, ограничивающая скорость до ~100 портов/с. Для локальной сети этого хватает, чтобы избежать переполнения буфера ядра.
SYN-ACK есть в захвате, но скрипт их не видит — ядро шлёт RST. Проверьте фильтром tcp.flags.rst==1 && ip.src==<ваш_IP>: если RST уходит через микросекунды после каждого SYN-ACK, добавьте iptables-правило блокировки RST для нужной цели. Не забудьте удалить правило после завершения сканирования.
Нет ответов вообще — rate limiting. Снизьте интенсивность до 10–50 пакетов/с и добавьте ретраи: если ответа нет, повторите SYN через удвоенный интервал (100 мс → 200 мс → 400 мс).
После каждого исправления повторите захват и сравните статистику. Цель — приблизить соотношение SYN/SYN-ACK к показателям nmap.
Я часто вижу на форумах и в чатах людей, которые учат синтаксис nmap — ключи, тайминги, скрипты NSE — но никогда не открывают Wireshark, чтобы посмотреть, что nmap делает на уровне отдельных пакетов. Это работает ровно до момента, когда скан даёт странные результаты или нужно написать собственный инструмент. Тогда человек гадает: «порт фильтруется или пакет потерялся?» — а ответ буквально в одном tcpdump-захвате и трёх фильтрах Wireshark.
TCP-хендшейк не менялся с 1981 года (RFC 793), и навык чтения пакетов не устаревает. Протоколы поверх TCP меняются, TLS обновляется, HTTP/3 работает поверх UDP — но умение открыть захват и разобрать последовательность SYN → SYN-ACK → ACK остаётся фундаментом сетевой безопасности. Вместо заучивания ключей стоит один раз потратить вечер на сравнение двух pcap-файлов — и увидеть, как 200 строк Python-кода превращаются в поток пакетов, часть которых теряется по вполне конкретным, диагностируемым причинам. Если хочешь пройти от сетевых основ до первых реальных задач системно, а не по разрозненным туториалам — IB Basics именно про это, без академического тона.
Эту тему и смежные навыки разбирают на практике в курсе «Компьютерные сети» Codeby Academy.