Ключевые слова:iptables, linux, netfilter, module, firewall, (найти похожие документы)
From: ilya :: HackZona.ru
Date: Wed, 2 Aug 2007 18:21:07 +0000 (UTC)
Subject: Как работает firewall в Linux
Оригинал: http://www.hackzona.ru/hz.php?name=News&file=article&sid=3605
Каждый уважающий себя администратор Linux должен уметь не только
настраивать iptables, но и знать, как он работает. В этой статье речь
пойдет не о том, как правильно настраивать iptables или какой-нибудь
другой firewall, а о том, как работают firewall'ы в Linux.
В первую очередь, эта статья нацелена на читателей, которые занимаются
(начинают или только хотят начать) программированием модулей ядра
Linux (Linux Kernel Module - LKM), а также, надеюсь, поможет некоторым
администраторам более детально разобраться в работе iptables.
Оглавление :
1) Netfilter Hacking
2) Firewall своими руками
3) Пример norm.c
Netfilter Hacking
Что такое netfilter и какое отношение он имеет к Firewall'ам ?
В основном, Netfilter представляет собой набор функций (hook)
,расположенных в ядре, при помощи которых firewall'ы могут получать
доступ к пакетам и,основываясь на правилах, решать, как с ними
поступать дальше. Netfilter содержит 5 основных hook-функций, которые
описаны в linux/netfilter_ipv4.h.
Графически их можно изобразить так :
[INPUT]---> --->[ROUTE]--->[3]--->[4]--->[OUTPUT]]]
---------------------------|--------------------^
---------------------------|---------------------|
---------------------------|---------------- [ROUTE]
---------------------------v---------------------|
-------------------------[5]
---------------------------|--------------------- ^
---------------------------|----------------------|
---------------------------v----------------------|
---------------------- [INPUT*]-----------[OUTPUT*]
[1] NF_IP_PRE_ROUTING
[2] NF_IP_LOCAL_IN
[3] NF_IP_FORWARD
[4] NF_IP_POST_ROUTING
[5] NF_IP_LOCAL_OUT
NF_IP_PRE_ROUTING - функция срабатывает как только мы получаем пакет,
даже если он проходящий. Если мы хотим иметь доступ ко всем пакетам,
проходящим через наш интерфейс, то мы должны использовать эту функцию.
NF_IP_LOCAL_IN - срабатывает в случае, когда пакет адресован нам,
перед поступлением пакета в сетевой стек.
NF_IP_FORWARD - если пакет необходимо смаршрутизировать с одного
интерфейса на другой.
NF_IP_POST_ROUTING - для исходящих пакетов из нашего сетевого стека.
NF_IP_LOCAL_OUT - для всех исходящих пакетов.
Более подробную схему обработки пакетов вы можете посмотреть :
http://open-source.arkoon.net/kernel/kernel_net.png
После вызова функции и проведения нехитрых проверок над пакетом, нам
нужно вынести вердикт, что делать с этим пакетом дальше. В нашем
распоряжение 5 вариантов :
[1] NF_ACCEPT : пропускает пакет дальше
[2] NF_DROP : отбрасывает пакет
[3] NF_REPEAT : повторный вызов функции
[4] NF_STOLEN : забирает пакет (прекращается передвижение)
[5] NF_QUEUE : ставит пакет в очередь, как правило для передачи в
пользовательское пространство (мы ведь работаем в пространстве ядра)
Вот собственно и все, что нужно для нормальной работы любого
firewall'а в Linux. С одной стороны, набор функций, позволяющий
получать доступ к пакетам практически в любой точке сетевого стека, а
с другой, набор решений, как поступить с пакетом дальше.
/* Я думаю, что администраторам дальше можно не читать, там пойдет
объяснение структур, правильность их заполнение, а также примеры
использования. Вся теория работы firewall'а заканчивается
*/
Теперь попытаемся разобраться, как все это работает!
Первым делом нам нужно познакомиться со структурой nf_hook_ops, она и
будет нашим проводником в мир netfilter'a. Описание её можно найти в
/Linux/netfilter.h :
44 struct nf_hook_ops
45 {
46 struct list_head list;
47
48 /* User fills in from here down. */
49 nf_hookfn *hook;
50 int pf;
51 int hooknum;
52 /* Hooks are ordered in ascending priority. */
53 int priority;
54 };
Первое, что мы видим, это <<struct list_head list>> - это структура,
которая содержит список всех hook-функций, но нас она не сильно
интересует.
nf_hookfn *hook - указатель на нашу функцию, в которой мы и будем
проводить все наши проверки. Возвращаемое значение должно быть одно из
5-и поведений (NF_ACCEPT, NF_DROP, ...).
int pf - служит для определения протокола, с которым мы хотим работать
(PF_INET)
int hooknum - а вот и место нашего вызова. (например
NF_IP_PRE_ROUTING)
int priority - приоритет. В случае, если определено несколько функций
на один вызов, первым сработает тот, у кого выше приоритет. Мы будем
использовать - NF_IP_PRI_FIRST.
Не поверите, но это все!
Остается лишь маленькое дополнение.
После того, как мы объявим и заполним нашу структуру, её необходимо
зарегистрировать.
Для этого служат 2-е функции, которые объявлены все в том же
/Linux/netfilter.h :
89 /* Function to register/unregister hook points. */
90 int nf_register_hook(struct nf_hook_ops *reg);
91 void nf_unregister_hook(struct nf_hook_ops *reg);
nf_register_hook - для регистрации нашей hook-функции
nf_unregister_hook - для удаление нашей функции из цепочки.
Ничего особенного, просто банальное предупреждение. Обязательно
выгружайте ваши функции при выгрузке модуля из памяти при помощи
nf_unregister_hook.
Если этого не сделать, произойдет очень неприятная вещь.
Придет пакет, сработает наш вызов, ядро попытается обратиться к
странице памяти для вызова нашей функции для обработки, а там.... Эээ
в лучшем случае ничего, в худшем ..кто-то занял наше место и тогда
результат будет непредсказуем.
Firewall своими руками
Для примера напишем маленький firewall.
Который будет беспощадно уничтожать все входящие и исходящие пакеты.
bash$ > cat firewall.c
#define __KERNEL__
#define MODULE
#define LINUX
#include <module.h>
#include <kernel.h>
#include <netfilter.h>
#include <netfilter_ipv4.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ilya");
/* Объявление структур. Мы объявим 2-е структуры. */
/* 1-я для входящих пакетов */
/* 2-я для исходящих пакетов */
struct nf_hook_ops nf_incoming;
struct nf_hook_ops nf_outgoing;
/* наша функция обработки */
unsigned int main_hook (unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff*))
{
/* Для примера, мы будем отбрасывать все пакеты */
return NF_DROP;
}
int init_module ()
{
/* Заполнение структур */
/* Сначала, заполним структуру для входящих пакетов */
nf_incoming.hook = main_hook; /* указатель на нашу функцию */
nf_incoming.pf = PF_INET;
nf_incoming.hooknum = NF_IP_PRE_ROUTING;
nf_incoming.priority = NF_IP_PRI_FIRST;
/* Теперь для исходящих */
nf_outgoing.hook = main_hook;
nf_outgoing.pf = PF_INET;
nf_outgoing.hooknum = NF_IP_PRE_ROUTING;
nf_outgoing.priority = NF_IP_PRI_FIRST;
/* Вроде все, осталось только зарегистрировать наши функции */
nf_register_hook(&nf_incoming);
nf_register_hook(&nf_outgoing);
printk ("FireWall loaded обратный слеш n");
return 0;
}
void cleanup_module ()
{
/* Не забываем удалить наши вызовы J, а то конфуз может случится */
nf_unregister_hook(&nf_incoming);
nf_unregister_hook(&nf_outgoing);
printk ("FireWall unload обратный слэш n ");
}
Ну вот. Все очень просто!
Теперь компилируем наш модуль :
Gcc -c firewall.c -I- -I /usr/src/Linux/include
А еще лучше, написать makefile!
Но это уже на ваше усмотрение.
И запускаем : insmod firewall.o (иногда приходится запускать с ключем
-f : insmod -f firewall.o, а то ему версии не нравятся, но кому не
лень, можно в модуле прописать все данные о версии ядра)
Посмотрите /var/log/messages - увидите <<FireWall loaded>>
Значит наш модуль загрузился.
Теперь, если вы попробуйте подключится к кому-нибудь или, наоборот,
кто-то захочет к вам подключится, ничего не выйдет. Наш модуль не
пропустит ни одного пакета.
Что бы вернуть все на место, просто выгрузите модуль :
Rmmod firewall
Вот пример firewall'a в 60 строк, включая заголовки. Не сложно
правда..!!! :))
Теперь перейдем к более сложным вещам.
Но совсем на чуть-чуть :).
Пример norm.c
В этом примере мы будем проводить небольшой анализ захваченного нами
пакета.
Наша программа будет анализировать заголовки пакета и в случае
неудовлетворения правилам, будет удалять его или править. Правила я
брал из статьи <<Нормализация пакета>> (Спасибо автору, очень
познавательная).
Итак, небольшое введение в структуру sk_buff:
Sk_buff - это буфер для работы с пакетами. Как только приходит пакет
или появляется необходимость его отправить, создается sk_buff, куда и
помещается пакет, а также сопутствующая информация, откуда, куда, для
чего... На протяжение всего путешествия пакета в сетевом стеке
используется sk_buff. Как только пакет отправлен или данные переданы
пользователю, структура уничтожается, тем самым освобождая память.
Описание этой структуры можно найти в linux/skbuff.h
Она очень большая, я не буду выкладывать её сюда :)
Все что мы будем использовать из неё, это :
Protocol - чтобы знать, с каким протоколом серевого уровня мы имеем
дело
Data - место, где лежит пакет.
Более подробно о работе sk_buff можно почитать в Интернете, информации
о нем море, а что касается практической части, советую почитать статью
из phrack No. 55 "Building Into The Linux Network Layer" http://www.phrack.org/show.php?p=55&a=12
Ну вроде все, с теорией маленько разобрались.
Теперь определимся, что и как мы будем делать. Так как это лишь пример
использования, я не буду заострять внимание над нормализацией
конкретного протокола, просто пробежимся немного по протоколам и все :
1) IP - проверка протокола следующего уровня (пропускать только TCP,
UDP, ICMP)
2) IP - если поле TTL cat norm.c
#define __KERNEL__
#define MODULE
#define LINUX
#include <module.h>* Эйй, мы ведь пишем модуль к ядру */
#include <kernel.h>
#include <netfilter.h>
#include <netfilter_ipv4.h>
/*подключаем заголовки для работы с сетевыми протоколами */
/* и собственно говоря sk_buff */
#include <skbuff.h>
#include <inet.h>
#include <ip.h>
#include <ip.h>
#include <tcp.h>
#include <icmp.h>
#include <uaccess.h>
/* определяем дериктивы для преобразования байт из сетевого в
нормальный :) */
/* и наоборот */
#define ntohs(x) __be16_to_cpu(x)
#define htons(x) __cpu_to_be16(x)
/* Увековечим свое имя Aha-ha... */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("llya");
struct nf_hook_ops nf_incoming;
struct sk_buff *skbf;
struct tcphdr *th;
struct icmphdr *icmph;
struct iphdr *iph;
unsigned int main_hook (unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff*))
{
skbf=*skb;
/* Работаем только с IP */
if (skbf->protocol != htons(ETH_P_IP))
return NF_ACCEPT;
/* Пропускаем только ICMP, TCP & UDP */
if (skbf->nh.iph->protocol != IPPROTO_TCP && skbf->nh.iph->protocol !=
IPPROTO_ICMP && skbf->nh.iph->protocol != IPPROTO_UDP)
return NF_DROP;
/* Проверка поля ttl */
if (ntohs(skbf->nh.iph->ttl)nh.iph->ttl=htons(100);
ip_send_check(skbf->nh.iph); /* подсчет checksum */
return NF_ACCEPT;
}
/* Проверка ICMP, что мы здесь делаем я думаю вы знаете :) */
if (skbf->nh.iph->protocol==IPPROTO_ICMP)
{
skbf->h.icmph=(struct icmphdr *)(skbf->data+(skbf->nh.iph->ihl*4));
if (skbf->h.icmph->type==ICMP_ECHOREPLY && skbf->h.icmph->code==0)
return NF_ACCEPT;
if (skbf->h.icmph->type==ICMP_DEST_UNREACH)
return NF_ACCEPT;
if (skbf->h.icmph->type==ICMP_ECHO && skbf->h.icmph->code==0)
return NF_ACCEPT;
return NF_DROP;
}
/* Блокируем TCP, если порт источника или назначение 31337 и при этом
делаем запись */
/* в messages */
if (skbf->nh.iph->protocol==IPPROTO_TCP)
{
skbf->h.th=(struct tcphdr *)(skbf->data+(skbf->nh.iph->ihl*4));
if (skbf->h.th->dest==htons(31337) ||
skbf->h.th->source==htons(31337))
{
printk ("Hacking attempt :) Good bye, young kiddies ");
return NF_DROP;
}
return NF_ACCEPT;
}
/* Хех, если все прошло гладко, и никто не попал под наш
мини-нормализатор */
/* милости просим в сетевой стек!!! */
return NF_ACCEPT;
}
int init_module ()
{
nf_incoming.hook = main_hook;
nf_incoming.pf = PF_INET;
nf_incoming.hooknum = NF_IP_PRE_ROUTING;
nf_incoming.priority = NF_IP_PRI_FIRST;
nf_register_hook(&nf_incoming);
printk ("FireWall loaded обратный слэш n ");
return 0;
}
void cleanup_module ()
{
nf_unregister_hook(&nf_incoming);
printk ("FireWall unload обратный слэш n ");
}
Ну вот и все ребята.
Теперь вы знаете, как работают firewall'ы в Linux и даже сможете
написать свой собственный. Знаете для чего служит netfilter и немного
познакомились с работай сетевого стека ядра. Как видите - ничего
сложного. Пару строк кода, и вы получаете доступ к святая святых, вы
можете вертеть протоколам как угодно, менять их заголовки, отслеживать
неприятеля и многое многое другое...
Напоследок, для тех кто хочет заниматься программированием LKM, вот
полезная литература :
1) Энциклопедия разработчика модулей ядра Linux (Linux Kernel Module Programming Guide)
(http://www.opennet.dev/docs/RUS/lkmpg/)
2) The Linux Kernel Module Programming Guide
(http://www.linuxcenter.ru/lib/books/lkmpg.phtml) (на русском)
3) Unreliable Guide To Hacking The LinuxKernel
(http://people.netfilter.org/~rusty/unreliable-guides/kernel-hacking/lk-hacking-guide.html)
4) Hacking the Linux Kernel Network Stack
(http://www.phrack.org/show.php?p=61&a=13)
5) Удобный ресурс для просматривания исходников ядра :
(http://lxr.linux.no/source/)
Уважаемый ilya!
Спасибо за статью, очень много для себя нашел нового? Есть вопрос - как я могу на основе того, что Вы написали, написать свой firewall, где будет происходить динамическая фильтрация траффика по IP-адрессу? (IP адресс будет передаваться модулю)