Содержание первой части:
Содержание второй части:
2.1 — Введение во вторую часть. Смотрим на сеть и протоколы. Wireshark.
2.2 — Таблицы Firewall. Transport Layer. Структуры TCP, UDP. Расширяем Firewall.
2.3 — Расширяем функциональность. Обрабатываем данные в user space. libnetfilter_queue.
2.4 — Бонус. Изучаем реальную Buffer Overflow атаку и предотвращаем с помощью нашего Firewall'а.
Мы закончили прошлую часть на
В этой части мы посмотрим, как легко послать пакеты из kernel space в user space и обратно. Для примера мы возьмем именно HTTP соединение и добавим блокировку отдельных сайтов. В бонусной части мы рассмотрим существующую уязвимость и, в качестве примера, как с ней бороться.
К нашему везенью, есть уже готовое решение – низкоуровневая библиотека, написанная на C, для простого взаимодействия с netfilter из userspace – libnetfilter_queue. Как можно догадаться по названию, когда мы получаем очередной пакет и вызывается установленная нами hook_function, есть возможность принять пакет (return NF_ACCEPT), выкинуть (return NF_DROP), а есть возможность поместить его в специальную очередь (queue), просто выполнив:
HTTP_QUEUE_NUMBER – это определенный мною номер очереди, куда послать пакет (может быть несколько очередей, что удобно для простого разделения пакетов по типам)
NF_QUEUE_NR – это макрос, который необходимо использовать (он делает побитовые сдвиги, чтобы вернуть нужное число).
По умолчанию можно также написать:
return NF_QUEUE
и тогда все пакеты будут посланы в очередь с номером ноль. То есть единственное, что необходимо нам поменять в коде, чтобы послать все пакеты связанные с http протоколом, это добавить эту строчку:
Теперь необходимо эти пакеты получить в user space. И вот тут нам поможет libnetfilter_queue.
Для того, чтобы им воспользоваться, необходимо скачать и установить нужные библиотеки. Подробно про установку – тут, все необходимое для установки.
Внутри также есть простой пример, а возможные проблемы при установке решаются при помощи поисковика (у меня нужно было установить еще один пакет). Давайте посмотрим на самые важные моменты из примера. В функции main() (напоминаю, что мы сейчас в user space!) происходят разные инициализации
Тут важно указать правильный номер QUEUE. В примере это ноль, но мы его меняем на наш qh = nfq_create_queue(h, HTTP_QUEUE_NUMBER, &cb, NULL);
Если все инициализации прошли успешно, то мы попадем в бесконечный цикл, где будем «ждать» поступления новых пакетов из kernel. Когда такой пакет поступает, вызывается функция cb, указатель на которую мы также передали в nfw_create_queue:
Функция же в свою очередь вызывает print_pkt, которой передают указатель на структуру, содержащую пакет, который к нам пришел. Каждый пакет, получает в соответствии с порядком поступления – номер id, который возвращает print_pkt (на которую мы посмотрим вот вот), и в конце вызывается функция вердикта данному пакету – в данном случае NF_ACCEPT – принять.
Теперь к «сердцу» примера – print_pkt:
В оригинальном примере есть множество функций для доступа к различным данным через nfq_data *tb, мы же рассмотрим доступ к данным на тех уровнях, которые нам хорошо знакомы из предыдущих частей – Application, Transport, Network
Вот полный код функции:
Ну, тут уже не должно быть вопросов.
Компилируем, проверяем:
Так выглядят все части, http – сервер который будет слушать http трафик, interface – управление устройством, module – наш firewall. Компилируем http сервер, запускаем firewall, запускаем http сервер:

Отдельно заходим с host2 и в этот раз для интереса подключаемся к host1 на порт 80 и посылаем команду GET, чтобы получить главную страницу:

Смотрим результаты, что пишет http сервер. Видно посланную нами команду GET, а после нее и всю страницу, которую нам послал host1 в ответ на просьбу.

