В этой статье мы рассмотрим создание простого сниффера под ОС Windows.
Кому интересно, добро пожаловать под кат.
Введение
Цель: написать программу, которая будет захватывать сетевой трафик (Ethernet, WiFi), передающийся по протоколу IP.
Средства: Visual Studio 2005 или выше.
Подход, который здесь описан, не принадлежит лично автору и успешно применяется во многих коммерческих, а также категорически бесплатных программах (привет, GPL).
Сей труд предназначен прежде всего для новичков в сетевом программровании, которые, однако, имеют хотя бы базовые знания в области сокетов вообще, и windows-сокетов в частности. Здесь я часто буду писать общеизвестные вещи, потому что предметная область специфическая, если что-то пропустить — в голове будет каша.
Надеюсь, Вам будет интересно.
Теория (читать не обязательно, но желательно)
В данный момент подавляющее большинство современных информационных сетей базируются на фундаменте стека протоколов TCP/IP. Стек протоколов TCP/IP (англ. Transmission Control Protocol/Internet Protocol) — собирательное название для сетевых протоколов разных уровней, используемых в сетях. В настоящей статье нас будет интересовать в основном протокол IP — маршрутизируемый сетевой протокол, используемый для негарантированной доставки данных, разделяемых на так называемые пакеты (более верный термин – дейтаграмма) от одного узла сети к другому.
Особый интерес для нас представляют IP-пакеты, предназначенные для передачи информации. Это достаточно высокий уровень сетевой OSI-модели данных, когда можно обстрагироваться от устройства и среды передачи данных, оперируя лишь логическим представлением.
Совершенно логичным является то обстоятельство, что рано или поздно должны были появится инструменты для перехвата, контроля, учета и анализа сетевого трафика. Такие средства обычно называется анализаторами трафика, пакетными анализаторыми или снифферами (от англ. to sniff — нюхать). Это — сетевой анализатор трафика, программа или программно-аппаратное устройство, предназначенное для перехвата и последующего анализа, либо только анализа сетевого трафика, предназначенного для других узлов. [1]
Практика (разговор по существу)
На данный момент создано достаточно много программного обеспечения для прослушивания трафика. Наиболее известный из них: Wireshark. Естественно, пожинать его лавры цель не стоит — нас интересует задача перехвата трафика методом обычного «прослушивания» сетевого интерфейса. Важно понимать, что мы не собираемся заниматься взломом и перехватывать чужой трафик. Нужно всего лишь просматривать и анализировать трафик, который проходит через наш хост.
Для чего это может понадобиться:
- Смотреть текущий поток трафика через сетевое соеднинение (входящий/исходящий/всего).
- Перенаправлять трафик для последующего анализа на другой хост.
- Теоретически, можно попытаться применить его для взлома WiFi-сети (мы ведь не собираемся этим заниматься?).
В отличие от Wireshark, который базируется на библиотеке libpcap/WinPcap, наш анализатор не будет использовать этот драйвер. Чего уж там, у нас вообще не будет драйвера, и свой NDIS(о ужас!) мы писать не собираемся. Про это можно прочитать в этом топике. Он будет просто пассивным наблюдателем, использующим только библиотеку WinSock. Использование драйвера в данном случае избыточно.
Как так? Очень просто.
Ключевым шагом в превращении простого сетевого приложения в сетевой анализатор является переключение сетевого интерфейса в режим прослушивания (promiscuous mode), что и позволит ему получать пакеты, адресованные другим интерфейсам в сети. Этот режим заставляют сетевую плату принимать все кадры, вне зависимости от того, кому они адресованы в сети. [2]
Начиная с Windows 2000 (NT 5.0) создать программу для прослушивания сегмента сети стало очень просто, т.к. ее сетевой драйвер позволяет перевести сокет в режим приёма всех пакетов.
Включение неразборчивого режима
long flag = 1;
SOCKET socket;
#define SIO_RCVALL 0x98000001
ioctlsocket(socket, SIO_RCVALL, &RS_Flag);
Наша программа оперирует IP-пакетами, и использует библиотеку Windows Sockets версии 2.2 и «сырые» сокеты (raw sockets). Для того чтобы получить прямой доступ к IP-пакету, сокет нужно создавать следующим образом:
Создание сырого сокета
s = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
Здесь вместо константы SOCK_STREAM (протокол TCP) или SOCK_DGRAM (протокол UDP), мы используем значение SOCK_RAW. Вообще говоря, работа с raw sockets интересна не только с точки зрения захвата трафика. Фактически, мы получаем полный контроль за формированием пакета. Вернее, формируем его вручную, что позволяет, например, послать специфический ICMP-пакет…
Идем дальше. Известно, что IP-пакет состоит из заголовка, служебной информации и, собственно, данных. Советую заглянуть сюда, чтобы освежит знания. Опишем в виде структуры IP-заголовок (спасибо отличной статье на RSDN [3]):
Описание структуры IP-пакета
typedef struct _IPHeader
{
unsigned char ver_len; // версия и длина заголовка
unsigned char tos; // тип сервиса
unsigned short length; // длина всего пакета
unsigned short id; // Id
unsigned short flgs_offset; // флаги и смещение
unsigned char ttl; // время жизни
unsigned char protocol; // протокол
unsigned short xsum; // контрольная сумма
unsigned long src; // IP-адрес отправителя
unsigned long dest; // IP-адрес назначения
unsigned short *params; // параметры (до 320 бит)
unsigned char *data; // данные (до 65535 октетов)
}IPHeader;
Главная функция алгоритма прослушивания будет выглядеть следующим образом:
Функция захвата одного пакета
IPHeader* RS_Sniff()
{
IPHeader *hdr;
int count = 0;
count = recv(RS_SSocket, (char*)&RS_Buffer[0], sizeof(RS_Buffer), 0);
if (count >= sizeof(IPHeader))
{
hdr = (LPIPHeader)malloc(MAX_PACKET_SIZE);
memcpy(hdr, RS_Buffer, MAX_PACKET_SIZE);
RS_UpdateNetStat(count, hdr);
return hdr;
}
else
return 0;
}
Здесь все просто: получаем порцию данных с помощью стандартной функции socket-функции recv, а затем копируем их в структуру типа IPHeader.
И, наконец, запускаем бесконечный цикл захвата пакетов:
Захватым все пакеты, которые попадут на наш сетевой интерфейс
while (true)
{
IPHeader* hdr = RS_Sniff();
// обработка IP-пакета
if (hdr)
{
// печатаем заголовок в консоли
}
}
Немного оффтопика
Здесь и далее у некоторых важных функций и переменных автор сделал префкис RS_ (от Raw Sockets). Проект делал 3-4 года назад, и была шальная мысль написать полноценную библиотеку для работы с сырыми сокетами. Как это часто бывает, после получения сколь-нибудь значимых(для автора) результатов, энтузиазм угас, и дальше учебного примера дело не полшло.
В принципе, можно пойти дальше, и описать заголовки всех последующих протоколов, находящихся выше. Для этого необходимо анализировать поле protocol в структуре IPHeader. Посмотрите на пример кода (да, там должен быть switch, чёрт возьми!), где происходит раскрашивание заголовка в зависимости от того, какой протокол имеет пакет, инкапсулированный в IP:
/*
* Выделение пакета цветом
*/
void ColorPacket(const IPHeader *h, const u_long haddr, const u_long whost = 0)
{
if (h->xsum)
SetConsoleTextColor(0x17); // если пакет не пустой
else
SetConsoleTextColor(0x07); // пустой пакет
if (haddr == h->src)
{
SetConsoleTextColor(BACKGROUND_BLUE | /*BACKGROUND_INTENSITY |*/
FOREGROUND_RED | FOREGROUND_INTENSITY); // "родной" пакет на отдачу
}
else if (haddr == h->dest)
{
SetConsoleTextColor(BACKGROUND_BLUE | /*BACKGROUND_INTENSITY |*/
FOREGROUND_GREEN | FOREGROUND_INTENSITY); // "родной" пакет на прием
}
if (h->protocol == PROT_ICMP || h->protocol == PROT_IGMP)
{
SetConsoleTextColor(0x70); // ICMP-пакет
}
else if(h->protocol == PROT_IP || h->protocol == 115)
{
SetConsoleTextColor(0x4F); // IP-in-IP-пакет, L2TP
}
else if(h->protocol == 53 || h->protocol == 56)
{
SetConsoleTextColor(0x4C); // TLS, IP with Encryption
}
if(whost == h->dest || whost == h->src)
{
SetConsoleTextColor(0x0A);
}
}
Однако это существенно выходит за рамки этой статьи. Для нашего учебного примера вполне достаточно будет посмотреть ip-адреса хостов, с которых и на которые идет трафик, и посчитать его количество в единицу времени(готовая программа в архиве в конце статьи).
Для того, чтобы отобразить данные IP-заголовка, необходимо реализовать функцию преобразования заголовка (но не данных) дейтаграммы в строку. В качестве примера реализации, можно предложить такой вариант:
Преобразование IP-заголовка в строку
inline char* iph2str(IPHeader *iph)
{
const int BUF_SIZE = 1024;
char *r = (char*)malloc(BUF_SIZE);
memset((void*)r, 0, BUF_SIZE);
sprintf(r, "ver=%d hlen=%d tos=%d len=%d id=%d flags=0x%X offset=%d ttl=%dms prot=%d crc=0x%X src=%s dest=%s",
BYTE_H(iph->ver_len),
BYTE_L(iph->ver_len)*4,
iph->tos,
ntohs(iph->length),
ntohs(iph->id),
IP_FLAGS(ntohs(iph->flgs_offset)),
IP_OFFSET(ntohs(iph->flgs_offset)),
iph->ttl, iph->protocol,
ntohs(iph->xsum), nethost2str(iph->src),
nethost2str(iph->dest)
);
return r;
}
На основании приведенных выше базовых сведений, получается вот такая небольшая программа (жуткое название ss, сокр. от англ. simple sniffer), реализующая локальное прослушивание IP-трафика. Интерфейс ее приведен ниже на рисунке.
Исходный и бинарный код предоставляю как есть, таким как он был несколько лет назад. Сейчас мне на него страшно смотреть, и все же, он вполне читабельный (конечно же, нельзя быть таким самоуверенным). Для компиляции будет достаточно даже Visual Studio Express 2005.
Что у нас получилось в итоге:
- Сниффер работает в режиме пользователя, однако требует привилегии администратора.
- Пакеты не фильтруются, отображаясь как есть (можно добавить настраиваемые фильтры — предлагаю подробно рассмотреть эту тему в следующей статье, если интересно).
- WiFi-трафик тоже захватывается(все зависит от конкретной модели чипа, у Вас может и не работать, как у меня несколько лет назад), хотя есть AirPcap, которая чудесно это умеет делать, но стоит денег.
- Весь поток дейтаграмм логируется в файл (см. архив, приложенный в конце статьи).
- Программа работает в качестве сервера на порту 2000. Можно подключиться с помощью утилиты telnet к хосту и произвести мониторинг потоков трафика. Количество подключений ограничено двадцатью (код не мой, нашел на просторах сети и применял для экспериментов; удалять не стал — жалко)
Спасибо за внимание, проздравляю иок и всех-всех-всех с наступающим Рождеством!
Автор: antonpv