Как стать автором
Обновить

Перехват вызовов функций в Linux или простейший файрвол своими руками

Настройка Linux
Из песочницы

Вступление


Нетерпеливым эти лирические отступления можно не читать.

Некоторое время назад меня посетила мысль: «Как сделать так, чтобы закрыть доступ к интернету (или к какому-то конкретному хосту) какой-нибудь одной программе в Linux?». Эта мысль меня посетила и полетела дальше по своим делам. И вот сегодня я получил в свой RSS-ридер один вопрос на askdev.ru. Ба! Да это как раз то, о чем я думал! Надо помочь человеку, заодно и самому разобраться в вопросе.

Полез я в гугл смотреть, нету ли каких-нибудь намеков на решение. Оттуда я узнал, что штатным iptables это с некоторых пор сделать стало невозможно, а народ рекомендует посмотреть в сторону AppArmor. «Горя огромным желанием изучить AppArmor», я стал искать дальше и почти случайно наткнулся на сообщение на ЛОРе, в котором описывался довольно интересный метод.

Метод


Заключался он в том, чтобы «подменить» функцию соединения на свою, которая решает, разрешить ли соединение и «пропустить» запрос к настоящей функции или нет — вернуть ошибку. В сообщении многоуважаемого Chaoser'а использовалась низкоуровневая блокировка сокетов, которая не давала возможности принимать решение в зависимости от адреса сервера и/или порта. Это меня не устраивало, мне нужно было запретить доступ только для одного порта — 80-го. Запустив telnet, под strace'ом, я почти сразу увидел подходящую жертву — фунцию connect. strace ее описал так:

connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("87.250.251.3")}, 16) = 0

В этом описании отчетливо видно, что есть все необходимые мне составляющие: и IP-адрес, и порт, и тип подключения (AF_INET).

Что ж, приступим.

Решение


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

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

  1. #define _GNU_SOURCE
  2. #include <dlfcn.h>
  3. #include <stdio.h>
  4. #include <sys/types.h>
  5. #include <sys/socket.h>
  6. #include <netinet/in.h>
  7. #include <arpa/inet.h>
  8. #include <errno.h>
  9. static int (*real_connect)(int sockfd, const struct sockaddr *addr,
  10. socklen_t addrlen) = 0;
  11. int connect(int sockfd, const struct sockaddr *addr,
  12. socklen_t addrlen)
  13. {
  14. printf("NF_DEBUG: -----------------------------------------------\n");
  15. int sa_family = addr->sa_family;
  16. printf("NF_DEBUG: Address family: %d (AF_INET = %d)\n", sa_family, AF_INET);
  17. if (sa_family == AF_INET)
  18. {
  19. struct sockaddr_in *addr_in = (struct sockaddr_in*)(addr);
  20. struct in_addr sin_addr = addr_in->sin_addr;
  21. uint16_t sin_port = addr_in->sin_port;
  22. uint16_t sin_port_h = ntohs(sin_port);
  23. printf("NF_DEBUG: IP: %s\n", inet_ntoa(sin_addr));
  24. printf("NF_DEBUG: Port: %d\n", sin_port_h);
  25. if (sin_port_h == 80)
  26. {
  27. printf("NF_DEBUG: Rejected!\n");
  28. printf("NF_DEBUG: -----------------------------------------------\n");
  29. errno = ENETUNREACH;
  30. return -1;
  31. }
  32. }
  33. if(!real_connect)
  34. real_connect = dlsym(RTLD_NEXT, "connect");
  35. printf("NF_DEBUG: Accepted\n");
  36. printf("NF_DEBUG: -----------------------------------------------\n");
  37. return real_connect(sockfd, addr, addrlen);
  38. }
* This source code was highlighted with Source Code Highlighter.


Разбор кода


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

Строки 1-8 — директивы препроцессора, ничего интересного в них нет. Разве что директива #define _GNU_SOURCE, которая подключает расширения GNU, необходимые для функции dlsym.

