С/C++ - Самопальный снифер под Linux

kas1e

Xakep, номер #050, стр. 082-085

Sniffer - англ. "нюхач". Такой вот незамысловатый перевод довольно распространенного на сегодняшний день слова. Тема гораздо шире, чем может показаться на первый взгляд, и поэтому для более полного понимания начнем с краткой истории, постепенно разгоняясь и углубляясь в детали.

В середине 90-х годов компания Network Associates (организованная в 1989) выпускает программку, которая позволяет полностью следить за происходящим в сети и контролировать передаваемые данные. Программку назвали Network Analyzer и выпустили ее под маркой Sniffer(r). После этого большинство вновь выходящих сетевых анализаторов от сторонних контор стали называть сниферами, что не очень точно, но всех, в общем, устраивает. Сегодня сниферы сильно помогают как сетевым администраторам в поиске неисправностей, так и различного рода злодеям в поиске чего душе угодно.

Техническая сторона

Что должен делать простейший снифер? Правильно, он должен вынюхивать. Вынюхивать данные, передаваемые по сети, топология построения которой чаще всего была основана на Etheet с участием пассивных хабов (теперь свичи стали настолько дешевы, что их продают под названием хабы). В этом основное предназначение стандартного снифера. Давай посмотрим, как передаются данные в такого рода сетях.

Допустим, у нас имеется сеть с несколькими машинами, у каждой из которых есть по сетевой карте, и на каждой сетевой карте настроено по IP. Включаем сеть. Какой-то хост захотел передать данные, что он сделает? Он пошлет широковещательный запрос на broadcast-адрес с вопросом типа "вот тут у меня есть IP, чей это?". Все машины будут брать этот пакет и смотреть, как только найдется машина, которая соответствует запрошенному IP (это смотрится по соответствиям ip-mac адресам), она отправит запросившей машине ответ типа "да-да, я нашлась и готова меняться данными". Вот так работает сетевая карта в обычном режиме. Т.е. она сравнивает некие данные, и если они не подходят - обработка пакетов завершается. Однако есть некий режим, который говорит сетевой карте в любом случае обрабатывать пакеты, ходящие по сети. Т.е. любой пакет, который физически будет видеть сетевая карта, должен быть обработан. Режим этот называется "promiscuous". Переводится как "смешанный" или "неразборчивый". В этот режим мы можем переходить как программно (имеется в виду писать установку в promisc кодом), так и всякими подсобными утилитами, такими как ifconfig (утилита ;)). В этой статье описан переход в promisc, осуществляемый ifconfig'ом, что проще, и к тому же уменьшается (хоть и незначительно) размер кода/исполнимого файла. Команда для перехода, как я сказал, очень проста, но чтобы не возникло лишних неприятностей, вот она:

# ifconfig eth0 promisc

и соответственно для перевода обратно:

# ifconfig eth0 -promisc

Т.е. то, что мы будем писать, будет работать в любом случае. Хоть с promisc, хоть без него. Только в одном случае будут анализироваться данные двух хостов, а в другом - всех. Главное понимать, что ловить все пакеты мы сможем только тогда, когда в сегменте стоит пассивный хаб - раритет, на смену которому приходят равноценные свичи. Но для анализа и понимания работы - разницы нет. К тому же, есть разные хитрые способы, которые реализуют разные люди ;).

{
unsigned int ip_length:4;   // длина идет раньше из-за того, что на x86
unsigned int ip_version:4;  // порядок следования байт - little endian
unsigned char ip_tos;
unsigned short ip_total_length;
unsigned short ip_id;
unsigned short ip_flags;
unsigned char ip_ttl;
unsigned char ip_protocol;
unsigned short ip_chsum;
unsigned int ip_source;     // вот это добавили уже сами
unsigned int ip_dest;        // чтобы проще было
};

Типы int, char, short мы уже выбираем сами в зависимости от длины поля. Далее, имея вот такую структуру, мы сможем легко адресоваться по буферу, используя данные поля. Соответственно и ARP-пакет описывается точно так же:

struct my_arp/* смещение описание */
{
unsigned short arp_hardware_type;/* 2b 0001 = Ethee*/
unsigned short arp_protocol;/* 2b 0800 = IPv4*/
unsigned char arp_mac_len;/* 1b 06 = 6 байт*/
unsigned char arp_ip_len;/* 1b 04 = 4 байта*/
unsigned short arp_opcode;/* 2b 0001/2 = запрос/ответ */
unsigned char arp_mac_source[6];/* 6b mac источника*/
unsigned char arp_ip_source[6];/* 6b ip источника*/
unsigned char arp_mac_dest[6];/* 6b mac назначения*/
unsigned char arp_ip_dest[6];/* 6b ip назначения*/
};

