Створення і тестування Firewall в Linux, Частина 1.2. Простий перехоплення трафіку з Netfilter

Зміст першої частини:

1.1 — Створення віртуальної лабораторії (щоб нам було де працювати, я покажу як створити віртуальну мережу на вашому комп'ютері. Мережа буде складатися з 3х машин Linux ubuntu).
1.2 – Написання простого модуля в Linux. Введення в Netfilter та перехоплення трафіку з його допомогою. Об'єднуємо всі разом, тестуємо.
1.3 – Написання простого char device. Додавання віртуальної файлової системи — sysfs. Написання user interface. Об'єднуємо всі разом, тестуємо.

Дуже короткий вступ в Операційні системи

Для тих, хто не знайомий з основами роботи операційних систем загалом і linux-систем зокрема, ДУЖЕ коротко потрібні нам основні поняття, щоб ми могли рухатися далі. В Linux, як і багатьох інших ОС, є два адресних простору – kernel space user space.Kernel space – тут працює операційна система, вона в основному займається тим, що розподіляє ресурси комп'ютера між програмами (наприклад якась із програм зараз працює на процесорі (scheduling), якою програмою направити output з клавіатури, що робити з пакетами з мережевої карти, операції IO і багато всього іншого). ОС знаходиться в дуже інтимних зв'язках з апаратним забезпеченням (миша, монітор, принтер...). Так як її робота дуже важлива, для неї виділена окрема частина в пам'яті, яка не пов'язана з user space – місцем, де працюють більшість програм користувача, такі як: редактори, калькулятори, браузери і т. д. В Linux, щоб змінити або додати нові функції ОС — є кілька способів. Один з них — це поміняти вихідні тексти ядра і скомпілювати його заново. Але цей спосіб довгий і важкий, особливо коли потрібно розширити функціональність мінімально або динамічно. Тому є інший спосіб – модулі. Модулі – грубо кажучи — це програма, яку можна динамічно і швидко додати в kernel space, після чого, модуль стане частиною операційної системи і отримає більше функціональності і доступу до ресурсів ніж звичайні програми.

Є багато статей на цю тему, наприклад, дуже хороша. Сподіваюся, що я зміг донести інтуїцію.

Написання простого модуля в Linux. Практика
Для початку, дуже коротко розглянемо приклад простого модуля звідси — потім розглянемо, що таке Netfilter та сумісний всі разом в одному вихідному коді. Нижче будуть картинки, в кінці статті, посилання на всі вихідні тексти. І так:

image
Так виглядає вихідний код дуже простого модуля. Кілька речей:

1 – я працюю в редакторі Geany, так як Eclpise-зі подібні в даному випадку не підійдуть. Компилирую через термінал, але здається можна і через Geany.

2 – kernel немає printf, але є її аналог printk, яка працює таким же чином. До речі в kernel є аналоги всіх необхідних бібліотек і функцій (і немає стандартних бібліотек типу stdlib).

3 – printk, не пише в консолі, а пише в спеціальний файл, вміст якого можна прочитати, використовуючи команду dmesg. У нас — це буде головний спосіб для налагодження програми (є й інші способи побачити як працює printk, наприклад, якщо перейти в консоль без X інтерфейсу Ctrl+Shift+F1). Так само буде корисним dmesg –c, для очищення всього, що накопичилося в цьому файлі.

4 MODULE_LICENSE, MODULE_AUTHOR, MODULE_DESCRIPTION — це макроси, які «дають» linux інформацію про модулі, яку потім можна отримати за допомогою спеціальних команд (шукати детальніше за посиланням).

5 – kernel використовується синтаксист C89 для написання, коли на C. Наприклад (і головне) — на відміну від звичного C99, всі змінні повинні бути оголошені в самому початку функцій і не можуть бути оголошені в іншій її частині. Якщо ви НЕ будете дотримуватися це правило, код відбудеться створення, але з warnings (що погано і не для нас).

Компіляція модуля
Зверніть увагу (нижче на картинці) на Makefile. Він відрізняється від звичайних програм user space. Так виглядає весь процес:

image
Модуль завантажується спеціальною командою “insmod ./module_name.ko" після чого запускається макросmodule_init. Модуль видаляється з пам'яті комп'ютера командного «rmmod module_name».

Перевіряємо. Просто завантажимо модуль, а потім вивантажимо і подивимося на dmesg

image
Готове. На цьому введення в модулі закінчено.

Введення в Netfilter. Теорія
Netfilter – це framework, вбудований в linux kernel, який дозволяє виробляти різні мережні операції. Нас буде цікавити перехоплення вхідного і вихідного трафіку.

До речі, сама часто асоційована програма з даними framework – iptables, яка дозволяє динамічно задавати правила фільтрації трафіку (тобто — це ні що інше, як простий firewall) вбудований в Linux. В українській версії Wikipedia, майже не зробили відмінностей між двома термінами, що вважаю не правильним, тому даю посилання на англійську версію. Wikipedia — Netfilter
і на офіційний сайт. На ньому є вся необхідна інформація. www.netfilter.org

Архітектура перехоплення(hooking) трафіку. Теорія
Архітектура перехоплення(hooking) трафіку в Netfilter виглядає наступним чином:

image
Фото

На схемі позначені стадії, які проходять пакети після потрапляння на phy мережевої карти. Давайте розглянемо детальніше:

Prerouting – сюди потрапляють всі пакети, які прийшли на мережеву карту пристрою ззовні (наприклад хтось з мережі, катує послати нам mail, або ми на шляху проходження пакету і повинні передати його далі).
Forward – якщо отриманий пакет призначений не для даного IP-адреси, то операційна система перешле його далі (пам'ятаєте forward enable з попередньої частини?) або викине, якщо вважатиме його непотрібним.
Input – сюди потраплять всі пакети, які призначені для якої-небудь аплікації. Наприклад, це можуть бути пакети для браузера, після запиту будь-якої сторінки.
Output – тут з'являються всі пакети, які аплікації комп'ютера посилають в мережу (тобто не ті, яких ми робимо forwarding). Наприклад, той самий запит браузера отримати сторінку з інтернету. Або ping.
Postrouting – об'єднує всі вихідні пакети.
Математично:

Input data = Prerouting = Forward + Input
Output data = Postrouting = Output + Forward

Звичайно ж, зазвичай input != output (якщо ви не зрозуміли чому — то варто перечитати тему ще раз або подивитися додатково в інтернеті).

Наприклад, після того як ми введемо в рядку браузера www.site.com то пакет пройде дві «зупинки» перш ніж потрапить в мережу – Output Postrouting.

Коли site.com буде нам відповідати, то пакет пройде дві «станції», перш ніж потрапити до браузеруInput data Prerouting.

Інші пакети, наприклад, з host1 -> host2, пройдуть через Input, Forward, Output.
На кожній «зупинці» ми зможемо прийняти рішення з приводу «спійманого пакету» — чи хочемо ми його пропускати далі, чи ні (або щось ще).

Архітектура перехоплення(hooking) трафіку. Практика
Для інтересу, ми придумаємо правило – всі пакети, які призначені для комп'ютера FW_dev (той де ми встановимо firewall), або які FW_dev посилає від свого імені іншим комп'ютерам – ми дозволимо пройти (accept). Весь інший трафік, ми закриємо (deny). Кінцева мета – підрахувати скільки пакетів пройшло, скільки ми заборонили і передати дані користувача (user space).

Для цього спочатку модуля, визначимо декілька глобальних змінних.

static unsigned int accepted_num = 0;
static unsigned int dropped_num = 0;

Тепер встановимо hook functions, в потрібні точки перехоплення трафіку. Для початку напишемо самі функції. Всі функції повинні мати заздалегідь певний тип:

unsigned int hook_func(unsigned int hooknum, struct sk_buff *skb,
const struct net_device *in, const struct net_device *out,
int (*okfn)(struct sk_buff *)) {}

Код функцій:

// out hook packets, accept packet
unsigned int hook_func_out(unsigned int hooknum, struct sk_buff *skb,
const struct net_device *in, const struct net_device *out,
int (*okfn)(struct sk_buff *)) {
accepted_num++;
return NF_ACCEPT;
}

// hook in packets, accept packet
unsigned int hook_func_in(unsigned int hooknum, struct sk_buff *skb,
const struct net_device *in, const struct net_device *out,
int (*okfn)(struct sk_buff *)) {
accepted_num ++;
return NF_ACCEPT;
}

// hook forward packets, drop packet
unsigned int hook_func_forward(unsigned int hooknum, struct sk_buff *skb,
const struct net_device *in, const struct net_device *out,
int (*okfn)(struct sk_buff *)) {
dropped_num++;
return NF_DROP;
}

Наступний крок – їх необхідно зареєструвати в системі, тому логічно зробити це при завантаженні модуля. Виглядає це наступним чином:

// hook functions structs for registration usage
static struct nf_hook_ops nfho_forward;
static struct nf_hook_ops nfho_out;
static struct nf_hook_ops nfho_in;

static int __init fw_module_init(void)
{
.....
// netfilter functions
printk("initialize kernel module\n");

nfho_in.hook = hook_func_in;
nfho_in.hooknum = NF_INET_LOCAL_IN;
nfho_in.pf = PF_INET;
nfho_in.priority = NF_IP_PRI_FIRST;
nf_register_hook(&nfho_in); // Register the hook

nfho_out.hook = hook_func_out;
nfho_out.hooknum = NF_INET_LOCAL_OUT;
nfho_out.pf = PF_INET;
nfho_out.priority = NF_IP_PRI_FIRST;
nf_register_hook(&nfho_out); // Register the hook

nfho_forward.hook = hook_func_forward;
nfho_forward.hooknum = NF_INET_FORWARD;
nfho_forward.pf = PF_INET;
nfho_forward.priority = NF_IP_PRI_FIRST;
nf_register_hook(&nfho_forward); // Register the hook
...
}

Тут варто звернути увагу на полі hooknum, якому ми присвоюємо значення, яке і визначає місце перехоплення пакетів і відповідає діаграмі вище. Останнім кроком буде їх видалення (дерегистрация) перед видаленням модуля з ОС.

static void __exit fw_module_exit(void)
{
...
// net filter functions
nf_unregister_hook(&nfho_in);
nf_unregister_hook(&nfho_out);
nf_unregister_hook(&nfho_forward);
// end netfilter functions
...
}

Залишилося перевірити роботу, а також додати user interface, щоб кожен користувач міг вважати дані з нашого модуля. Спочатку перевіримо.

Передфінальний код
#include <linux/module.h>
#include <linux/netfilter_ipv4.h> 
#include "fw.h" 

MODULE_AUTHOR( AUTHOR); 
MODULE_DESCRIPTION( DESCRIPTION);
MODULE_VERSION( VERSION); 
MODULE_LICENSE("GPL"); 

static unsigned int accepted_num; 
static unsigned int dropped_num; 

// hook functions 
static struct nf_hook_ops nfho_forward; 
static struct nf_hook_ops nfho_out; 
static struct nf_hook_ops nfho_in; 

// out hook packets 
unsigned int hook_func_out(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) {
printk("Get input packet, accept\n");
accepted_num++;
return NF_ACCEPT; 
} 

// hook in packets 
unsigned int hook_func_in(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) {
printk("Get input packet, accept\n");
accepted_num++;
return NF_ACCEPT;
} 

// hook forward packets 
unsigned int hook_func_forward(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) {
printk("Get forward packet, drop\n");
dropped_num++;
return NF_DROP;
} 

static int __init fw_module_init(void) {
printk("Starting FW loading module\n");
accepted_num = 0;
dropped_num = 0;

nfho_in.hook = hook_func_in;
nfho_in.hooknum = NF_INET_LOCAL_IN;
nfho_in.pf = PF_INET;
nfho_in.priority = NF_IP_PRI_FIRST;
nf_register_hook(&nfho_in); // Register the hook 
nfho_out.hook = hook_func_out;
nfho_out.hooknum = NF_INET_LOCAL_OUT;
nfho_out.pf = PF_INET;
nfho_out.priority = NF_IP_PRI_FIRST;
nf_register_hook(&nfho_out); // Register the hook 
nfho_forward.hook = hook_func_forward;
nfho_forward.hooknum = NF_INET_FORWARD;
nfho_forward.pf = PF_INET;
nfho_forward.priority = NF_IP_PRI_FIRST;
nf_register_hook(&nfho_forward); // Register the hook 
return 0; 
} 

static void __exit fw_module_exit(void) 
{
printk("Removing FW module\n"); 
nf_unregister_hook(&nfho_in);
nf_unregister_hook(&nfho_out);
nf_unregister_hook(&nfho_forward); 
} 

module_init( fw_module_init); 
module_exit( fw_module_exit); 

Компілюємо, завантажуємо модуль
image
Шолом зhost2 одне ping повідомлення 10.0.2.3 (ми очікуємо, що модуль пропустить його, тому що, 10.0.2.3 – внутрішній інтерфейс. Якби повідомлення було для host110.0.1.1, тоді ми б його не пропустили)

image
дивимося на «логи», вивантажуємо модуль

image
Що сталося?
8354 – я завантажив модуль.
У 8356 – ми виявили якийсь вихідний пакет. На даному етапі ми не можемо знати, що це, але швидше за все один з пакетів DHCP інтерфейсу, який ми налаштували.
У 8359 – ми виявили вхідний пакет — це був наш ping. Після того як ми його отримали, ми відразу послали відповідь який і бачимо далі.
У 8359 – відповідь на ping.
У 8394 – вивантажили модуль.
На даному етапі можна погратися з системою і переконатися, що трафік з host1 ->host2, не проходить.

На цьому поки все, список посилань:

» The Linux Kernel Module Programming Guide
» How to Write Your Own Linux Kernel Module with a Simple Example
» https://en.wikipedia.org/wiki/Netfilter
www.netfilter.org
» or Firewall Packet Filtering — тут я брав картинку :)
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.