РАЗБОР На проверке 

Weird machines в сетевом стеке: парсеры DNS, TCP, HTTP

Сергей Попов
Сергей Попов Red Team · 6,4 тыс. сообщений
Подписаться
36
Режим чтения
Портативный анализатор сетевых протоколов на тёмном антистатическом коврике: OLED-экран показывает схему цепочки DNS-указателей сжатия с петлёй из трёх стрелок, рядом лежит распечатка Ethernet-паке...


При фаззинге DNS-ответов в проприетарном TCP/IP-стеке промышленного контроллера через AFL++ с кастомным харнесом под функцию декомпрессии DNS-имён я обнаружил штуку, от которой сел перечитывать RFC 1035. Цепочка из трёх compression-указателей в одном ответе заставляла парсер пройти пять состояний - ни одно из них в спецификации не описано. Каждый двухбайтовый offset работал как инструкция безусловного перехода, а сам парсер превращался в программируемый автомат, управляемый содержимым сетевого пакета. Это и есть weird machine в сетевом стеке - скрытый вычислитель, который возникает в коде разбора протокола без ведома разработчика и программируется крафтованными пакетами атакующего. Русскоязычных материалов, связывающих теорию weird machines Bratus-Dullien именно с парсерами сетевых протоколов (не с ROP/JOP), на момент написания нет.

Парсер сетевого протокола как weird machine​

Weird machine - вычислительный артефакт, где исполнение кода выходит за рамки исходной спецификации программы. В работах Bratus, Locasto, Patterson, Sassaman она определена как набор непредусмотренных состояний и переходов, активируемых при обработке специально подготовленного ввода. В ROP/JOP (подробно разобранном на Codeby) weird machine живёт в стеке вызовов или dispatch table, а «лентой» служат адреса гаджетов. В сетевых протоколах роль ленты берёт на себя сам пакет - его поля, указатели и значения длин.

Почему сетевые парсеры - идеальная среда для нестандартных вычислений в сетевых пакетах?

Loose contracts по определению. Trail of Bits определяют loose contract как код, где множество допустимых предусловий шире необходимого. Сетевой парсер принимает байты из сети - от кого угодно, без аутентификации. Его входной контракт максимально loose: каждая непроверенная ветка if, каждый пропущенный bounds check расширяет пространство состояний weird machine.

Многослойность парсинга. TCP/IP-стек обрабатывает DNS, HTTP, FTP, ARP, ICMP, TFTP. По данным Forescout, TCP/IP-стеки «expose a significant attack surface that can often be exploited without authentication». Комбинаторика форматов умножает число парсерных состояний - и это ещё мягко сказано.

Достижимость до аутентификации. DNS-ответ обрабатывается стеком автоматически. TCP SYN принимается ядром без участия приложения. Результат - Exploitation for Client Execution (T1203) или Exploitation for Privilege Escalation (T1068) из сети, без предварительного доступа. Пакет прилетел - парсер уже работает.

Бизнес-логика атаки через парсерные weird machines​

Зачем атакующему «программировать» парсер? Ответ простой - initial access без клика. На пентесте embedded-устройства (PLC, сетевой контроллер, IoT-шлюз) классический payload через фишинг не работает: нет ни пользователя, ни браузера. Единственный вход - сетевой пакет. Weird machine в DNS-парсере такого устройства превращает один крафтованный ответ в полный захват, минуя все уровни защиты прикладного слоя. Один пакет - и ты внутри.

DNS-парсер эксплуатация: компрессия указателей как вычислительный примитив​

📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

Исследование Name:Wreck (Forescout, 2021) выявило девять уязвимостей парсеров DNS-имён в TCP/IP-стеках FreeBSD, IPNet, NetX и Nucleus NET. По данным Palo Alto Networks, уязвимости «mainly caused by improperly parsing the names in the DNS responses or during domain name record decompression». Атакующий в позиции DNS-сервера или MitM отвечает крафтованными ответами, эксплуатируя парсерную weird machine - декомпрессия выполняет переходы, не предусмотренные спецификацией, и достигается запись за пределы буфера.

В NicheStack (проприетарный стек, который использовали Siemens, Honeywell, Rockwell Automation) исследователи Forescout и JFrog нашли RCE-уязвимость CVE-2020-25928 в DNSv4-парсере с CVSS 9.8 - out-of-bounds read/write при разборе DNS-ответа. Weird machine в парсере позволяла из одного крафтованного пакета перезаписать стековый буфер контролируемыми данными. По отчёту Forescout, уязвимость затрагивала версии NicheStack до 4.3. Стек разрабатывался с 1996 года - тогда о безопасности парсеров думали, мягко говоря, не так много.

ПараметрЗначение
Позиция атакующегоDNS-сервер или MitM на DNS-трафике
АутентификацияНе требуется
Взаимодействие пользователяНе требуется
Затронутые стекиNicheStack < 4.3, FreeBSD, IPNet, NetX, Nucleus NET
РезультатRCE (CVSS 9.8), DoS, DNS cache poisoning

State machine атаки на TCP/IP: скрытые переходы в tcp_input.c и tcpip.sys​

