Протокол TCP: SYN-, FIN-, NULL-, ACK-, XMAS-сканирования и выявление признаков последнего
Автор статьи никого не призывает к правонарушениям и отказывается нести ответственность за ваши действия. Вся информация предоставлена исключительно в ознакомительных целях. Все действия происходят на виртуальных машинах и внутри локальной сети автора. Спасибо!
В этой статье продолжим изучение протоколов, разберемся как осуществляется SYN-, FIN-, NULL-, XMAS-, ACK-сканирования и закрепим весь теоретический материал на практике. После того как научимся сканировать, реализуем анализатор, который будет выявлять XMAS-сканирование.
Из этой статьи вы не только узнаете про все основные виды сканирования целевой машины и научитесь их с нуля разрабатывать на Python, но и начнете более осознано использовать флаги -sS
, -sN
, -sF
, -sX
, -sA
самой используемой сетевой утилиты nmap.
Объяснить, зачем вам в этом так подробно разбираться я не смогу лучше, чем автор документации по nmap в первом абзаце. Вот этот фрагмент: «Как новичок в автомобильном деле, я могу часами биться в попытках использовать свои элементарные инструменты (молоток, клейкая лента, гаечный ключ и т.д.) для решения какой-либо проблемы. Когда все мои попытки с крахом проваливаются, и я буксирую свою развалюху к настоящему механику, он неизменно достает из большой коробки с интрументами какую-нибудь штуковину, и сразу складывается впечатление, что решение проблемы не требует много усилий. Искусство сканирования портов очень на это похоже. Эксперты понимают дюжины различных приемов сканирования портов и выбирают для конкретной задачи подходящий (или комбинацию из нескольких). Неопытные пользователи и script kiddies, пытаются решить все задачи с помощью используемого по умолчанию SYN сканирования. Т.к. Nmap является бесплатной, то единственным барьером на пути к овладению техникой сканирования портов является знание. Это все же лучше, чем в мире автомобилей, где, когда вам наконец-то удается определить, что вам необходимо какое-либо устройство, вам еще надо будет заплатить за него тысячу долларов.».
Скучная теория SYN-сканирования
SYN-сканирование – самый популярный тип сканирования, при котором сканер портов генерирует IP-пакеты и отслеживает ответы на них. При таком сканировании полное соединение TCP/IP никогда не открывается, поэтому такой способ называют сканирование с использованием полуоткрытых портов.
Если порт на целевой машине открыт, с него придет пакет SYN-ACK
. Это нам нужно знать, чтобы определить состояние порта, поэтому позже к этому вернемся. После чего сканер отвечает пакетом RST
и тем самым закрывает соединение. Схема для наглядности:
Реализуем SYN-сканер
Для написания простого сканера будем использовать Python с модулем scapy, поэтому импортируем все что нам нужно:
import sys
from scapy.all import IP, ICMP, TCP, sr1
Протоколы IP и TCP, думаю понятно, для чего нам нужны. Протокол ICMP пригодится, чтобы проверить хост, а функция sr1()
нужна для отправки и приема только одного пакета. Теперь напишем функцию icmp_check()
, которая принимает IP-адрес и возвращает булевое значение, означающее его состояние. Если True
, то хост подключен к сети:
def icmp_check(ip):
icmp_packet = IP(dst=ip) / ICMP()
response_packet = sr1(icmp_packet, timeout=10, verbose=0)
return response_packet != None
Разберемся с кодом. Сначала формируем ICMP пакет используя оператор / для объединения информации разных слоев и изменения значений по умолчанию. Далее отправляем пакет и получаем ответ. Если ответ есть, то хост подключен к сети. Кстати, verbose=0
мы указываем, чтобы в консоль не отправлялась служебная информация, которая захламляет вывод. Теперь реализуем функцию syn_scan()
, которая отправляет пакет с флагом SYN
.
def syn_scan(ip, port):
syn_request = IP(dst=ip) / TCP(dport=port, flags='S')
syn_response = sr1(syn_request, timeout=10, verbose=0)
return syn_response
Принцип тот же, только мы переопределяем в коде флаг пакета TCP на S
, что означает установку SYN
флага у TCP пакета. Кстати, название полей можно посмотреть в документации класса scapy.layers.inet.TCP. Теперь напишем оставшуюся часть программы:
if __name__ == '__main__':
ip = sys.argv[1]
port_start = 1
port_end = 23
if icmp_check(ip):
for p in range(port_start, port_end + 1):
syn_response = syn_scan(ip, p)
# print(syn_response.show())
if str(type(syn_response)) != "<class 'NoneType'>":
if syn_response.getlayer('TCP').flags == 0x12:
print(f'Port {p} open')
else:
print(f'Port {p} closed')
else:
print(f'Port {p} closed')
IP-адрес мы получаем из первого аргумента, а диапазон портов для проверки пока зададим вручную. Далее проверяем, подключен ли хост к сети и сканируем каждый порт при помощи нашей функции syn_scan()
и сохраняет ответ в переменную syn_response
. Чтобы посмотреть сами пакеты, можете используйте функцию show()
:
syn_response.show()
Нас интересует только флаг TCP-пакета, значение которого можно получить так:
syn_response.getlayer('TCP').flags
Итак, почему мы сравниваем флаг со значением 0x12
. Как оно получилось? Если установлен лишь флаг SYN
, то флаг у TCP-пакета будет 0x02
. Если только флаг ACK
, то значение будет 0x10
. Если оба, то 0x12
. Вот на форуме про это спрашивали, можете посмотреть более подробные объяснения. Запускаем наш сканер и смотрим результат:
python3 syn_scanner.py 192.168.1.101
Для наглядности я запустил Wireshark и просканировал один открытый порт. Сначала машины обменялись ARP-пакетами, про которые мы подробно говорили в статье про ARP-спуфинг. Далее происходит обмен ICMP-пакетами, чтобы убедиться о наличии подключения хоста к сети. После чего отправляем TCP-пакет с флагом SYN
и получаем ответ SYN, ACK
. В конце разрываем соединение.
Если порт закрыт, то после отправки TCP-пакета с флагом SYN
мы получим в ответ RST, ASK
:
Скучная теория FIN-сканирования (у NULL-, XMAS- алгоритм идентичный)
Некоторые серверы отслеживают SYN-сканирование портов и разрывают соединение для защиты от сканирования. Согласно RFC 793 «TRANSMISSION CONTROL PROTOCOL», на поступивший пакет с флагом FIN
на закрытый порт сервер должен ответить пакетом с флагом RST
. Если на открытый порт, то он должен быть проигнорирован. RFC – это рекомендация и не все операционные системы ей придерживаются.
Реализуем FIN-сканер
Перенесем часть кода SYN-сканера в функцию syn_scan()
, добавим второй аргумент, который позволит выбирать тип сканирования и напишем функцию fin_scan()
. В итоге у нас получился следующий код:
...
def fin_scan(ip, port):
fin_request = IP(dst=ip) / TCP(dport=port, flags='F')
fin_response = sr1(fin_request, timeout=3, verbose=0)
if str(type(fin_response)) == "<class 'NoneType'>":
return f'Port {p} open or filtered'
else:
if fin_response.haslayer('TCP'):
if fin_response.getlayer('TCP').flags == 0x14:
return f'Port {p} closed'
elif fin_response.haslayer('ICMP'):
if(int(fin_response.getlayer('ICMP').type) == 3
and int(fin_response.getlayer('ICMP').code)
in [1, 2, 3, 9, 10, 13]):
return f'Port {p} filtered'
if __name__ == '__main__':
ip = sys.argv[1]
# s - SYN, f - FYN, x - XMAS
mode = sys.argv[2]
port_start = 1
port_end = 23
if icmp_check(ip):
for p in range(port_start, port_end + 1):
if mode == '-s':
print(syn_scan(ip, p))
elif mode == '-f':
print(fin_scan(ip, p))
Он далек от изящества и емкости, но выполняет свои функции. Давайте запустим его:
python3 ports_scanner.py 192.168.1.101 –f
Как видите, при таком сканировании нельзя определить наверняка, открыт порт или он фильтруется. Если в ответ приходит ICMP ошибка (тип 3, код 1, 2, 3, 9, 10 или 13), то порт фильтруется. По такой же схеме работают NULL- и XMAS-сканирования, а отличаются лишь флагами TCP-пакета.
NULL- и XMAS-сканирования
Думаю, нет смысла подробно останавливаться на NULL- и XMAS-сканированиях, так как они работают, как и FIN-сканирование, только в первом случае поле с флагом остается пустым, а при XMAS-сканировании флаг TCP-пакета должен быть равен FPU
(PSH
, FIN
, URG
).
ACK-сканирование
Данный вид сканирования не используется для определения открытых и закрытых портов. Оно предназначено для определения наличия межсетевого экрана и анализа его правил, по возможности.
Реализация ACK-сканирования на Python
Теперь реализуем этот тип сканирования на Python. Напишем функцию ack_scan()
. В ней будет все тоже самое, что мы уже ранее обсуждали, только в этот раз проверяем TCP пакет на наличие флага R
, который равен 0x4
, чтобы определить не фильтруемый порт. Далее представлен весь код сканера, который мы вместе написали:
import sys
from scapy.all import IP, ICMP, TCP, sr1
def icmp_check(ip):
icmp_packet = IP(dst=ip) / ICMP()
response_packet = sr1(icmp_packet, timeout=10, verbose=0)
return response_packet != None
def send_packet(ip, port, flag):
request = IP(dst=ip) / TCP(dport=port, flags=flag)
return sr1(request, timeout=3, verbose=0)
def check_icmp_error(r):
if r.haslayer('ICMP'):
if(int(r.getlayer('ICMP').type) == 3
and int(r.getlayer('ICMP').code)
in [1, 2, 3, 9, 10, 13]):
return True
return False
def syn_scan(ip, port):
response = send_packet(ip, port, 'S')
if str(type(response)) != "<class 'NoneType'>":
# 0x12 = SA
if response.getlayer('TCP').flags == 0x12:
return f'Port {p} open'
else:
return f'Port {p} closed'
else:
return f'Port {p} closed'
def fin_null_xmas_scan(ip, port, flag):
response = send_packet(ip, port, flag)
if str(type(response)) == "<class 'NoneType'>":
return f'Port {p} open or filtered'
else:
if response.haslayer('TCP'):
# 0x14 = RA
if response.getlayer('TCP').flags == 0x14:
return f'Port {p} closed'
elif check_icmp_error(response):
return f'Port {p} filtered'
def ack_scan(ip, port):
response = send_packet(ip, port, 'A')
if str(type(response)) == "<class 'NoneType'>":
return f'Port {p} filtered'
else:
if response.haslayer('TCP'):
# 0x4 = R
if(response.getlayer(TCP).flags == 0x4):
return f'Port {p} unfiltered'
elif check_icmp_error(response):
return f'Port {p} filtered'
if __name__ == '__main__':
ip = sys.argv[1]
# s - SYN, f - FYN, n - NULL, x - XMAS, a - ACK
mode = sys.argv[2]
port_start = int(sys.argv[3])
port_end = int(sys.argv[4])
if icmp_check(ip):
for p in range(port_start, port_end + 1):
result_scan_port = ''
if mode == '-s':
result_scan_port = syn_scan(ip, p)
elif mode == '-f':
result_scan_port = fin_null_xmas_scan(ip, p, 'F')
elif mode == '-n':
result_scan_port = fin_null_xmas_scan(ip, p, '')
elif mode == '-x':
result_scan_port = fin_null_xmas_scan(ip, p, 'FPU')
elif mode == '-a':
result_scan_port = ack_scan(ip, p)
print(result_scan_port)
Для запуска скрипта откройте терминал и укажите нужные параметры (-f
— вид сканирования, 1
— начальный порт, 23
— конечный порт):
python3 ports_scanner.py 192.168.1.101 –f 1 23
Выявление признаков XMAS-сканирования
В статье про перехват трафика при помощи ARP-спуфинга мы уже поднимали тему анализа пакетов. Воспользуемся нашими знаниями и постараемся обнаружить XMAS-сканирование нашей системы.
Как вы узнали из этой статьи, XMAS-сканирование выдает себя из-за наличия в пакете TCP флагов PSH
, FIN
, URG
. Так-то мы и определим «вероятное» XMAS-сканирование нашего хоста.
import os
from scapy.all import sniff
def process(packet):
# print(packet.show())
ip_dst = packet['IP'].dst
flags = packet['TCP'].flags
if ip_dst == host:
if flags == 'FPU':
print(f'Perhaps XMAS port {packet["IP"].dport} \
scanning from the host {packet["IP"].src}:{packet["IP"].sport}')
host = '192.168.1.100'
sniff(count=0, filter='tcp', store=0, prn=process)
Все что нам нужно, это отфильтровать только TCP пакеты и при нахождении в них флага FPU
, написать в консоль о возможном XMAS-сканировании. Теперь запустите скрипт и попробуйте просканировать с другого компьютера при помощи утилиты nmap:
nmap -sX 192.168.1.100
Выводы
Надеюсь, после прочтения статьи у вас сложилось хорошее понимание видов сканирования, вы углубили свои знания протоколов, а именно TCP и лучше познакомились с модулем scapy. Уверен, вас больше не будут пугать множество различных и непонятных видов сканирования в nmap и вы с легкостью выберите нужный в конкретной ситуации.