Для более полного представления смотри соответствующие RFC (в блок-врезке) и заголовочные файлы linux'a.

Вот таким образом ты можешь наполнять какой-нибудь файл различными структурами, а потом просто подключать его в главную программу. Получается довольно удобно. Так же можно поступать и с функциями, но так как наши программки довольно маленькие, нам это не понадобится.

Анализатор пакетов

Эта программка делает простые вещи. При запуске она начинает читать всю информацию, проходящую через сетевую карту, и выводить на экран информацию об etheet/ip/arp-пакетах. В общем-то, для своего размера (2-3 килобайта) это очень приятный сниферок. Если у тебя сегмент с пассивным хабом, то можно перевести ifconfig'ом карточку в promisc режим и ужаснуться огромнейшему количеству пакетов :).

Сначала мы опишем все переменные, которые нам пригодятся в анализаторе:

int sock;// Дескриптор на открытый сокет
unsigned char buffer[65555];// Буфер сделаем побольше
struct my_ip *ip;// Вот наши
struct my_arp *arp;// структурки
struct in_addr need;// А это необходимо для inet_ntoa макроса

После этого поднимаем наш супер-сокет:

sock = socket(PF_PACKET,SOCK_PACKET,htons(ETH_P_ALL));

И далее в цикле начинаем читать данные с сокета и парсить в понятном формате на экран:

while(1)
{
recvfrom(sock,buffer,sizeof buffer,0,0,0); // прочитали в буфер
/* Теперь мы парсим, как нам будет угодно. Например, для вывода типа в ARP-пакете мы показываем, где у нас в буфере ARP-пакет, и какое поле в структуре выводить: */
arp = (struct my_arp *)(buffer+14);
printf ("arp hardware type :%.04xn",arp->arp_hardware_type);
/* и так далее со всем остальным. Полный пример идет бонусом к статье. */
}

В итоге у нас получился простейший "анализатор пакетов", который и является снифером.

POP3-снифер

Для выискивания каких-то данных мы рассмотрим pop3-снифер. Он выбирает из буфера все строки, в которых есть слова "user " и "pass ". Затем выводит эти данные вместе с IP-адресами в лог-файл. Здесь нет ничего сложного, зато ярко демонстрируется незащищенность стандартного pop3-протокола. Вначале описываются необходимые переменные, а после этого делается функция, дабы читать код было проще:

void print_ips()// делаем функцию
{
struct my_ip *ip;// описали нашей структурой
struct in_addr need;// для необходимости при преобразовании
ip = (struct my_ip *)(buffer+14);// показали, где в буфере лежит IP
need.s_addr = ip->ip_source;// нужное поле
fprintf (fd,"IP source :%sn",inet_ntoa(need));// вывод
need.s_addr = ip->ip_dest;// нужное поле
fprintf (fd,"IP dest:%sn",inet_ntoa(need));// вывод
};

Функция просто выводит в файл IP-адреса, взятые с наших структур. Для преобразования используется структура in_addr и макрос inet_ntoa. Далее мы, как и выше, сначала поднимаем сокет, а потом делаем цикл, в котором будем читать в буфер данные с сокета. После этого начинаем искать в буфере нужные нам данные:

memmem (buffer,bytes_recieved,"user ",5);

Этой строчкой мы ищем 5 нужных байт в буфере. Используем memmem, чтобы не встала проблема встречи с нулями (конец строки). Если нашли данные, то ищем далее x0d (конец строки) и вставляем туда 0 (для упрощения при выводе), если нет - наш цикл продолжится. И так же повторяем процедуру при поиске "pass " фразы. Вот пример обработки "user ":

/* user */
p = memmem (buffer,bytes_recieved,"user ",5);// ищем в буфере "user"
if (p==0) {}// если не нашли - ничего,
else { d = memmem (p,50,"x0d",1);// если нашли, то ищем 0d
if (d==0) {}// если не нашли - ничего,
else{// если нашли - открываем
fd = fopen ("sniff_logs","a+");// файл, суем нуль в конец
print_ips();// строки и выводим все
memset ((char *)d,'',1);// это в файл
fprintf (fd,"%sn",p);// после чего файл close
fclose (fd);
};
};

И аналогично делается для "pass ". Работоспособные примеры можно скачать с www.xakep.ru или на диске ][.

Заключение

Вот, в принципе, и все. В заключение я хотел бы поблагодарить товарищей Сивухина и Синюхина. Фамилии, естественно, вымышленные, а сами товарищи в глубоком подполье :). И еще. Используй полученные знания в позитивных целях.

Также советую почитать книгу "Язык С. Кернигана и Ритчи".

Содержание
ttfb: 4.6379566192627 ms