Дата публикации: 29.08.2007
Автор статьи:
Источник: (Оригинал: Перейти)
Взято с http://www.opennet.ru
Оригинал: 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 «);
}
Взято с http://www.opennet.ru
Copyright © 2006—2011 "Портал RusCentOS"
Хостинг нашего сайта в Хост.SU (