TCP state machine - один из наиболее формализованных конечных автоматов в сетевом стеке. RFC 793 определяет 11 состояний и детерминированные переходы. На практике реализация в net/ipv4/tcp_input.c ядра Linux - это десятки тысяч строк, обрабатывающих таймеры, ретрансмиссии, window scaling, SACK, ECN. Каждая подсистема добавляет состояния, которых в RFC нет и в помине.

Predictable TCP ISN. В NicheStack обнаружены предсказуемые Initial Sequence Numbers. ISN - входной параметр TCP state machine. Предсказуемый ISN расширяет набор достижимых состояний weird machine: off-path атакующий инжектирует пакеты в чужую TCP-сессию без позиции MitM. По формальной модели Dullien, предсказуемость ISN ослабляет контракт - переходы, требующие знания ISN, становятся доступны наблюдателю. В 2024 году. Предсказуемые ISN. Серьёзно.

ICMP-triggered transitions. По исследованию ACM «Off-Path Attacks on the TCP/IP Protocol Suite», атакующие используют поддельные ICMP error messages для манипуляции состоянием TCP-соединения. ICMP Destination Unreachable с корректным embedded TCP header заставляет ядро закрыть или модифицировать соединение. Код обработки ICMP ошибок - часть спецификации, но его комбинация с TCP state machine создаёт переходы, не предусмотренные проектировщиком. Атакующий «программирует» эти переходы последовательностью крафтованных ICMP-пакетов - каждый пакет меняет одну переменную состояния TCP.

Weird machines Windows kernel сетевой стек: tcpip.sys. В Windows TCP/IP-стек реализован драйвером tcpip.sys в пространстве ядра. Уязвимости парсинга TCP-пакетов в этом драйвере ведут к Exploitation for Privilege Escalation (T1068): код исполняется с привилегиями SYSTEM. По данным SecureLayer7, «attackers can manipulate TCP packets to gain elevated privileges», причём эксплуатация «can be exploited without additional user interaction» - пакет обрабатывается парсером до любой прикладной проверки. Sequence number, acknowledgment number, TCP flags, опции - всё это входные символы weird machine в tcpip.sys. Пакет прилетел в ядро - и ядро уже «исполняет программу» атакующего.

Ключевое отличие от гаджетов ROP в сетевых стеках: здесь не нужно переиспользовать фрагменты кода через подмену адресов возврата. Парсер сам выполняет ветвления на основе содержимого пакета. Гаджет-ориентированное программирование заменяется «программированием полей пакета» - и это принципиально другая механика.

Уязвимости парсеров HTTP: weird machine в chunked encoding​

HTTP/1.1 chunked transfer encoding (RFC 7230) передаёт тело порциями. Каждая начинается с hex-строки размера, за ней CRLF, данные, CRLF. Парсер читает hex-размер, аллоцирует буфер, копирует данные. Завершение - 0\r\n\r\n.

Weird machine возникает в разборе hex-размера. RFC допускает chunk extensions после точки с запятой. Реализации обрабатывают ведущие нули, пробелы, mixed case hex по-разному - и вот тут начинается самое интересное. HTTP-парсер NicheStack содержал критическую RCE-уязвимость (buffer overflow при парсинге HTTP-запросов, точный CVSS зависит от конкретного CVE).

Механика: если парсер chunk size использует аналог strtoul() без проверки переполнения, chunk size FFFFFFFF на 32-битной системе вызовет integer overflow при аллокации. size + 1 при size = 0xFFFFFFFF даёт wrap around к 0. Дальше memcpy запишет данные за пределы нулевого буфера. Один hex-строка - и парсер пишет куда не надо.

Weird machine программируется двумя «инструкциями»:
  1. Hex-строка chunk size - управляет размером аллокации (какой буфер выделить)
  2. Тело chunk - управляет содержимым записи (что и куда записать)
В комбинации - произвольная запись в память, аналог write-what-where примитива, достигнутый через эксплуатацию парсеров протоколов без единого ROP-гаджета. Чистая парсерная weird machine.

Фаззинг сетевых парсеров: поиск weird machine примитивов​

Поиск weird machines в сетевом стеке - задача для structure-aware фаззинга. Случайные байты не пройдут первичный парсинг заголовков - парсер их просто выбросит. Нужен фаззер, который мутирует поля внутри грамматически корректных пакетов.

AFL++ для embedded-стеков. Для NicheStack, lwIP, uIP: выдёргиваем функцию парсинга DNS или HTTP, компилируем с AddressSanitizer, подаём AFL++. Corpus seeds - реальные DNS-ответы, собранные через tcpdump -w dns_corpus.pcap port 53 и сконвертированные в файлы по одному пакету. Я обычно начинаю с 50-100 реальных ответов - чем разнообразнее, тем лучше покрытие.
Bash:
# Компиляция харнеса с ASan:
# AFL_USE_ASAN=1 afl-clang-fast -o dns_parser_harness harness.c
afl-fuzz -i corpus/dns_responses -o findings/ \
  -m none -t 1000 \
  -- ./dns_parser_harness @@
