Анализатор сетевого трафика и пакетный фильтр своими руками (Часть 2)
  • Категории
  • Подписка
  • Разместить статью
09/04/10 8 6693 Сетевые анализаторы
-

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

burt_securos.org.ua

Как будет осуществляться фильтр пакетов в теории? После запуска анализатор будет фиксировать все пакеты, проходящие по сети, и выводить информацию про каждый принятый пакет. Допустим, если нам интересны пакеты, принадлежащие определенным хостам, значит необходимо анализировать адресную часть каждого принятого пакета. Это может делать сам анализатор при помощи оператора условия if. Но лучше возложить эту функцию на ядро ОС.

Отсеять лишний трафик можно, используя средства Linux Socket Filter (LSF). LSF позволяет создать и подключить непосредственно к сокету фильтр, который представляет собой последовательность инструкций следующего формата (см. <linux/filter.h>):

1
2
3
4
5
6
7
struct sock_filter	/* Filter block */
{
    __u16	code;   /* Actual filter code */
    __u8	jt;	/* Jump true */
    __u8	jf;	/* Jump false */
    __u32	k;      /* Generic multiuse field */
};

LSF является наследником Berkeley Packet Filter (BPF) — языка, разработанного Стивом Маккеном (Steve McCanne) и Ван Якобсоном (Van Jacobson). Коды инструкций LSF идентичны BPF, подробную информацию обо всех инструкциях можно получить, обратившись к BSD bfp.4 manpage, либо на opennet.ru.

А теперь перейдем к практике…

Модифицируем код функции getsock_recv(), подключив к создаваемому сокету фильтр. Подключаемый фильтр описывает следующая структура (см. <linux/filter.h>):

1
2
3
4
5
struct sock_fprog
{
    unsigned short len;	/* Number of filter blocks */
    struct sock_filter *filter;
};

Здесь struct sock_filter *filter — это программа фильтрации, представляющая собой массив структур struct sock_filter. Перед тем, как составлять программу фильтрации, определим условия фильтрации: принимать пакеты IP протокола, адрес отправителя — 192.168.1.2, транспортный протокол — TCP, порт — telnet. При составлении программы фильтрации воспользуемся следующими макроопределениями (см. <linux/filter.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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
    #define BPF_STMT(code, k) { (unsigned short)(code), 0, 0, k }
    #define BPF_JUMP(code, k, jt, jf) { (unsigned short)(code), jt, jf, k }
 
/*
 * Листинг 5. Программа фильтрации
 */
 
    struct sock_fprog Filter;
    struct sock_filter BPF_code[] = {
 
/*
 * В принятом Ethrnet-кадре по смещению, равному 12 байт (6 байт MAC-адреса
 * отправителя + 6 байт MAC-адреса получателя) находится 2-х байтовый
 * идентификатор протокола сетевого уровня. Эти 2 байта мы загружаем в
 * аккумулятор
 */
	BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 12),
 
/*
 * Проверяем соответствие значения, загруженного в аккумулятор, идентификатору
 * IP протокола (ETH_P_IP). При выполнении условия равенства этих значений
 * переходим к следующей инструкции (jt = 0). В противном случае смещаемся
 * на 8 инструкций вниз (jf = 8) и выходим из программы фильтрации с возвратом
 * нулевого значения
 */
	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_IMM, ETH_P_IP, 0, 8),
 
/*
 * Загружаем в аккумулятор 4-х байтовое значение, находящееся по смещению 26
 * в принятом пакете. Это значение соответствует IP адресу источника
 */
	BPF_STMT(BPF_LD+BPF_W+BPF_ABS, 26),
 
/*
 * Проверяем соответствие значения, загруженного в аккумулятор, IP адресу
 * 192.168.1.2, в шестнадцатеричном представлении - 0xC0A80102. В сетевом
 * формате этот адрес выглядит как 0x0201A8C0. Это связано с порядком передачи
 * данных в сети - передача начинается с байта младшего разряда. Если адреса не
 * совпали - выходим из программы фильтрации
 */
	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_IMM, 0xC0A80102, 0, 6),
 
/*
 * Загружаем в аккумулятор 1 байт, находящийся по смещению 23. Этот байт
 * содержит идентификатор протокола транспортного уровня
 */
	BPF_STMT(BPF_LD+BPF_B+BPF_ABS, 23),
	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_IMM, IPPROTO_TCP, 0, 4),
 
/*
 * Определяем длину IP заголовка
 */
	BPF_STMT(BPF_LDX+BPF_B+BPF_MSH, ETH_HLEN),
 
/*
 * Значение длины IP заголовка загружено в индексный регистр. Порт источника
 * находится по смещению, определяемому суммой длин IP и Ethernet заголовков
 */
	BPF_STMT(BPF_LD+BPF_H+BPF_IND, ETH_HLEN),
 
/*
 * Проверяем значение порта источника
 */
	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_IMM, 0x17, 0, 1),
	BPF_STMT(BPF_RET+BPF_K,1500),
	BPF_STMT(BPF_RET+BPF_K,0),
    };

Программа фильтрации готова. Заполняем поля структуры struct sock_fprog Filter и подключаем фильтр к сокету:

1
2
3
4
    Filter.len = 11; // количество структур в массиве BPF_code
    Filter.filter = BPF_code;
 
    setsockopt(sd, SOL_SOCKET, SO_ATTACH_FILTER, &Filter, sizeof(struct sock_fprog));