Теперь уже совершенно очевидно, как, например, заблокировать доступ к отдельным сайтам.
Мы начали первую часть с того, что построили простую сеть из трех компьютеров с операционной системой linux. Дальше мы написали простое устройство, которое умело считать количество пакетов, которые проходят через сетевые карты компьютера, а также разрежать и запрещать их дальнейшую передачу. Все это происходило на уровне ядра операционной системы. Для того, чтобы нам было удобно управлять устройством и получать от него информацию, мы добавили user interface, которые умел посылать команды нашему устройству и принимать от него данные. Так мы получали статистику в удобной нам форме.
Вторую часть мы начали с изучения сетей и протоколов, исследовав детали некоторых из них. И дальше применили наши знания, существенно расширив возможности firewall и получив доступ ко всей необходимой информации, которая содержится в пакете на интересующих нас OSI уровнях. Все это происходит в kernel space.
В этой части мы послали некоторые из пакетов в user space, тем самым намного расширив удобство написания кода и его функциональность, открыв для себя возможность использования множества доступных готовых С\С++ библиотек. Надо отметить, что, сделав это, мы «заплатили» в производительности, но при этом не стали нагружать ядро операционной системы и переложили анализ трафика на пользовательскую аппликацию. Теперь работа firewall стала намного медленней, но в нашем случае это не принципиально.
Современные же firewall – это очень сложный hardware-software комплекс, где все заточено под скорость и качество, и который устанавливается на главный «вход» в большие сети и обрабатывает гигабайты в секунду. Можно представить, что даже незначительное ухудшение производительности (например, обработки пакета) в гигабайтах будет обходиться очень дорого. Поэтому в них постоянно ищут слабые звенья и работают над улучшением их производительности. Кроме того, в задачу современных firewall входит не только проверка правил, но и намного более сложные задачи: отражение разных типов DOS атак, вирусов, предотвращение утечки информации, попыток взлома компьютеров, доступа к подозрительным сайтам и скачивания подозрительного контента. Простейший пример в качестве бонусной статьи мы рассмотрим в последней части – завершающей.
Создание лаборатории, архитектура Netfilter, char device, sysfs
1.1 — Создание виртуальной лаборатории (чтобы нам было, где работать, я покажу как создать виртуальную сеть на вашем компьютере. Сеть будет состоять из 3х машин Linux ubuntu).
1.2 – Написание простого модуля в Linux. Введение в Netfilter и перехват трафика с его помощью. Объединяем все вместе, тестируем.
1.3 – Написание простого char device. Добавление виртуальной файловой системы — sysfs. Написание user interface. Объединяем все вместе, тестируем.
1.2 – Написание простого модуля в Linux. Введение в Netfilter и перехват трафика с его помощью. Объединяем все вместе, тестируем.
1.3 – Написание простого char device. Добавление виртуальной файловой системы — sysfs. Написание user interface. Объединяем все вместе, тестируем.
Содержание второй части:
2.1 — Введение во вторую часть. Смотрим на сеть и протоколы. Wireshark.
2.2 — Таблицы Firewall. Transport Layer. Структуры TCP, UDP. Расширяем Firewall.
2.3 — Расширяем функциональность. Обрабатываем данные в user space. libnetfilter_queue.
2.4 — Бонус. Изучаем реальную Buffer Overflow атаку и предотвращаем с помощью нашего Firewall'а.
Часть 2.3 – Введение
Мы закончили прошлую часть на
if(dest_port == HTTP_PORT || src_port == HTTP_PORT) { printk("HTTP packet\n"); }
В этой части мы посмотрим, как легко послать пакеты из kernel space в user space и обратно. Для примера мы возьмем именно HTTP соединение и добавим блокировку отдельных сайтов. В бонусной части мы рассмотрим существующую уязвимость и, в качестве примера, как с ней бороться.
libnetfilter_queue.
К нашему везенью, есть уже готовое решение – низкоуровневая библиотека, написанная на C, для простого взаимодействия с netfilter из userspace – libnetfilter_queue. Как можно догадаться по названию, когда мы получаем очередной пакет и вызывается установленная нами hook_function, есть возможность принять пакет (return NF_ACCEPT), выкинуть (return NF_DROP), а есть возможность поместить его в специальную очередь (queue), просто выполнив:
#define HTTP_QUEUE_NUMBER 1 return NF_QUEUE_NR(HTTP_QUEUE_NUMBER);
HTTP_QUEUE_NUMBER – это определенный мною номер очереди, куда послать пакет (может быть несколько очередей, что удобно для простого разделения пакетов по типам)
NF_QUEUE_NR – это макрос, который необходимо использовать (он делает побитовые сдвиги, чтобы вернуть нужное число).
По умолчанию можно также написать:
return NF_QUEUE
и тогда все пакеты будут посланы в очередь с номером ноль. То есть единственное, что необходимо нам поменять в коде, чтобы послать все пакеты связанные с http протоколом, это добавить эту строчку:
if(dest_port == HTTP_PORT || src_port == HTTP_PORT) { printk("HTTP packet\n"); return NF_QUEUE_NR(HTTP_QUEUE_NUMBER); }
Теперь необходимо эти пакеты получить в user space. И вот тут нам поможет libnetfilter_queue.
Для того, чтобы им воспользоваться, необходимо скачать и установить нужные библиотеки. Подробно про установку – тут, все необходимое для установки.
Внутри также есть простой пример, а возможные проблемы при установке решаются при помощи поисковика (у меня нужно было установить еще один пакет). Давайте посмотрим на самые важные моменты из примера. В функции main() (напоминаю, что мы сейчас в user space!) происходят разные инициализации
int main(int argc, char **argv) { … printf("opening library handle\n"); h = nfq_open(); if (!h) { exit(1); } … qh = nfq_create_queue(h, 0, &cb, NULL); if (!qh) { exit(1); } … for (;;) { if ((rv = recv(fd, buf, sizeof(buf), 0)) >= 0) { printf("pkt received\n"); nfq_handle_packet(h, buf, rv); continue; } …
Тут важно указать правильный номер QUEUE. В примере это ноль, но мы его меняем на наш qh = nfq_create_queue(h, HTTP_QUEUE_NUMBER, &cb, NULL);
Если все инициализации прошли успешно, то мы попадем в бесконечный цикл, где будем «ждать» поступления новых пакетов из kernel. Когда такой пакет поступает, вызывается функция cb, указатель на которую мы также передали в nfw_create_queue:
static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) { u_int32_t id = print_pkt(nfa); printf("entering callback\n"); return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); }
Функция же в свою очередь вызывает print_pkt, которой передают указатель на структуру, содержащую пакет, который к нам пришел. Каждый пакет, получает в соответствии с порядком поступления – номер id, который возвращает print_pkt (на которую мы посмотрим вот вот), и в конце вызывается функция вердикта данному пакету – в данном случае NF_ACCEPT – принять.
Теперь к «сердцу» примера – print_pkt:
static u_int32_t print_pkt(struct nfq_data *tb)
В оригинальном примере есть множество функций для доступа к различным данным через nfq_data *tb, мы же рассмотрим доступ к данным на тех уровнях, которые нам хорошо знакомы из предыдущих частей – Application, Transport, Network
Вот полный код функции:
static u_int32_t print_pkt(struct nfq_data *tb) { int i = 0; int packet_len = 0; unsigned char *data = NULL; int ip_src_array[4] = {0}; int ip_dst_array[4] = {0}; packet_len = nfq_get_payload(tb, &data); ip_dst_array[3] = data[16]; ip_dst_array[2] = data[17]; ip_dst_array[1] = data[18]; ip_dst_array[0] = data[19]; ip_src_array[3] = data[12]; ip_src_array[2] = data[13]; ip_src_array[1] = data[14]; ip_src_array[0] = data[15]; char ip_dst_str[20] = {0}; char ip_src_str[20] = {0}; ip_hl_to_str(ip_dst_array, ip_dst_str); ip_hl_to_str(ip_src_array, ip_src_str); printf("src_ip = %s, dst_ip = %s", ip_src_str, ip_dst_str); printf("\n"); if( packet_len >= 0x34 ) { for(i = 0x34; i < packet_len; ++i){ if(data[i] >= ' ' && data[i] <= '}') printf("%c", (int)data[i]); } } printf("End of packet checking\n"); return NF_ACCEPT; }
Ну, тут уже не должно быть вопросов.
Компилируем, проверяем:
Так выглядят все части, http – сервер который будет слушать http трафик, interface – управление устройством, module – наш firewall. Компилируем http сервер, запускаем firewall, запускаем http сервер:

Отдельно заходим с host2 и в этот раз для интереса подключаемся к host1 на порт 80 и посылаем команду GET, чтобы получить главную страницу:

Смотрим результаты, что пишет http сервер. Видно посланную нами команду GET, а после нее и всю страницу, которую нам послал host1 в ответ на просьбу.

Теперь уже совершенно очевидно, как, например, заблокировать доступ к отдельным сайтам.
Завершение первой и второй части
Мы начали первую часть с того, что построили простую сеть из трех компьютеров с операционной системой linux. Дальше мы написали простое устройство, которое умело считать количество пакетов, которые проходят через сетевые карты компьютера, а также разрежать и запрещать их дальнейшую передачу. Все это происходило на уровне ядра операционной системы. Для того, чтобы нам было удобно управлять устройством и получать от него информацию, мы добавили user interface, которые умел посылать команды нашему устройству и принимать от него данные. Так мы получали статистику в удобной нам форме.
Вторую часть мы начали с изучения сетей и протоколов, исследовав детали некоторых из них. И дальше применили наши знания, существенно расширив возможности firewall и получив доступ ко всей необходимой информации, которая содержится в пакете на интересующих нас OSI уровнях. Все это происходит в kernel space.
В этой части мы послали некоторые из пакетов в user space, тем самым намного расширив удобство написания кода и его функциональность, открыв для себя возможность использования множества доступных готовых С\С++ библиотек. Надо отметить, что, сделав это, мы «заплатили» в производительности, но при этом не стали нагружать ядро операционной системы и переложили анализ трафика на пользовательскую аппликацию. Теперь работа firewall стала намного медленней, но в нашем случае это не принципиально.
Современные же firewall – это очень сложный hardware-software комплекс, где все заточено под скорость и качество, и который устанавливается на главный «вход» в большие сети и обрабатывает гигабайты в секунду. Можно представить, что даже незначительное ухудшение производительности (например, обработки пакета) в гигабайтах будет обходиться очень дорого. Поэтому в них постоянно ищут слабые звенья и работают над улучшением их производительности. Кроме того, в задачу современных firewall входит не только проверка правил, но и намного более сложные задачи: отражение разных типов DOS атак, вирусов, предотвращение утечки информации, попыток взлома компьютеров, доступа к подозрительным сайтам и скачивания подозрительного контента. Простейший пример в качестве бонусной статьи мы рассмотрим в последней части – завершающей.