# Для ускорения используйте persistent mode (__AFL_FUZZ_TESTCASE_BUF)
syzkaller для ядра Linux. Фаззинг tcp_input.c и net/dns_resolver/ требует ядерного фаззера. syzkaller генерирует последовательности системных вызовов (socket(), bind(), sendto() с крафтованными данными), прогоняющих пакеты через весь сетевой стек. Для целенаправленного фаззинга DNS-парсера нужно описание структуры DNS-пакета в syzlang-шаблонах для sendto(). Описание syzlang-шаблонов - отдельная история, но без него syzkaller будет генерировать мусор вместо валидных DNS-пакетов.

WinDbg для tcpip.sys. Реверс через IDA Pro/Ghidra с фокусом на функции парсинга TCP-опций. Точка останова bp tcpip!TcpSegmentTcbReceive позволяет трейсить ветвления парсера при получении крафтованного пакета, отправленного через Scapy с хоста атакующего. Без исходников трассировка парсерных ветвлений занимает дни - но именно она показывает, какие поля пакета управляют какими переходами weird machine. Нудная работа, но альтернативы нет.

Что искать. Не crash сам по себе (крашей будет много), а управляемые примитивы: контролируемая запись за пределы буфера (длина определяется полем пакета), контролируемый указатель перехода (DNS compression offset), контролируемый размер аллокации (HTTP chunk size). Каждый такой примитив - «инструкция» weird machine, из которых собирается эксплойт на основе анализа протокольных парсеров.

Детектирование активации weird machines в сетевом стеке​

Weird machine активируется, когда парсер входит в непредусмотренное состояние. Детектировать это можно на нескольких уровнях:

УровеньИнструментЧто обнаруживает
Ядро (runtime)KASAN / KFENCE (Linux)OOB-доступ при парсинге пакета
Ядро (hardening)Stack canaries (D3-SFCV)Перезапись стека в функции парсера
Сеть (IDS)Suricata / SnortАномальные DNS/HTTP-пакеты
SIEMSigma lnx_buffer_overflows.ymlKernel oops от buffer overflow
EndpointShadow Stack (D3-SSC)ROP-цепочка после активации weird machine
HardenDEP / NX (D3-PSEP)Исполнение из data-секций после exploit
HardenASLR (D3-SAOR)Рандомизация адресов стека и кучи

IDS-уровень - самый ранний. Правила для Suricata, проверяющие глубину рекурсии DNS compression-указателей (>3 уровня - подозрительно, >6 - почти наверняка попытка эксплуатации), или HTTP-ответы с chunk size, превышающим Content-Length, закрывают вектор до того, как пакет дойдёт до уязвимого парсера. Но есть нюанс: правило нужно написать, а для этого нужно знать, что искать.

Критическая разница: ядро Linux прошло три десятилетия security review и включает KASAN, KFENCE, stack protector. Проприетарные стеки вроде NicheStack разрабатывались, по данным Forescout, «at a time when software vulnerabilities were not as well understood as today» - без ASLR, без stack canaries, часто без NX. Weird machine в таком стеке эксплуатируется тривиально: от OOB write до перехвата управления - один шаг, буквально. Shodan показывал порядка 6400 устройств с NicheStack, доступных из интернета. Forescout насчитал почти 200 вендоров-клиентов InterNiche, включая Siemens, Honeywell, Schneider Electric. Патчи от HCC Embedded (текущий владелец) доступны только по запросу OEM-клиентов - конечные пользователи ждут обновлений от производителей устройств, многие из которых прекратили поддержку. Тупик.

Последние два года я трачу больше времени на парсеры протоколов, чем на классический binary exploitation с переполнениями и ROP-цепочками. Скрытые вычисления в парсерах TCP/IP, DNS и HTTP достижимы с сети, не требуют утечки адресов, не требуют обхода ASLR и CFI - потому что парсер сам становится вычислителем. В embedded-стеках, где нет KASAN, canaries и Shadow Stack, путь от крафтованного пакета до RCE - десятки байт и одна функция. Для Linux kernel ситуация лучше, но не решена: KASAN ловит многие OOB при фаззинге через syzkaller, однако coverage сетевых парсеров в syzkaller до сих пор неполный - edge cases в обработке DNS-опций или TCP SACK остаются нефаззенными. Я убеждён, что следующая волна сетевых эксплойтов пойдёт не через memory corruption в привычном понимании, а через композицию парсерных примитивов - серию пакетов, где каждый «исполняет инструкцию» на скрытом вычислителе внутри парсера. ROP учил нас собирать вычисления из гаджетов. Парсерные weird machines учат собирать вычисления из полей протокола - и это принципиально другой skill set, для которого нужно думать не «где мой pop rdi; ret», а «какой offset в compression pointer переведёт парсер в нужное мне состояние». На HackerLab лежат сценарии, где бинарный примитив нужно раскрутить в полноценную цепочку от начального доступа до перехвата исполнения - формат, который быстро показывает разницу между пониманием парсера и умением его «запрограммировать».
Полезно

Комментарии

0

Ещё по теме