В приведенном примере есть существенный недостаток — исходные данные для фильтрации (IP адрес и порт) вводятся непосредственно в текст программы. Модифицируем функцию getsock_recv() для возможности гибкой настройки на новые условия фильтрации. Параметры фильтрации — IP адрес и порт — будут передаваться из главной функции. Прототип функции getsock_recv() принимает следующий вид:

1
 int getsock_recv (int, __u8 *, int)

Первый параметр не изменился — это индекс сетевого интерфейса. Второй параметр – указатель на строку, содержащую IP адрес, третий параметр — значение порта. В массиве BPF_code третий и восьмой элементы перепишем в следующем виде:

1
2
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_IMM, 0, 0, 6), // 3-й элемент
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_IMM, 0, 0, 1), // 8-й

Здесь константы, содержащие значение IP адреса и порта, обнулены. Заполним их значениями, переданными из главной функции:

1
2
    BPF_code[3].k = __swab32(inet_addr(ip_addr));
    BPF_code[8].k = port;

Строковое значение IP адреса мы преобразуем в сетевой формат, а затем при помощи макроопределения  __swab32 (см. файл <linux/byteorder/swab.h>) меняем местами байты — нулевой с третьим, второй с первым.

В итоге функция getsock_recv() будет выглядеть следующим образом:

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
/*
 * Листинг 6. Функция создания пакетного сокета и подключения
 * пакетного фильтра (файл getsock_recv.c)
 */
 
#include <sys/socket.h>
#include <linux/types.h>
#include <linux/in.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/filter.h>
#include <linux/byteorder/swab.h>
 
int getsock_recv (int index, __u8 *ip_addr, int port)
{
    int sd;
 
    struct sockaddr_ll s_ll;
    struct sock_fprog Filter;
    struct sock_filter BPF_code[] = {
	BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 12),
	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_IMM, ETH_P_IP, 0, 8),
	BPF_STMT(BPF_LD+BPF_W+BPF_ABS, 26),
	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_IMM, 0, 0, 6),
	BPF_STMT(BPF_LD+BPF_B+BPF_ABS, 23),
	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_IMM, IPPROTO_TCP, 0, 4),
	BPF_STMT(BPF_LDX+BPF_B+BPF_MSH, ETH_HLEN),
	BPF_STMT(BPF_LD+BPF_H+BPF_IND, ETH_HLEN),
	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_IMM, 0, 0, 1),
	BPF_STMT(BPF_RET+BPF_K,1500),
	BPF_STMT(BPF_RET+BPF_K,0),
    };
 
    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.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;
    }
 
    BPF_code[3].k = __swab32(inet_addr(ip_addr));
    BPF_code[8].k = port;
 
    Filter.len = 11;
    Filter.filter = BPF_code;
 
    if(setsockopt(sd, SOL_SOCKET, SO_ATTACH_FILTER, &Filter, sizeof(struct sock_fprog))<0) {
	perror("SO_ATTACH_FILTER");
	close(sd);
	return -1;
    }
 
    return sd;
}

Осталось внести именения в главную функцию (полный текст не приводится):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int main(int argc, char **argv)
{
 
/* Определения переменных */
    ....
 
    if(argc != 3) {
	perror("argc");
	return -1;
    }
 
/* Определение параметров интерфейса и отображение результатов */
    ....
 
/* Получение дескриптора пакетного сокета */
 
    if((eth0_if = getsock_recv(ifp.index, argv[1], atoi(argv[2]))) < 0) {
	perror("getsock_recv");
	return -1;
    }
 
/* Цикл приема пакетов и отображения результатов */
    ....
}

При запуске анализатора в командной строке задаются параметры — IP адрес отправителя и порт. Составление программы фильтрации можно существенно упростить, если воспользоваться услугами tcpdump. Например, составим программу фильтрации, принимающую пакеты, источником которых является хост 192.168.1.1, транспортный протокол — TCP, порт — 23. Вводим команду:

  tcpdump -dd src 192.168.1.1 and tcp src port 23

Получаем в ответ:

    { 0x28, 0, 0, 0x0000000c },
    { 0x15, 0, 10, 0x00000800 },
    { 0x20, 0, 0, 0x0000001a },
    { 0x15, 0, 8, 0xc0a80101 },
    { 0x30, 0, 0, 0x00000017 },
    { 0x15, 0, 6, 0x00000006 },
    { 0x28, 0, 0, 0x00000014 },
    { 0x45, 4, 0, 0x00001fff },
    { 0xb1, 0, 0, 0x0000000e },
    { 0x48, 0, 0, 0x0000000e },
    { 0x15, 0, 1, 0x00000017 },
    { 0x6, 0, 0, 0x00000060 },
    { 0x6, 0, 0, 0x00000000 },

Это и есть искомая программа фильтрации.

Честно говоря, в начале написания поста я думал, что это не так сложно, как оказалось потом. Некоторые моменты я открыл для себя впервые.

Как видите написать такую утилиту вполне реально, но со своей колокольни скажу, очень тяжело “с кандачка” это сделать.

Спасибо ребятам, которые не пожалели времени и сил. Авось кому пригодится!


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

  1. а что, интересная идея, склепать самому такой анализатор, — хоть за авторское право никто не придерется))

  2. Mic, придираются достаточно редко.

  3. попробовал под freebsd собрать, не получилось(.
    под какой сборкой linux компилировали если не секрет?

  4. bity:

    Довольно таки неплохо отсеять лишний трафик с помощью Linux Socket Filter.

  5. Круто но обычно пользователю как я такое не осилить. Проще скачать готовый фаервол

  6. Что то вторую часть не осилил. По сути оно мне и не нужно, но интересно до чертиков. Кстате у мну тоже под freebsd не собралось

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

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