В строках 10-11 мы объявляем указатель на «настоящую» функцию connect. У нас она будет называться real_connect. Описание функции взято из man connect.

Со строки 13 начинается уже новая функция connect, которую будут вызывать приложения и которая будет принимать решение, пропустить это приложение или нет. Ее описание полностью соответствует оригинальной connect и взято из того же мануала.

В 17-й строке из структуры, которая содержит все необходимые нам данные (и описание которой я взял здесь) получаем тип адреса.

Если тип адреса AF_INET (строка 20), то есть приложение «просится наружу» по протоколу IPv4, то мы применяем фильтрацию (строки 22-37), иначе — сразу пропускаем это соединение к «настоящей» функции connect.

Для того, чтобы отфильтровать подключения, необходимо преобразовать структуру addr к типу sockaddr_in, который дает возможность доступа к необходимым полям. Это происходит в строке 22.

Далее в строках 24-25 получаем значения адреса и порта, соответственно. В строке 26 мы меняем порядок следования байт в номере порта, тем самым получая обычный формат номера порта (80, 110, 25) из формата, который используется при соединении.

В строке 28 полученный адрес приводим к текстовому виду и выводим его, в 29-й строке выводим номер порта.

Далее в 31-й строке проверяем, соответствует ли номер порта тому, который мы хотим заблокировать, если да, то сообщаем об этом (строки 33-34), устанавливаем номер ошибки (строка 35; в данном случае будет ошибка «Сеть недоступна») и возвращаем ошибку (строка 36).

Если же все в порядке, то есть приложение обратилось, используя либо разрешенный порт, либо разрешенный тип адреса, то получаем адрес (точку входа) «настоящей» функции connect (строка 42), если до того мы его не получили (строка 41; не забываем, что real_connect у нас переменная глобальная), пишем, что все хорошо (строки 44-45), и передаем управление «настоящей» функции connect.

При получении адреса «настоящей» функции connect, мы используем параметр RTLD_NEXT для того, чтобы получить этот адрес из следующей загруженной библиотеки, а не из первой (нашей), иначе получим бесконечную рекурсию.

Использование


Скомпилировать библиотеку:

gcc -fPIC -shared -Wl,-soname,nonet.so -o nonet.so nonet.c

и запустить нужное приложение следующим образом:

LD_PRELOAD=/<полный путь к полученной библиотеке>/nonet.so <приложение>

Например, если библиотека лежит в /tmp, то следующей строкой можно запустить firefox без доступа к интернету (естественно, только через 80-й порт):

LD_PRELOAD=/tmp/nonet.so firefox

Заключение


Данный метод ни в коем случае не претендует на универсальность и всеприменимость, равно как и статья на Нобелевскую премию. Здесь я всего лишь осветил две темы, которые интересны мне, и, надеюсь, будут интересны еще кому-нибудь: ограничение доступа к сети опредеделенным программам и перехват вызовов функций в Linux.

Спасибо за внимание!

P.S. Чтобы меня не считали бесчестным человеком, укравшим код у digital'а c askdev.ru, думаю следует упомянуть, что digital и я — одно лицо.

UPD Большое спасибо за инвайт хабрачеловеку peter23. Перенес пост в блог «Linux для всех».
Теги:linuxfirewalllibrarysecurityбиблиотекадоступбезопасность
Хабы: Настройка Linux
Всего голосов 97: ↑88 и ↓9 +79
Просмотры3.7K

Похожие публикации

Linux администратор
до 120 000 ₽Строительный холдинг «ТИТАН-2»Санкт-ПетербургМожно удаленно
Системный администратор Linux
до 200 000 ₽СКИФ ЭППМосква
C++ Developer (Linux)
от 150 000 до 250 000 ₽MicroAviaСанкт-Петербург
C++ Embedded Developer (Linux)
от 150 000 до 250 000 ₽MicroAviaСанкт-Петербург
Системный администратор Linux / DevOps
от 170 000 ₽Golden GooseМоскваМожно удаленно

Лучшие публикации за сутки