ХакерДом: team/articles?/LinuxKernelFirewall ...

Null Page | Каталог | Изменения | НовыеКомментарии | Пользователи | Регистрация | Вход:  Пароль:  

Linux Kernel Firewall


e-mail: zelenchuk [ dog ] gmail.com

Предисловие


Каждый уважающий себя администратор Linux должен уметь не только настраивать iptables, но и знать, как он работает. В этой статье речь пойдет не о том, как правильно настраивать iptables или какой ни будь другой firewall, а то том, как работают firewall’ы в Linux.
В первую очередь, эта статья нацелена на читателей, которые занимаются (начинают или только хотят начать) программированием модулей ядра Linux (Linux Kernel Module – LKM), а также, надеюсь, поможет некоторым администраторам более детально разобраться в работе iptables. Все примеры в статье написаны для ядра 2.6.12.


Оглавление :


1. Netfilter Hacking
2. Firewall своими руками
3. Пример norm.c


 

1. Netfilter Hacking


Что такое netfilter и какое отношение он имеет к Firewall’ам ?
В основном, Netfilter представляет собой набор функций (hook) расположенных в ядре, при помощи которых, firewall’ы могут получать доступ к пакетам и основываясь на правилах решать, как с ними поступать дальше. Netfilter содержит 5 основных hook-функций, которые описаны в linux/netfilter_ipv4.h.


Графически их можно изобразить так :


[INPUT]--->[1]--->[ROUTE]--->[3]---->[4]--->[OUTPUT]

|
^

|
|

|
[ROUTE]

v
|

[2]
[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. Если этого не сделать, произойдет очень неприятная вещь. Придет пакет, сработает наш вызов, ядро попытается обратиться к странице памяти для вызова нашей функции для обработки, а там…. эээ в лучшем случае ничего, в худшем ..кто-то занял наше место и тогда результат буде непредсказуем.


 

2. Firewall своими руками


Для примера напишем маленький firewall. Который будет беспощадно уничтожать все входящие и исходящие пакеты.


bash$ > cat firewall.c


#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter.h>
 
MODULE_LICENSE ("GPL v2");
MODULE_AUTHOR ("ill");
MODULE_DESCRIPTION ("Firewall");
 
/* 
* Объявление структур. Мы объявим 2-е структуры.
* 1-я для входящих пакетов
* 2-я для исходящих пакетов
*/
 
struct nf_hook_ops nf_incoming;
struct nf_hook_ops nf_outgoing;
int i=1;
 
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 (void)
{
 
/*
* Заполнение структур
* Сначала, заполним структуру для входящих пакетов
*/
 
 
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_POST_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 (void)
{
 
/* Не забываем удалить наши вызовы :), а то конфуз может случится */
 
nf_unregister_hook(&nf_incoming);
nf_unregister_hook(&nf_outgoing);
 
printk ("FireWall unload\n");
    
}


Ну вот. Все очень просто! Теперь компилируем наш модуль, для этого я пользуюсь вот таким Makefile'ом:
(Исходный код сохраним с именем firewall.c, а исходники ядра находиться в папке /usr/src/linux.)


obj-m += firewall.o
 
all:
    make -C /usr/src/linux/ M=${PWD} modules
clean:
    make -C /usr/src/linux/ M=${PWD} clean


И запускаем: insmod firewall.ko (иногда приходится запускать с ключем -f: insmod -f firewall.ko, а то ему версии не нравятся, но кому не лень, можно в модуле прописать все данные о версии ядра) Посмотрите /var/log/messages – если увидите "Fire Wall? loaded" значит наш модуль загрузился. Теперь, если вы попробуйте подключится к кому ни будь, или наоборот, кто-то захочет к вам подключится, ничего не выйдет. Наш модуль не пропустит ни одного пакета. Что бы вернуть все на место, просто выгрузите модуль – rmmod firewall


Вот пример firewall’a в 60 строк, включая заголовки. Не сложно правда..!!!)
Теперь перейдем к более сложным вещам. Но совсем на чуть-чуть


 

3. Пример norm.c


В этом примере мы будем проводить небольшой анализ захваченного нами пакета. Наша программа будет анализировать заголовки пакета и в случае неудовлетворения правилам будет удалять его или править. Правила я брал из статьи «Нормализация пакета» (Спасибо автору, очень познавательная). И так, для начала, небольшое введение в структуру sk_buff:
sk_buff – это буфер для работы с пакетами. Как только приходить пакет или появляется необходимость его отправить, создается sk_buff куда и помещается пакет, а также сопутсвующая информация, откуда, куда, для чего… На протяжение всего путешествия пакета в сетевом стеке используется sk_buff. Как только пакет отправлен или данные переданы пользователю, структура уничтожается, тем самым освобождая память. Описание этой структуры можно найти в linux/skbuff.h. Она очень большая, я не буду выкладывать её
сюда :) Все что мы будем использовать из неё, это:


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


Более подробно о работе sk_buff можно почитать в Интернете, информации о нем море, а что касается практической части, совету почитать статью из phrack № 55 “Building Into The Linux Network Layer”


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


1. IP – проверка протокола следующего уровня (пропускать только TCP, UDP, ICMP)
2. IP – если поле TTL < 10 увеличиваем до 100 (не забывайте пересчитывать контрольную сумму :), после изменения содержимого пакета)
3. ICMP – пропускаем только следующие пары:


Type=0 Code=0 – ответ (echo_reply) на ping
Type=3 Code=0–15 -назначение недоступно (сеть, хост, порт…)
Type=8 Code=0 – запрос (echo) на ping


4. TCP – заблокируем порт 31337 !


bash$ > cat norm.c


#include <linux/module.h> /* Эйй, мы ведь пишем модуль  к ядру */
#include <linux/kernel.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
 
/*подключаем заголовки для работы с сетевыми протоколами */
/* и собственно говоря sk_buff */
 
#include <linux/skbuff.h>
#include <linux/inet.h>
#include <linux/ip.h>
#include <net/ip.h>
#include <linux/tcp.h>
#include <linux/icmp.h>
#include <asm/uaccess.h>
 
/* Увековечим свое имя Aha-ha… */
 
MODULE_LICENSE ("GPL v2");
MODULE_AUTHOR ("ill");
MODULE_DESCRIPTION ("Firewall");
 
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)<10)
{
    skbf->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\n");
        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.ru/docs/RUS/lkmpg/)
2. The Linux Kernel Module Programming Guide (http://www.linuxcenter.ru/lib/books/lkmpg.phtml) (на русском)
3. Unreliable Guide To Hacking The Linux Kernel(http://people.netfilter.org/~rusty/unreliable-guides/kernel-hacking/lk-hacking-guide.html)
4. Hacking the Linux Kernel Network Stack


P.S. Может кто подскажет а как в Windows работает FireWall? :)



 
Файлов нет. [Показать файлы/форму]
Один комментарий. [Показать комментарии/форму]