Возможность работы с сокетами предоставляют нам системные "Поставщики услуг". Именно поставщик ограничивает тот или иной функционал, например возможность использовать протокол ICMP или девственные RAW-сокеты. В отношении WinSock, базовых поставщиков всего двое – это библиотека mswsock.dll, со-своей подругой rsvpsp.dll (заправляет функциями NetBIOS).
Поставщик подключается к WinSock при помощи имеющегося в нём интерфейса SPI - "Service Provider Interface". После регистрации поставщика в системе, служба Winsock использует его для реализации определённых функций сокета, например setsockopt(), ioctlsocket(), connect() и прочих для тех типов адресов, которые поставщик указал в качестве им реализуемых. Тип адреса под виндой всегда одинаковый – это AF_INET с константой =2 (для сетей IPv4).
Сторонние поставщики небезопасны и Microsoft относится к ним с презрением. Они могут с лёгкостью обойти надстройки безопасности сетевых протоколов, поскольку имеют прямой доступ к ядру операционной системы, а точнее к транспортной её части WSK (Winsock Kernel, в младенчестве TDI). Финт с установкой самих себя в качестве поставщика многоуровневой службы по модели "Layered Service Provider" (LSP) часто использует малварь и шпионские программы.
5.5.0. Системные ограничения WinSock
На борту Win имеется консольная утилита NETSH (Network Shell). На запрос вида winsock show catalog она отзывается портянкой с описанием услуг базовых поставщиков. Поскольку лог получается большой и не вмещается в консольную форточку, имеет смысл перенаправить вывод в файл. Вот пример, в котором я принебрёг деталями не интересного нам протокола NetBIOS. Как видим – выбор не велик, и попытки заставить сокеты работать с отсутствующими здесь протоколами, обречены на провал:
Ого… даже RAW имеется в списке! Круто!
Но бросать чепчики в воздух рановато – всё/это для отвода глаз, чтобы армия юзеров не посчитали систему ущербной. Продуманы из Microsoft не придумали ничего лучше, как ввести RAW-сокеты на уровне приложений, и тут-же ограничить их функционал на уровне ядра. Такой подход вводит в ступор большинство программистов, поскольку мы можем создавать сырьё, но первая-же операция с ними, приводит к ошибке. В точку здесь попадает определение – праздник закрытых дверей.
Однако не всё так плохо, и с барского стола нам тоже кое-что перепало. Нужно запомнить одно правило – в большинстве случаях, операции с сырыми сокетами под WinXP+ возможны только для входящих пакетов. Если стража системы пронюхает исходящий пакет RAW, то безжалостно уничтожит его, не дав возможности даже объясниться. Вот
Вся прелесть сырых сокетов в том, чтобы самому формировать заголовки пакетов – иначе они и даром не нужны. Но что сделали макрушники? Они ввели RAW, но предписывают использовать их только внутри своего узла. Это вообще труба! Да.., мы можем отправлять сырые пакеты с валидным/нашим IP, но это превращает сырой сокет в обычный, и непонятно зачем вообще называть его RAW.
5.5.1. Тёмная сторона сокетов
В штанинах Winsock имеется функция для тюннинга сокетов – это setsockopt(). Характер опций во многом зависит от поставщика и не придаётся громкой огласке. Поэтому мелкософт выложила у себя только некоторые из них, хотя на самом деле список намного обширней. Вот прототип функции и некоторые (интересные на мой взгляд) рычаги управления.
С дефолтными настройками сокетов, всю черновую работу по инкапсуляции и заполнению заголовков всех уровней выполняет служба Winsock. С одной стороны это освобождает нас от низкоуровневой рутины, с другой – навязывает свои правила. Для таких случаев, у сокетов имеется опция IP-HDRINCL (Include Header, подключать заголовки = да/нет), которая предписывает службе Winsock не вмешиваться в этот процесс и что заголовки пакетов мы планируем формировать сами.
Ещё одна опция – это TCP_NODELAY, или нет задержкам! Столь философскую мысль выдвинул некто Джон Нейгл, и посоветовал внедрить в сокеты механизм, который позволял-бы не передавать по линии связи слишком мелкие пакеты, а копить их до определённого размера, после чего выстрелить в сеть дробью.
Дело в том, что по-умолчанию никто это дело не контролирует, и функции отправки данных send() абсолютно по-барабану, сколько полезных данных (payload) содержит пакет. В нём может быть всего 1-байт, а остальное – заголовки Header. КПД такого обмена стремится к нулю, и только занимает пропускную способность канала. Вот Нейгл и предложил перед отправкой тестить пайлоад, и собирать пакет по краполям, в один баш. Ясно, что при этом получаются задержки, и первыми бросились на амбразуру разработчики сетевых игр, для которых такие тайм-ауты критичны. Опция по-умолчанию отключена и пакеты буферизуются.
5.5.1(1) Общаемся с драйвером сетевой карты
Другой полезной фишкой является возможность передавать команды напрямую драйверу транспорта WSK. Как мы помним, на системах х32 пространство памяти ОЗУ разделено на две равные части – нижняя половина от 0x1000 и до адреса 0x7FFFFFFF выделена в наше юзерское распоряжение, а выше 0х80000000 и до самых небес системное ядро забирает себе. Доступ к ядерной памяти контролируется центральным процессором CPU на аппаратном уровне, и любые попытки вторжения в кернел жёстко пресекаются сторожем страниц PageGuard.
Однако большинство служб и системных сервисов сами тусуются в пространстве юзера, и по роду своей деятельности должны общаться с ядром. Поэтому полностью изолировать юзера на необитаемый остров нельзя, в результате чего появился шлюз в Kernel в виде функции DeviceIoControl(). Эта функция универсальна и не привязана к какому-то одному драйверу. В качестве аргумента она ждёт код-запроса, по которому передаёт сообщение только одному из многочисленных драйверов в системе. Необнародованный список кодов затмит роман "Война и мир", а с документированным листом можно ознакомиться
Дочерней по отношению к DeviceIoControl() является функция ioctlsocket(). Эту функцию можно использовать для любого сокета в любом состоянии, независимо от протокола и подсистемы связи. Прототип функции выглядит так (нуль на выходе в EAX свидетельствует об успешной операции):
5.5.2. Историческая справка
Ещё во-времена динозавров, разработчикам приходилось долго и муторно тестить поддержку сети на ошибки, в результате чего на свет появились "анализаторы пакетов". При помощи анализа, админы и службы тех/поддержки могут наблюдать за передвижением трафика в сети, диагностировать и устранять возникающие проблемы и т.д. Но как это часто бывает, обычный анализатор превратился в грозное оружие под названием "сниффер".
В былые времена, используя снифер хакеры рыбачили в сети и вылавливали из неё пароли и другую конфиденциальную инфу. Особенно актуально это было для таких протоколов как Telnet, где трафик гуляет в незашифрованном виде. Прослушивание линии стало возможным благодаря особенности архитектуры Ethernet, в которой все узлы подключены к одной среде передачи данных и делят её по-братски. Для прослушки локальной сети не нужно резать кабели – в ней и так все связаны кровными узами.
Но беззаботная жизнь на хабах продолжалась не долго.. В локальную сеть, как слон в посудную лавку, ввалился коммутатор Switch. Теперь хоть линия передачи как и прежде одна, зато узлы в ней разделены логически. Виной тому – всего-то крошечный логический элемент с аббревиатурой "И". Он имеет два входа и один выход, на который вешают узел сети. Если "И" на первый "И" на второй вход подать единицу, то затвор на выходе открывается и трафик поступает на подключённый к выходу узел. Таким образом, обычный логический элемент из двух транзисторов полностью перекрыл кислород сниферам, оставив в их владениях периметр только до коммутатора.
В противоположность свитчу, хаб представляет из-себя просто многопортовый слот типа тройника для электо-сети. В наше время, его можно использовать как разветвитель и он не разделяет подключённые к сети узлы. Трафик курсирует в нём в обе стороны, что только на руку сниферам. Структурные их схемы представлены ниже:
Для простоты понимания, в примере с коммутатором я исключил из схемы дешифратор. Этот электронный компонент может иметь, например, 4-входа и 16-выходов, чего достаточно для 16-портовых свитчей. На 4-входа дешифратора поступает двоичная тетрада последнего байта IP-адреса (см.рис.выше), по которой он подключает один из 16-ти выходных портов (бинарная логика).
Если собрать всё во-едино, то получается что в современных сетях снифать пакетики можно только внутри своей локальной сети. Высунуть нос наружу мешает в первую очередь коммутатор, после которого может быть ещё и VPN, и дальше маршрутизатор. В добавок, хоть мы и пронюхаем пакет в своей сетке LAN, всё-равно получить от него профит не получится, т.к. сейчас весь трафик передаётся только в зашифрованном виде и привести его в понятный вид не так просто. Только посмотрите на эту баррикаду, и всё сразу станет ясно:
5.5.3. Практика – пишем сниффер на RAW сокетах
Теперь посмотрим, что представляет из-себя сниффер на генетическом уровне..
Во-первых, нужно перевести сетевую карту в неразборчивый режим Promisc. Для этого задействуем функцию ioctlsocket() с кодом SIO_RCVALL равным 0x98000001, который отключит в контроллере способность фильтровать трафик на свой/чужой. Теперь остаётся в непрерывном режиме принимать пакеты функцией recv() в свой буфер и выводить его содержимое на экран.
После чтения очередной порции трафика, в буфере получим пакет, формат которого сильно зависит от типа протокола. Можно снабдить программу фильтром протоколов, например чтобы прожка принимала только TCP пакеты, UDP или ICMP. Но в демонстрационном примере лучше принимать всё-подряд, а прикрутить фильтр можно будет позже – главное понять суть.
В независимости от используемого отправителем протокола, пакет в буфере будет начинаться с IP-заголовка, а дальше - в игру вступает уже конкретный протокол. Если вспомнить, как инкапсулируются в пайлоад заголовки, то картина прояснится:
Чтобы вытянуть из пакета полезные данные payload, нам придётся сначала в IP-заголовке проверить поле "iph_proto", где будет указан код протокола, по которому был отправлен данный пакет. Если это IPPROTO_TCP с кодом 0х06, значит после IP-заголовка идёт 20-байтный TCP-заголовок (см.рис.выше). Для IPPROTO_UDP код будет 0х11, а для ICMP = 0x01. Дамп перехваченного пакета в отладчике выглядит так:
Ну и немного о деталях самого исходника..
Чтобы вывести тип протокола на экран, придётся по его коду искать соответствующую строку. Для этого я создал таблицу переходов, где первое значение это код-протокола из поля iph_proto заголовка IP, а второе – адрес строки с его именем в секции данных программы. Получив код я ищу его в таблице, и при совпадении беру следующее значение, которое будет указателем на соответствующую строку. Вот образец этой таблицы:
Алгоритм всего кода снифера распишем так:
Как показывает скрин, снифер исправно нюхает сеть и если его не остановить, то в поле зрения попадаются интересные пакеты, в которых IP-адреса вообще не принадлежат моей сети. Судя по всему, без моего ведома кто-то периодически стучится в сеть в поисках своего сервера (или демона на моём узле). По сути прожка вместе с IP выводит и порты, так-что вычислить софтину и заинжектить ей ледокаин пониже радикулита, будет не сложно.
--------------------------------------------------------
Разговаривать о сети можно бесконечно. В её архитектуре столько потайных мест, что думаю разработчики сами уже запутались в ней. Поэтому этот пост будет последним в данной весовой категории, и мы проследуем дальше.. в дебри файловых систем, различных контролёров, работы с дисковыми накопителями, механизмами защиты и т.д.. Держите руку на пульсе..
Поставщик подключается к WinSock при помощи имеющегося в нём интерфейса SPI - "Service Provider Interface". После регистрации поставщика в системе, служба Winsock использует его для реализации определённых функций сокета, например setsockopt(), ioctlsocket(), connect() и прочих для тех типов адресов, которые поставщик указал в качестве им реализуемых. Тип адреса под виндой всегда одинаковый – это AF_INET с константой =2 (для сетей IPv4).
Сторонние поставщики небезопасны и Microsoft относится к ним с презрением. Они могут с лёгкостью обойти надстройки безопасности сетевых протоколов, поскольку имеют прямой доступ к ядру операционной системы, а точнее к транспортной её части WSK (Winsock Kernel, в младенчестве TDI). Финт с установкой самих себя в качестве поставщика многоуровневой службы по модели "Layered Service Provider" (LSP) часто использует малварь и шпионские программы.
5.5.0. Системные ограничения WinSock
На борту Win имеется консольная утилита NETSH (Network Shell). На запрос вида winsock show catalog она отзывается портянкой с описанием услуг базовых поставщиков. Поскольку лог получается большой и не вмещается в консольную форточку, имеет смысл перенаправить вывод в файл. Вот пример, в котором я принебрёг деталями не интересного нам протокола NetBIOS. Как видим – выбор не велик, и попытки заставить сокеты работать с отсутствующими здесь протоколами, обречены на провал:
Bash:
C:\> netsh winsock show catalog > wslog.txt
Элемент поставщика каталога Winsock
---------------------------------------
Тип элемента: Базовый поставщик услуг
Описание: MSAFD Tcpip [TCP/IP]
Путь поставщика: %SystemRoot%\system32\mswsock.dll
Версия: 2 ; WinSock 2.0
Макс длина адреса: 16 ; размер структуры "sockaddr_in"
Тип адреса: 2 ; AF_INET
Тип сокета: 1 ; SOCK_STREAM
Протокол: 6 ; IPPROTO_TCP
Элемент поставщика каталога Winsock
---------------------------------------
Тип элемента: Базовый поставщик услуг
Описание: MSAFD Tcpip [UDP/IP]
Путь поставщика: %SystemRoot%\system32\mswsock.dll
Макс длина адреса: 16
Тип адреса: 2 ; AF_INET
Тип сокета: 2 ; SOCK_DGRAM
Протокол: 17 ; IPPROTO_UDP
Элемент поставщика каталога Winsock
---------------------------------------
Тип элемента: Базовый поставщик услуг
Описание: MSAFD Tcpip [RAW/IP]
Путь поставщика: %SystemRoot%\system32\mswsock.dll
Макс длина адреса: 16
Тип адреса: 2 ; AF_INET
Тип сокета: 3 ; SOCK_RAW
Протокол: 0 ; IPPROTO_IP
Ого… даже RAW имеется в списке! Круто!
Но бросать чепчики в воздух рановато – всё/это для отвода глаз, чтобы армия юзеров не посчитали систему ущербной. Продуманы из Microsoft не придумали ничего лучше, как ввести RAW-сокеты на уровне приложений, и тут-же ограничить их функционал на уровне ядра. Такой подход вводит в ступор большинство программистов, поскольку мы можем создавать сырьё, но первая-же операция с ними, приводит к ошибке. В точку здесь попадает определение – праздник закрытых дверей.
Однако не всё так плохо, и с барского стола нам тоже кое-что перепало. Нужно запомнить одно правило – в большинстве случаях, операции с сырыми сокетами под WinXP+ возможны только для входящих пакетов. Если стража системы пронюхает исходящий пакет RAW, то безжалостно уничтожит его, не дав возможности даже объясниться. Вот
Ссылка скрыта от гостей
В Win-7, Vista, XP(sp3) возможность отправки трафика по сырым сокетам была ограничена несколькими способами:
• Данные TCP не могут быть отправлены через сырые сокеты.
• Вызов функции bind() с сырым сокетом для протокола IPPROTO_TCP запрещён.
• UDP-трафик с недопустимым адресом отправителя не может быть отправлен через сырые сокеты.
• IP-адрес отправителя для любой исходящей UDP-дейтаграммы должен существовать в сетевом интерфейсе, или датаграмма удаляется.
Эти изменения были сделаны, чтобы ограничить способность вредоносного кода создавать распределённые атаки типа DDoS и возможность отправки TCP/IP-пакетов с поддельным IP-адресом источника.
Вся прелесть сырых сокетов в том, чтобы самому формировать заголовки пакетов – иначе они и даром не нужны. Но что сделали макрушники? Они ввели RAW, но предписывают использовать их только внутри своего узла. Это вообще труба! Да.., мы можем отправлять сырые пакеты с валидным/нашим IP, но это превращает сырой сокет в обычный, и непонятно зачем вообще называть его RAW.
5.5.1. Тёмная сторона сокетов
В штанинах Winsock имеется функция для тюннинга сокетов – это setsockopt(). Характер опций во многом зависит от поставщика и не придаётся громкой огласке. Поэтому мелкософт выложила у себя только некоторые из них, хотя на самом деле список намного обширней. Вот прототип функции и некоторые (интересные на мой взгляд) рычаги управления.
C-подобный:
setsockopt (
socket ; дескриптор сокета
level ; IPPROTO_TCP/UDP/IP/RAW
optname ; имя опции (см.ниже)
optval ; указатель на значение опции: 1= вкл, 0= выкл.
optlen ) ; длина значения в байтах, обычно 4 (dword)
;== Имя опций ===============
IP_HDRINCL = 2 ; PROTO_RAW. Если значение =1, то IP-заголовок заполняем сами
IP_DONTFRAGMENT = 14 ; PROTO_any. Если значение =1 не фрагментировать пакет
TCP_NODELAY = 1 ; PROTO_TCP. Вкл/выкл алгоритм Nagle
UDP_NOCHECKSUM = 1 ; PROTO_UDP. Вкл/выкл проверку контрольной суммы
SO_BROADCAST = 0x0020 ; PROTO_UDP. Широковещательные сообщения на сокете UDP
С дефолтными настройками сокетов, всю черновую работу по инкапсуляции и заполнению заголовков всех уровней выполняет служба Winsock. С одной стороны это освобождает нас от низкоуровневой рутины, с другой – навязывает свои правила. Для таких случаев, у сокетов имеется опция IP-HDRINCL (Include Header, подключать заголовки = да/нет), которая предписывает службе Winsock не вмешиваться в этот процесс и что заголовки пакетов мы планируем формировать сами.
Ещё одна опция – это TCP_NODELAY, или нет задержкам! Столь философскую мысль выдвинул некто Джон Нейгл, и посоветовал внедрить в сокеты механизм, который позволял-бы не передавать по линии связи слишком мелкие пакеты, а копить их до определённого размера, после чего выстрелить в сеть дробью.
Дело в том, что по-умолчанию никто это дело не контролирует, и функции отправки данных send() абсолютно по-барабану, сколько полезных данных (payload) содержит пакет. В нём может быть всего 1-байт, а остальное – заголовки Header. КПД такого обмена стремится к нулю, и только занимает пропускную способность канала. Вот Нейгл и предложил перед отправкой тестить пайлоад, и собирать пакет по краполям, в один баш. Ясно, что при этом получаются задержки, и первыми бросились на амбразуру разработчики сетевых игр, для которых такие тайм-ауты критичны. Опция по-умолчанию отключена и пакеты буферизуются.
5.5.1(1) Общаемся с драйвером сетевой карты
Другой полезной фишкой является возможность передавать команды напрямую драйверу транспорта WSK. Как мы помним, на системах х32 пространство памяти ОЗУ разделено на две равные части – нижняя половина от 0x1000 и до адреса 0x7FFFFFFF выделена в наше юзерское распоряжение, а выше 0х80000000 и до самых небес системное ядро забирает себе. Доступ к ядерной памяти контролируется центральным процессором CPU на аппаратном уровне, и любые попытки вторжения в кернел жёстко пресекаются сторожем страниц PageGuard.
Однако большинство служб и системных сервисов сами тусуются в пространстве юзера, и по роду своей деятельности должны общаться с ядром. Поэтому полностью изолировать юзера на необитаемый остров нельзя, в результате чего появился шлюз в Kernel в виде функции DeviceIoControl(). Эта функция универсальна и не привязана к какому-то одному драйверу. В качестве аргумента она ждёт код-запроса, по которому передаёт сообщение только одному из многочисленных драйверов в системе. Необнародованный список кодов затмит роман "Война и мир", а с документированным листом можно ознакомиться
Ссылка скрыта от гостей
(для Winsock искать (Ctrl+F) по маске TDI и NDIS).Дочерней по отношению к DeviceIoControl() является функция ioctlsocket(). Эту функцию можно использовать для любого сокета в любом состоянии, независимо от протокола и подсистемы связи. Прототип функции выглядит так (нуль на выходе в EAX свидетельствует об успешной операции):
C-подобный:
ioctlsocket (
socket ; дескриптор сокета
cmd ; код управления
argp ; указатель на опцию кода (вкл/выкл)
);
;// Некоторые коды управления для fn. "ioctlsocket"
;===================================================
SIO_RCVALL = 0x98000001 ;// вкл/выкл Promisc
SIO_RCVALL_MCAST = 0x98000002 ; Promisc для многоадресной рассылки UDP
SIO_RCVALL_IGMPMCAST = 0x98000003 ; Promisc для IGMP-пакетов
SIO_KEEPALIVE_VALS = 0x98000004 ;
SIO_ABSORB_RTRALERT = 0x98000005 ;
SIO_UCAST_IF = 0x98000006 ;
SIO_LIMIT_BROADCASTS = 0x98000007 ;
SIO_INDEX_BIND = 0x98000008 ;
SIO_INDEX_MCASTIF = 0x98000009 ;
SIO_INDEX_ADD_MCAST = 0x9800000A ;
SIO_FLUSH = 0x28000004 ; Удаляет содержимое очереди отправки
SIO_MULTICAST_SCOPE = 0x8800000A ; TTL
SIO_GET_BROADCAST_ADDRESS = 0x48000005 ; Возвращает струк.SOCKADDR для UDP
SIO_ADDRESS_LIST_QUERY = 0x48000016 ; Cписок лок/интерфейсов, к которым можно привязать сокет
SIO_ROUTING_INTERFACE_QUERY = 0xC8000014 ; Возвращает адреса интерфейсов, удалённого хоста
FIOASYNC = 0x8004667D ; Вкл уведомлений при ожидании данных.
FIONBIO = 0x8004667E ; Блокирующий режим сокета: 0=да, 1=нет
FIONREAD = 0x4004667F ; Возвращает кол-во байт, доступных для чтения
5.5.2. Историческая справка
Ещё во-времена динозавров, разработчикам приходилось долго и муторно тестить поддержку сети на ошибки, в результате чего на свет появились "анализаторы пакетов". При помощи анализа, админы и службы тех/поддержки могут наблюдать за передвижением трафика в сети, диагностировать и устранять возникающие проблемы и т.д. Но как это часто бывает, обычный анализатор превратился в грозное оружие под названием "сниффер".
В былые времена, используя снифер хакеры рыбачили в сети и вылавливали из неё пароли и другую конфиденциальную инфу. Особенно актуально это было для таких протоколов как Telnet, где трафик гуляет в незашифрованном виде. Прослушивание линии стало возможным благодаря особенности архитектуры Ethernet, в которой все узлы подключены к одной среде передачи данных и делят её по-братски. Для прослушки локальной сети не нужно резать кабели – в ней и так все связаны кровными узами.
Но беззаботная жизнь на хабах продолжалась не долго.. В локальную сеть, как слон в посудную лавку, ввалился коммутатор Switch. Теперь хоть линия передачи как и прежде одна, зато узлы в ней разделены логически. Виной тому – всего-то крошечный логический элемент с аббревиатурой "И". Он имеет два входа и один выход, на который вешают узел сети. Если "И" на первый "И" на второй вход подать единицу, то затвор на выходе открывается и трафик поступает на подключённый к выходу узел. Таким образом, обычный логический элемент из двух транзисторов полностью перекрыл кислород сниферам, оставив в их владениях периметр только до коммутатора.
В противоположность свитчу, хаб представляет из-себя просто многопортовый слот типа тройника для электо-сети. В наше время, его можно использовать как разветвитель и он не разделяет подключённые к сети узлы. Трафик курсирует в нём в обе стороны, что только на руку сниферам. Структурные их схемы представлены ниже:
Для простоты понимания, в примере с коммутатором я исключил из схемы дешифратор. Этот электронный компонент может иметь, например, 4-входа и 16-выходов, чего достаточно для 16-портовых свитчей. На 4-входа дешифратора поступает двоичная тетрада последнего байта IP-адреса (см.рис.выше), по которой он подключает один из 16-ти выходных портов (бинарная логика).
Если собрать всё во-едино, то получается что в современных сетях снифать пакетики можно только внутри своей локальной сети. Высунуть нос наружу мешает в первую очередь коммутатор, после которого может быть ещё и VPN, и дальше маршрутизатор. В добавок, хоть мы и пронюхаем пакет в своей сетке LAN, всё-равно получить от него профит не получится, т.к. сейчас весь трафик передаётся только в зашифрованном виде и привести его в понятный вид не так просто. Только посмотрите на эту баррикаду, и всё сразу станет ясно:
5.5.3. Практика – пишем сниффер на RAW сокетах
Теперь посмотрим, что представляет из-себя сниффер на генетическом уровне..
Во-первых, нужно перевести сетевую карту в неразборчивый режим Promisc. Для этого задействуем функцию ioctlsocket() с кодом SIO_RCVALL равным 0x98000001, который отключит в контроллере способность фильтровать трафик на свой/чужой. Теперь остаётся в непрерывном режиме принимать пакеты функцией recv() в свой буфер и выводить его содержимое на экран.
После чтения очередной порции трафика, в буфере получим пакет, формат которого сильно зависит от типа протокола. Можно снабдить программу фильтром протоколов, например чтобы прожка принимала только TCP пакеты, UDP или ICMP. Но в демонстрационном примере лучше принимать всё-подряд, а прикрутить фильтр можно будет позже – главное понять суть.
В независимости от используемого отправителем протокола, пакет в буфере будет начинаться с IP-заголовка, а дальше - в игру вступает уже конкретный протокол. Если вспомнить, как инкапсулируются в пайлоад заголовки, то картина прояснится:
Чтобы вытянуть из пакета полезные данные payload, нам придётся сначала в IP-заголовке проверить поле "iph_proto", где будет указан код протокола, по которому был отправлен данный пакет. Если это IPPROTO_TCP с кодом 0х06, значит после IP-заголовка идёт 20-байтный TCP-заголовок (см.рис.выше). Для IPPROTO_UDP код будет 0х11, а для ICMP = 0x01. Дамп перехваченного пакета в отладчике выглядит так:
Ну и немного о деталях самого исходника..
Чтобы вывести тип протокола на экран, придётся по его коду искать соответствующую строку. Для этого я создал таблицу переходов, где первое значение это код-протокола из поля iph_proto заголовка IP, а второе – адрес строки с его именем в секции данных программы. Получив код я ищу его в таблице, и при совпадении беру следующее значение, которое будет указателем на соответствующую строку. Вот образец этой таблицы:
C-подобный:
protoTable dd 00, m1 ;// таблица адресов
dd 01, m2 ;
dd 02, m3 ;
dd 06, m4 ;
dd 17, m5 ;
m1 db 'IP' ,0 ;// массив строк
m2 db 'ICMP',0 ;
m3 db 'IGMP',0 ;
m4 db 'TCP' ,0 ;
m5 db 'UDP' ,0 ;
unknown db 'Unknown!',0 ;
Алгоритм всего кода снифера распишем так:
- Узнать свой IP и вывести его на экран. Это позволит ориентироваться в адресах – куда, кому, зачем и почему.
- Создать сырой RAW-сокет типа IPPROTO_IP, что позволит ловить пакеты всех предыдущих протоколов в стеке TCP/IP. Например если создать сокет типа IPPROTO_UDP, то получим фильтр пакетов и кроме дейтаграмм UDP наш снифер нишиша не получит.
- Перевести сетевой контролёр в режим Promisc – без этого ничего не выйдет. Результаты всех операций будем выводить на экран.
- Зациклиться на приёме пакетов функцией recv() и внутри каждого цикла парсить приёмный буфер. Здесь я ввёл счётчик.. можете установить его на любое кол-во пакетов.
- Вот собственно и все шаги..
C-подобный:
format pe console
entry start
include 'win32ax.inc'
;----------
.data
wsa WSADATA
addr sockaddr_in
capt db 13,10,' Sniffer example v0.1'
db 13,10,' **************************************',13,10,0
proto db 13,10
db 13,10,' Proto == %s',0
sPort db 13,10,' Source.....: port %05d',0
src db ' ',0xb1,' ip %s',0
dPort db 13,10,' Destination: port %05d',0
dest db ' ',0xb1,' ip %s',0
ttl db 13,10,' TTL........: %d',0
dSize db 13,10,' Data size..: %d byte',0
check db 13,10,' CheckSum...: %04x',0
succes db 13,10
db 13,10,' ~~~~~~'
db 13,10,' Sniff successful..'
db 13,10,' Press any key for exit...',0
protoTable dd 00,m1 ;// таблица адресов
dd 01,m2 ;
dd 02,m3 ;
dd 06,m4 ;
dd 17,m5 ;
m1 db 'IP' ,0 ;// массив строк
m2 db 'ICMP',0 ;
m3 db 'IGMP',0 ;
m4 db 'TCP' ,0 ;
m5 db 'UDP' ,0 ;
unknown db 'Unknown!',0 ;
frmt db '%s',0 ; спецификатор для scanf()
s dd 0 ; дескриптор сокета
flag dd 1 ; флаг "Promisc-mode"
errMes db 128 dup(0) ; под сообщение об ошибках
hostName db 64 dup(0) ; имя нашего хоста (узла)
count dd 50 ; счётчик сбора пакетов
align 16 ; выравнивание памяти на параграф
buff IP_Header ; буфер под пакет сниффера
;----------
.code
start:
;//---- Шапка и получаем имя/IP нашего хоста ------
invoke WSAStartup,0x0101,wsa
cinvoke printf,capt
invoke gethostname,buff,64
invoke gethostbyname,buff
mov eax,[eax+12]
mov eax,[eax]
mov eax,[eax]
;//---- Заполняем структуру "sockaddr_in" ---------
mov [addr.sin_addr],eax ; наш IP-адрес
mov [addr.sin_port],0 ; порт = произвольный
mov [addr.sin_family],AF_INET ; тип адреса
;//---- Дополнительная информация юзеру -----------
;// покажем текущий наш адрес
invoke inet_ntoa,eax
cinvoke printf,<' 0. Host IP address.... %s',13,10,0>,eax
;// пробуем создать RAW-IP сокет
cinvoke printf,<' 1. Create RAW_socket..',13,10,0>
invoke socket,AF_INET,SOCK_RAW,IPPROTO_IP
mov [s],eax
or eax,eax ; проверка на ошибку..
jnz @f ; вперёд (forward), если не нуль
jmp GetError ; иначе: покажем код ошибки, и на выход!
;// пробуем привязать к сокету адрес
@@: cinvoke printf,<' 2. Bind address.......',13,10,0>
invoke bind,[s],addr,16
or eax,eax
jz @f ; forward, если нуль (успешно)
jmp GetError
;// пробуем включить беспорядочный режим сетевой карты
@@: cinvoke printf,<' 3. Set promisc mode...',13,10,0>
invoke ioctlsocket,[s],SIO_RCVALL,flag ;// Receive All Packet
or eax,eax
jz @sniff ; нуль = успешно!
jmp GetError
;//--------------------------------------------
;//--- Бесконечный цикл приёма пакетов --------
@sniff: invoke recv,[s],buff,512,0
;//--- Парсим перехваченный пакет и выводим инфу
;// имя протокола --------
xor ebx,ebx ;
mov bl,[buff.iph_proto] ; EBX = код протокола
mov esi,protoTable ; ESI = адрес таблицы имён
mov ecx,5 ; число записей в ней
@findProtoName: ;
lodsd ; EAX = очередной код из таблицы
cmp ebx,eax ; сравнить с EBX
jz @ok ; если совпало.
add esi,4 ; иначе: на сл.элемент в таблице
loop @findProtoName ; промотать ЕСХ-раз..
mov eax,unknown ; облом - неизвестный код протокола.
jmp @prn ;
@ok: lodsd ; если нашли: EAX = адрес строки с именем
@prn: cinvoke printf,proto,eax ; выводим его на консоль!
;//--- Порт отправителя ---------------
mov esi,buff.iph_dest+4 ; он лежит после IP-заголовка
lodsw ; берём его в AX (word)
push esi ; запомнить указатель на порт получателя
and eax,0xffff ; оставить в EAX только AX
xchg ah,al ; обменять в АХ байты местами
cinvoke printf,sPort,eax ; вывести порт на косоль!
;//--- IP отправителя (Source) --------
mov eax,[buff.iph_src] ; лежит в IP-заголовке
invoke inet_ntoa,eax ; перевести в строку
cinvoke printf,src,eax ; принт..
;//--- Порт получателя ----------------
pop esi ; снимаем указатель со-стека
lodsw ; берём порт в АХ
and eax,0xffff ;
xchg ah,al ; обменять байты местами
cinvoke printf,dPort,eax ; на консоль его..
;//--- IP получателя (Destination) ----
mov eax,[buff.iph_dest] ; см.выше..
invoke inet_ntoa,eax ;
cinvoke printf,dest,eax ;
;//--- Выводим TTL (Time-to-Live) -----
xor eax,eax ; он размером в байт,
mov al,[buff.iph_ttl] ; ..и лежит в IP-заголовке
cinvoke printf,ttl,eax ;
;//--- Выводим размер данных ----------
xor eax,eax ;
mov ax,[buff.iph_len] ; размер хранится там-же
xchg ah,al ; восстановить порядок байт из LAN в CPU
cinvoke printf,dSize,eax ;
;//--- Контрольная сумма пакета -------
xor eax,eax ;
mov ax,[buff.iph_xsum] ; у CRC переставлять байты не надо.
cinvoke printf,check,eax ;
dec [count] ; уменьшить счётчик сниффера пакетов
jnz @sniff ; повторить, если не нуль..
;//--- Конец программы ----------------
cinvoke printf,succes ; мессага "Всё ОК!"
@exit: cinvoke scanf,frmt,frmt+5 ; ждём нажатия клавиши..
invoke ExitProcess,0 ; на выход!
;//*****************************************
;//--- Функция вывода ошибок
GetError:
invoke WSAGetLastError
invoke FormatMessage,0x1000,0,eax,0,errMes,128,0
invoke CharToOem,errMes,errMes
cinvoke printf,errMes
jmp @exit
;//--- Секция импорта ------
section '.idata' import data readable ;
library kernel32,'kernel32.dll',\ ; импортируемые библиотеки
user32,'user32.dll',\
wsock32,'wsock32.dll',\ ; ^^^
msvcrt,'msvcrt.dll' ; ^^^
import msvcrt, printf,'printf',scanf,'scanf'
include 'api\kernel32.inc'
include 'api\user32.inc'
include 'api\wsock32.inc'
Как показывает скрин, снифер исправно нюхает сеть и если его не остановить, то в поле зрения попадаются интересные пакеты, в которых IP-адреса вообще не принадлежат моей сети. Судя по всему, без моего ведома кто-то периодически стучится в сеть в поисках своего сервера (или демона на моём узле). По сути прожка вместе с IP выводит и порты, так-что вычислить софтину и заинжектить ей ледокаин пониже радикулита, будет не сложно.
--------------------------------------------------------
Разговаривать о сети можно бесконечно. В её архитектуре столько потайных мест, что думаю разработчики сами уже запутались в ней. Поэтому этот пост будет последним в данной весовой категории, и мы проследуем дальше.. в дебри файловых систем, различных контролёров, работы с дисковыми накопителями, механизмами защиты и т.д.. Держите руку на пульсе..
Последнее редактирование: