• Категории
  • Подписка
  • Разместить статью
02/04/10 1 2374 Сетевые анализаторы
-

Анализатор сетевого трафика и пакетный фильтр своими руками (Часть 1)

cc++_securos.org.ua

Недавно, будучи не совсем системным администратором, а скажем, просто любителем, я задался вопросом “как же, все-таки, защитить сеть от беспредельщиков?”. Благо информации на эту тему “пруд пруди”, плюс когда-то изучал эту тему, поэтому остались наработки. И вот так появились циклы статей про сетевые анализаторы, сканеры, а в будущем будет материал и про детекторы.

К чему я виду? А к тому, что люди читают посты, комментируют их, порой задают вполне конкретные и адекватные вопросы. Так и произошло в очередной раз. Просматривая новые комментарии, увидел такое: “…а есть ли сетевые анализаторы под Windows?..”. Сказать нет нельзя (и я думаю человек – это понимал, задавая вопрос), но такого просто не может быть. На вопрос я  “мало-мальски” ответил, еще добавил, что можно написать самому, имея “рыбу” – в сети хватает. А потом промелькнула мысль поискать скрипт такого анализатора и наткнулся на материал 2005 года, но вполне показательный. Сайт источник http://www.oszone.net (см. www.oszone.net), но и это не оригинал как оказалось, то есть где-то в закоулках интернета, в архивах opennet-а скрываются-таки, действительно ценные вещи!

Рассмотрим, наконец-то, пример программной реализации такого анализатора под Linux, который представляет собой программный код на языке С. Разобраться в котором без наличия определенных навыков, да это и понятно, будет сложно. Кроме знаний программирования нужно знать основы построения локальных сетей на протоколе Ethernet. И, само собой, разумеется, знать технологию перехвата пакетов, чтоб реализовать ее в программе. Начнем!

Как говорит источник, есть много способов перехватить данные сетевого трафика. Самым оптимальным является использование специализированных библиотек, например, libpcap. Именно эту библиотеку используют в своей работе tcpdump и snort. Мы же в нашем примере не будем опираться на библиотеки, а рассмотрим, как перехватить данные сетевого трафика, используя для этого пакетный сокет.

При создании сокета стандартным вызовом socket (int domain, int type, int protocol) параметр domain определяет коммуникационный домен, в котором будет использоваться сокет. Обычно используются значения PF_UNIX для соединений, ограниченных локальной машиной, и PF_INET, для соединений, базирующихся на протоколе IPv4. Аргумент type определяет тип создаваемого сокета и имеет несколько значений. Значение SOCK_STREAM указывается при создании сокета для работы в режиме виртуальных соединений (протокол TCP), а значение SOCK_DGRAM — для работы в режиме пересылки дейтаграмм (протокол UDP). Последний параметр protocol определяет используемый протокол (в соответствии с IEEE 802.3).

В версиях ядра Linux, начиная с 2.2, появилась поддержка нового типа сокетов — пакетных сокетов. Пакетные сокеты используются для отправления и приема пакетов на уровне драйверов устройств. Сокеты данного типа создаются вызовом socket (PF_PACKET, int type, int protocol). Параметр type принимает значение SOCK_RAW или SOCK_DGRAM. Пакеты типа SOCK_RAW передаются драйверу устройства и принимаются от него без всяких изменений данных пакета. SOCK_DGRAM работает на более высоком уровне. Физический заголовок (MAC-адрес) удаляется перед тем, как пакет отправляется на обработку пользователю.

Алгоритм работы таков:

  • после запуска на выполнение анализатор определяет параметры сетевого интерфейса eth0, такие как IP адрес, маска подсети, размер MTU, индекс, и переводит интерфейс в неразборчивый режим (promiscuous mode). В этом режиме интерфейс принимает все пакеты, циркулирующие в сети, даже если они не адресованы данному хосту;
  • создается пакетный сокет и выполняется его привязка к выбранному сетевому интерфейсу (eth0). Далее анализатор в бесконечном цикле выполняет прием сетевых пакетов и отображает данные об этом пакете – MAC-адреса и IP-адреса отправителя и получателя, размер пакета, размер IP заголовка, тип транспортного протокола (TCP/UDP), порт отправителя и получателя. Выход из цикла осуществляется по приходу сигнала SIGINT (генерируется комбинацией клавиш Ctrl-C);
  • получив сигнал SIGINT, анализатор прерывает цикл приема пакетов, снимает флаг неразборчивого режима с сетевого интерфейса и завершает выполнение.

Определять параметры сетевого интерфейса и переключать его режимы будет функция getifconf(). Прототип данной функции выглядит следующим образом:

int getifconf(__u8 *, struct ifparam *, int)

Функция принимает три параметра:

  • указатель на строку, содержащую символьное имя сетевого интерфейса;
  • указатель на структуру, в которой будут сохранены параметры сетевого интерфейса. Определение этой структуры будет рассмотрено ниже;
  • флаг, определяющий режим работы интерфейса.

Создавать пакетный сокет будет функция getsock_recv():

int getsock_recv (int)

Параметром функции является индекс сетевого интерфейса, к которому будет привязан сокет. Определение структуры struct ifparam, в которой будут храниться параметры сетевого интерфейса, разместим в файле analizator.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* Листинг 1. Файл analizator.h */
 
#include <linux/types.h>
 
#define PROMISC_MODE_ON 1 // флаг включения неразборчивый режим
#define PROMISC_MODE_OFF 0 // флаг выключения неразборчивого режима
 
struct ifparam {
    __u32 ip;	// IP адрес
    __u32 mask;	// маска подсети
    int mtu;	// размер MTU
    int index;	// индекс интерфейса
} ifp;

Рассмотрим подробно каждую функцию.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/*
 * Листинг 2. Функция определения параметров сетевого интерфейса и
 * переключения режимов (файл getifconf.c)
 */
 
#include <linux/socket.h>
#include <linux/ioctl.h>
#include <linux/if.h>
#include <linux/in.h>
#include "analizator.h"
 
int getifconf(__u8 *intf, struct ifparam *ifp, int mode)
{
    int fd;
    struct sockaddr_in s;
    struct ifreq ifr; // см. <linux/if.h>
 
    memset((void *)&ifr, 0, sizeof(struct ifreq));
    if((fd = socket(AF_INET,SOCK_DGRAM,0)) < 0)	return (-1);
 
    sprintf(ifr.ifr_name,"%s",intf);
 
/*
 * Проверяем флаг режима. Если он установлен в 0, неразборчивый режим
 * необходимо отключить, поэтому сразу выполняется переход на метку setmode
 */
    if(!mode) goto setmode;
 
/*
 * Определяем IP адрес сетевого интерфейса
 */
    if(ioctl(fd, SIOCGIFADDR, &ifr) < 0) {
	perror("ioctl SIOCGIFADDR");
	return -1;
    }
    memset((void *)&s, 0, sizeof(struct sockaddr_in));
    memcpy((void *)&s, (void *)&ifr.ifr_addr, sizeof(struct sockaddr));
    memcpy((void *)&ifp->ip, (void *)&s.sin_addr.s_addr, sizeof(__u32));
 
/*
 * Определяем маску подсети
 */
    if(ioctl(fd, SIOCGIFNETMASK, &ifr) < 0) {
	perror("ioctl SIOCGIFNETMASK");
	return -1;
    }
    memset((void *)&s, 0, sizeof(struct sockaddr_in));
    memcpy((void *)&s, (void *)&ifr.ifr_netmask, sizeof(struct sockaddr));
    memcpy((void *)&ifp->mask, (void *)&s.sin_addr.s_addr, sizeof(u_long));
 
/*
 * Определяем размер MTU
 */
    if(ioctl(fd, SIOCGIFMTU, &ifr) < 0) {
	perror("ioctl SIOCGIFMTU");
	return -1;
    }
    ifp->mtu = ifr.ifr_mtu;
 
/*
 * Индекс интерфейса
 */
    if(ioctl(fd, SIOCGIFINDEX, &ifr) < 0) {
	perror("ioctl SIOCGIFINDEX");
	return -1;
    }
    ifp->index = ifr.ifr_ifindex;
 
 
/*
 * Устанавливаем заданный режим работы сетевого интерфейса
 */
setmode:
 
/*
 * Получаем значение флагов
 */
    if(ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) {
	perror("ioctl SIOCGIFFLAGS");
	close(fd);
	return -1;
    }
 
/*
 * В зависимости от значения третьего параметра функции, устанавливаем
 * или снимаем флаг неразборчивого режима
 */
    if(mode) ifr.ifr_flags |= IFF_PROMISC;
    else ifr.ifr_flags &= ~(IFF_PROMISC);
 
/*
 * Устанавливаем новое значение флагов интерфейса
 */
    if(ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) {
	perror("ioctl SIOCSIFFLAGS");
	close(fd);
	return (-1);
    }
 
    return 0;
}

Значения SIOCGIFADDR, SIOCGIFNETMASK, SIOCGIFMTU и др. определены в файле <linux/sockios.h>. Этот файл не включен в список заголовочных файлов, т.к. он уже определен в <linux/socket.h>.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
 * Листинг 3. Функция создания пакетного сокета (файл getsock_recv.c)
 */
 
#include <linux/socket.h>
#include <linux/types.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
 
int getsock_recv(int index)
{
    int sd; // дескриптор сокета
/*
 * При работе с пакетными сокетами для хранения адресной информации
 * сетевого интерфейса вместо структуры sockaddr_in используется структура
 * sockaddr_ll (см. <linux/if_packet.h>)
 */
    struct sockaddr_ll s_ll;
 
/*
 * Cоздаем пакетный сокет. Т.к. MAC-адреса мы тоже собираемся обрабатывать,
 * параметр type системного вызова socket принимает значение SOCK_RAW
 */
    sd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if(sd < 0) return -1;
 
    memset((void *)&s_ll, 0, sizeof(struct sockaddr_ll));
 
/*
 * Заполним поля адресной структуры s_ll
 */
    s_ll.sll_family = PF_PACKET; // тип сокета
    s_ll.sll_protocol = htons(ETH_P_ALL); // тип принимаемого протокола
    s_ll.sll_ifindex = index; // индекс сетевого интерфейса
 
/*
 * Привязываем сокет к сетевому интерфейсу. В принципе, делать это не
 * обязательно, если на хосте активен только один сетевой интерфейс.
 * При наличии двух и более сетевых плат пакеты будут приниматься сразу со всех
 * активных интерфейсов, и если нас интересуют пакеты только из одного сегмента
 * сети, целесообразно выполнить привязку сокета к нужному интерфейсу
 */
    if(bind(sd, (struct sockaddr *)&s_ll, sizeof(struct sockaddr_ll)) < 0) {
	close(sd);
	return -1;
    }
 
    return sd;
}

Цикл приема сетевых пакетов и отображение результатов будет выполняться в главной функции main().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*
 * Листинг 4. Главная функция (файл analizator.c)
 */
 
#include <stdio.h>
#include <signal.h>
#include <sys/socket.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include "analizator.h"
 
/*
 * В буфере buff будут сохранятся принятые сетевые пакеты.
 * Значение ETH_FRAME_LEN равно максимальной длине кадра Ethernet (1514)
 * и определено в <linux/if_ether.h>
 */
 __u8 buff[ETH_FRAME_LEN];
 
/*
 * Функция, которая заменит стандартный обработчик сигнала SIGINT.
 * Задача этой функции - по приходу сигнала SIGINT вывести интерфейс из
 * состояния "Promiscuous mode" в обычный режим
 */
void mode_off()
{
    if(getifconf("eth0", &ifp, PROMISC_MODE_OFF) < 0) {
	perror("getifconf");
	exit(-1);
    }
 
    return;
}
 
/*
 * Главная функция
 */
int main()
{
    __u32 num = 0;
    int eth0_if, rec = 0, ihl = 0;
    struct iphdr ip; // структура для хранения IP заголовка пакета
    struct tcphdr tcp; // TCP заголовок
    struct ethhdr eth; // заголовок Ethernet-кадра
    static struct sigaction act;
 
/*
 * Получаем параметры сетевого интерфейса eth0 и переводим его
 * в неразборчивый режим
 */
    if(getifconf("eth0", &ifp, PROMISC_MODE_ON) < 0) {
	perror("getifconf");
	return -1;
    }
 
/*
 * Отобразим полученные параметры сетевого интерфейса
 */
    printf("IP адрес - %s\n",inet_ntoa(ifp.ip));
    printf("Маска подсети - %s\n",inet_ntoa(ifp.mask));
    printf("MTU - %d\n", ifp.mtu);
    printf("Индекс - %d\n", ifp.index);
 
/*
 * Получим дескриптор пакетного сокета
 */
    if((eth0_if = getsock_recv(ifp.index)) < 0) {
	perror("getsock_recv");
	return -1;
    }
 
/*
 * Определим новый обработчик сигнала SIGINT - функцию mode_off
 */
    act.sa_handler = mode_off;
    sigfillset(&(act.sa_mask));
    sigaction(SIGINT, &act, NULL);
 
/*
 * Запускаем бесконечный цикл приема пакетов
 */
    for(;;) {
 
	memset(buff, 0, ETH_FRAME_LEN);
 
	rec = recvfrom(eth0_if, (char *)buff, ifp.mtu + 18, 0, NULL, NULL);
	if(rec < 0 || rec > ETH_FRAME_LEN) {
	    perror("recvfrom");
	    return -1;
	}
 
	memcpy((void *)&eth, buff, ETH_HLEN);
	memcpy((void *)&ip, buff + ETH_HLEN, sizeof(struct iphdr));
	if((ip.version) != 4) continue;
	memcpy((void *)&tcp, buff + ETH_HLEN + ip.ihl * 4, sizeof(struct tcphdr));
 
/*
 * MAC-адреса отправителя и получателя
 */
	printf("\n%u\n", num++);
	printf("%.2x:%.2x:%.2x:%.2x:%.2x:%.2x\t->\t",
	eth.h_source[0],eth.h_source[1],eth.h_source[2],
	eth.h_source[3],eth.h_source[4],eth.h_source[5]);
 
	printf("%.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n",
	eth.h_dest[0],eth.h_dest[1],eth.h_dest[2],
	eth.h_dest[3],eth.h_dest[4],eth.h_dest[5]);
 
	printf("Длина заголовка - %d, ", (ip.ihl * 4));
	printf("длина пакета - %d\n", ntohs(ip.tot_len));
 
/*
 * Если транспортный протокол - TCP, отобразим IP адреса и порты
 * получателя и отправителя
 */
	if(ip.protocol == IPPROTO_TCP) {
	    printf("%s (%d)\t->\t",inet_ntoa(ip.saddr), ntohs(tcp.source));
	    printf("%s (%d)\n",inet_ntoa(ip.daddr), ntohs(tcp.dest));
	    printf("TCP пакет\n");
	}
    }
 
    return 0;
}

Для сборки исполняемого файла создадим Makefile следующего содержания:

.PHONY = clean
 
analizator: analizator.o getifconf.o getsock_recv.o
 
gcc $^ -g -o $@
 
%.o: %.c
 
gcc -c $^
 
clean:
 
rm -f *.o
 
rm -f ./analizator

После запуска на выполнение программа переводит интерфейс в неразборчивый режим и запускает бесконечный цикл прослушивания сетевого трафика и отображения результатов. Факт включения неразборчивого режима интерфейса будет зафиксирован в файле /var/log/messages:

eth0: Promiscuous mode enabled.
device eth0 entered promiscuous mode

Бесконечный цикл работы анализатора будет прерван после нажатия комбинации клавиш Ctrl-C. Программа получит сигнал SIGINT, и обработчик этого сигнала функция mode_off() снимет флаг неразборчивого режима с интерфейса. При этом в файле /var/log/messages появится запись:

device eth0 left promiscuous mode

В следующей статье мы рассмотрим пакетную фильтрацию, а точнее способ ее программной реализации. Успехов Всем!..


Один комментарий на «“Анализатор сетевого трафика и пакетный фильтр своими руками (Часть 1)”»

  1. Когда-то пытался писать подобное, но под Windows :) но все было куда более замутно с перехватом ядерных апи NDIS. Сейчас пользуюсь в основном линукс, интересно будет почитать продолжение статьи. спасибо! )

Добавить комментарий

Яндекс.Метрика