Содержание первой части:
Содержание второй части:
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 атак, вирусов, предотвращение утечки информации, попыток взлома компьютеров, доступа к подозрительным сайтам и скачивания подозрительного контента. Простейший пример в качестве бонусной статьи мы рассмотрим в последней части – завершающей.