Ключевые слова:linux, assembler, kernel, iptables, network, (найти похожие документы)
From: intuit <intuit[at]rootshell.be>
Date: Mon, 7 Apr 2004 14:31:37 +0000 (UTC)
Subject: Основы программирования Netfilter на ассемблере.
Оригинал: http://rootshell.be/~intuit/nf.txt
author: intuit
mail: intuit[at]rootshell.be
Основы программирования Netfilter на ассемблере.
Многие слышали фразу:
"ОС Linux создана программистами
для программистов"
На самом деле она звучит иначе:
"ОС Linux создана Си-программистами
для Си-программистов....."
[1. ][Вступление]
[2. ][О чем статья]
[3. ][Теория]
[4. ][Примеры LKM]
[5. ][Заключение]
[1. ][Вступление]
Netfilter - подсистема ядра linux 2.4(экспериментальная поддержка была включена
начиная с версий 2.3). Netfilter позволяет осуществлять пакетную фильтрацию, NAT
и существенно расширить возможности работы с сетью за счёт установки специальных
"hook'ов" в часть ядра ОС, ответственной за network. Их можно устанавливать в
ядро одним из двух способов: статически(требуется перекомпиляция ядра) или в виде
LKM, регистрируя тем самым разнообразные функции, которые будут вызываться при
определенных условиях. Например, при получении определенного сетевого пакета.
[2. ][О чем статья]
Статья о работе с Netfilter, подсистемой ядра, расширяющей возможности ОС в ра-
боте с сетевыми фреймами. Язык программирования - ассемблер. Синтаксис GAS.
В статье описано создание простейших модулей(LKM) для демонстрации возможностей
данной подсистемы. Желательно иметь под рукой заголовочные файлы с рассматрива-
емыми в статье структурами. Ну, и разумеется для загрузки(тестирования) модулей
вам необходимы привилегии root ;).
[3. ][Теория]
Ядро подсистемы Netfilter состоит из 5-ти hook-функций, обьявленных в
linux/netfilter_ipv4.h. Видно, что эти функциии для IPv4, хотя больших отличий
от их аналогов для IPv6 нет. С помощью них можно контроллировать пакеты на раз-
личных уровнях сетевого стека.
Ниже приведена схема анализа сетевого пакета системой Netfilter:
[INPUT]--->[1]--->[ROUTE]--->[3]--->[4]--->[OUTPUT]
| ^
| |
| [ROUTE]
v |
[2] [5]
| ^
| |
v |
[INPUT*] [OUTPUT*]
Таблица функций Netfilter с их кодами в правой части
(используются в struct nf_hook_ops, см. ниже)
[1] - NF_IP_PRE_ROUTING = 0
[2] - NF_IP_LOCAL_IN = 1
[3] - NF_IP_FORWARD = 2
[4] - NF_IP_POST_ROUTING = 3
[5] - NF_IP_LOCAL_OUT = 4
[*] - Network Stack
NF_IP_PRE_ROUTING - первый хук, используемый ядром при получении пакета.
NF_IP_LOCAL_IN - используется в тех случаях, когда поступивший пакет предназна-
чен нашей машине и далее такой пакет "форвардится" не будет.
NF_IP_FORWARD - для пакетов, предназначенных для другого интерфейса.
NF_IP_POST_ROUTING - для пакетов, которые уже настроены для дальнейшего "путе-
шествия" по сети к своему адресату и готовы покинуть наш сетевой стек.
NF_IP_LOCAL_OUT - этой функцией обрабатываются пакеты, исходяшие непосредствен-
но от нас(из нашего собственного сетевого стека).
Несомненно все хук-функции важны, но мы пока сосредоточимся только на первой из
них. После обработки(проверки) пакета функция NF_IP_PRE_ROUTING должна возвра-
тить одно из предопределенных значений(код), чтобы решить дальнейший "маршрут"
пакета:
0 = "NF_DROP" Отбросить пакет.
1 = "NF_ACCEPT" Сохранить пакет.(Отправить пакет след. хук-ф-ии.)
2 = "NF_STOLEN" "Забыть" о пакете.
3 = "NF_QUEUE" Поставить пакет в очередь.
4 = "NF_REPEAT" Вызвать этот хук еще раз.
Установить новый хук в системе очень просто. Во-первых, нужно заполнить струк-
туру nf_hook_ops, определенную в linux/netfilter.h.
На Си структура nf_hook_ops выглядит так:
struct nf_hook_ops {
struct list_head list; //Заполнение данной структуры не является
//обязательным для регистрации хука в системе.
/* Поля которые идут далее заполнять обязательно */
nf_hookfn *hook; //Указатель на нашу главную подпрограмму, которая
//будет вызываться всякий раз при срабатывании нашего
//хука.
int pf; //Семейство протоколов с которым будет работать наш хук,
//обычно PF_INET(см. таблицу ниже и bits/socket.h).
int hooknum; //Одна из 5-ти функций, на которой будет висеть наш хук.
//У нас - NF_IP_PRE_ROUTING(код из таблицы).
int priority; //Приоритет. Подробнее см. linux/netfilter_ipv4.h
// У нас - NF_IP_PRI_FIRST = INT_MIN
//INT_MIN - константа, равная (-(2^32)/2)
};
Таблица кодов семейств протоколов:
PF_UNSPEC = 0
PF_LOCAL = 1
PF_INET = 2
PF_AX25 = 3
PF_IPX = 4
<...>
На ассемблере заполнение данной структуры выглядит следующим образом:
(более подробно см. source ниже)
<...>
.comm nf_my_ops,24
<...>
movl $our_hook, nf_my_ops+8
movl $2, nf_my_ops+12
movl $0, nf_my_ops+16
movl $int_minimum, nf_my_ops+20
<...>
Второе, что нужно сделать - необходимо зарегистрировать свой hook в сис-
теме. Это делается с помощью системной функции nf_register_hook(). Данная
функция при успешном завершении возвращает 0. В качестве параметра ей пе-
редается - указатель на struct nf_hook_ops(см. net/core/netfilter.c):
<...>
pushl $nf_my_ops
call nf_register_hook
<...>
Теперь, разберем подробнее прототип функции nf_hookfn, указатель на кото-
рую передаем в struct nf_hook_ops через nf_hookfn *hook.
На Си он выглядит так:
typedef unsigned int nf_hookfn(unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *));
Первый аргумент - название(код) одной из пяти типов хук-функций Netfilter.
Например, NF_IP_PRE_ROUTING = 0.
Второй - указатель на указатель на структуру sk_buff(управляющая структура
для описания сетевого пакета, см. linux/skbuff.h).
net_device *in и net_device *out - указатели на структуру net_device(каж-
дое сетевое устройство в ОС Linux описывается этой структурой, например,
используется для описания интерфейсов lo, eth0). Соответственно, in - для
входящего траффика(пакетов), out - для исходящего.
Последний аргумент - указатель функции, также принимающей в качестве аргу-
мента sk_buff struct и возвращающей целое число(см. net/core/netfilter.c).
Работа со структурами, передаваемыми этими аргументами, из нашей функции
our_hook на ассемблере происходит следующим образом:
Пример 1. Проверка имени интерфейса net_device *in:
---------
<...>
interface: .string "lo"
<...>
our_hook:
pushl %ebp
movl %esp, %ebp <---*
subl $16, %esp <---резервируем место в стеке под локальные
переменные
pushl interface
pushl 16(%ebp) <---указатель на struct net_device *in,
в частности на поле char name[IFNAMSIZ]
call strcmp
<...>
На момент исполнения инструкции по адресу (*) программный стек будет выгля-
деть таким образом:
| [...верхние адреса памяти...]
| [...........................] _
| [ *okfn ] - 24(%ebp) \
| [ *out ] - 20(%ebp) |
рост [ *in ] - 16(%ebp) >---параметры our_hook
стека [ **skb ] - 12(%ebp) | на стеке
| [ hooknum ] - 8(%ebp)_/
| [ EIP ] - 4(%ebp)
| [ EBP ] - 0(%ebp)
| [ <next 4 bytes> ] <--- ESP point here
| [...........................]
V [....нижние адреса памяти...]
Приведу небольшую часть структуры net_device:
struct net_device {
/*
* This is the first field of the "visible" part of this structure
* (i.e. as seen by users in the "Space.c" file). It is the name
* the interface.
*/
char name[IFNAMSIZ]; - offset 0
/*
* I/O specific fields
* FIXME: Merge these and struct ifmap into one
*/
unsigned long rmem_end; /* shmem "recv" end */ - offset 16
unsigned long rmem_start; /* shmem "recv" start */ - offset 20
unsigned long mem_end; /* shared mem end */ - offset 24
unsigned long mem_start; /* shared mem start */ - offset 28
unsigned long base_addr; /* device I/O address */ - offset 32
unsigned int irq; /* device IRQ number */ - offset 36
...
}
Как видно начало структуры, как раз содержит имя интерфейса..
Пример 2. Проверка IP адреса отправителя:
---------
Для начала приведу кусок struct sk_buff:
struct sk_buff {
/* These two members must be first. */
/* Next buffer in list */
struct sk_buff * next; - offset 0
/* Previous buffer in list */
struct sk_buff * prev; - offset 4
/* List we are on */
struct sk_buff_head * list; - offset 8
/* Socket we are owned by */
struct sock *sk; - offset 12
/* Time we arrived */
struct timeval stamp; - offset 16
/* Device we arrived on/are leaving by */
struct net_device *dev; - offset 24
/* Transport layer header */
union
{
struct tcphdr *th; - offset 28
struct udphdr *uh; - offset 28
struct icmphdr *icmph; - offset 28
struct igmphdr *igmph; - offset 28
struct iphdr *ipiph; - offset 28
struct spxhdr *spxh; - offset 28
unsigned char *raw; - offset 28
} h;
/* Network layer header */
union
{
struct iphdr *iph; - offset 32
struct ipv6hdr *ipv6h; - offset 32
struct arphdr *arph; - offset 32
struct ipxhdr *ipxh; - offset 32
unsigned char *raw; - offset 32
} nh;
....
}
Видно, что для работы с заголовком IP-пакета(IPv4) используется поле по
смещению 32 bytes от начала структуры sk_buff, содержащее *iph(указатель
на заголовок IP-пакета). Сл-но, для определения IP-адреса отправителя мы
должны работать со структурой на которую указывает данное поле..
Теперь посмотрим на саму структуру базового IP-пакета:
|<-------- 8 бит -------->|<-------- 8 бит -------->|
|-------------------------------------------------------------|
0| Версия | Длина | Тип обслуживания |
|-------------------------------------------------------------|
2| Длина пакета |
|-------------------------------------------------------------|
4| Идентификатор |
|-------------------------------------------------------------|
6| 0 | DF | MF | Смещение фрагмента |
|-------------------------------------------------------------|
8| Число переходов | Протокол |
|-------------------------------------------------------------|
10| Контрольная сумма заголовка |
|-------------------------------------------------------------|
12| IP-адрес отправителя |
|-------------------------------------------------------------|
16| IP-адрес получателя |
|-------------------------------------------------------------|
20~ Параметры (до 40 байт) ~
|-------------------------------------------------------------|
20-60~ Данные (до 65535 байт минус заголовок) ~
|-------------------------------------------------------------|
IP адрес отправителя находится по смещению 12 bytes, от начала IP-пакета.
Непосредственное определение структуры IP-пакета на Си:
#typedef unsigned int uint;
#typedef unsigned char uchar;
struct ip_packet {
uint version:4; /* 4-bit version */
uint header_len:4; /* header length in words in 32bit words */
uint serve_type:8; /* how to service packet */
uint packet_len:16; /* total size of packet in bytes */
uint ID:16; /* fragment ID */
uint __reserved:1; /* always zero */
uint dont_frag:1; /* flag to permit fragmentation */
uint more_frags:1; /* flag for "more frags to follow" */
uint frag_offset:13; /* to help reassembly */
uint time_to_live:8; /* maximum router hop count */
uint protocol:8; /* ICMP, UDP, TCP */
uint hdr_chksum:16; /* ones-comp. checksum of header */
uchar IPv4_source; /* IP address of originator */
uchar IPv4_dest; /* IP address of destination */
uchar options[]; /* up to 40 bytes */
uchar data[]; /* message data up to 64KB */
};
На ассемблере определение IP-адреса отправителя будет выглядеть так:
.comm sock_buff,4
/* blocked ip = 127.0.0.1 */
ip_address: .string "\x7f\x00\x00\x01"
<...>
our_hook:
pushl %ebp
movl %esp, %ebp <--- (см. схему строения стека)
<...>
/* Проверка структуры sk_buff */
movl 12(%ebp), %eax <--- вытащим адрес
movl (%eax), %eax <--- структуры sk_buff..
movl %eax, sock_buff <--- в переменную sock_buff
cmpl $0, sock_buff
jne .ip_head
movl $1, %eax
jmp .quit
/* Проверка валидности IP пакета */
.ip_head:
movl sock_buff, %eax
cmpl $0, 32(%eax)
jne .check_saddr
movl $1, %eax
jmp .quit
/* Проверка IP-адреса отправителя */
.check_saddr:
movl sock_buff, %eax
movl 32(%eax), %eax <--- вытащим указатель на IP-header из sk_buff..
movl 12(%eax), %eax <--- и source IP из IP-header
movl ip_address, %edx
cmpl (%edx), %eax
jne .accept_packet
movl $0, %eax <--- drop packet, if source == blocked ip
jmp .quit
<...>
Также можно фильтровать пакеты по порту получателя.. здесь я этого не рассма-
триваю, пусть это будет вашим д/з :)
Надеюсь, что работу со струтурами на ассемблере обьяснил более-менее понятно,
если будут вопросы стучите в асю or use mail, а лучше смотрите нужные заголо-
вочные файлы и ковыряйте сами ;)
[4. ][Примеры LKM]
Модуль, блокирующий все входящие пакеты:
/* ---drop_all_incoming_packets.s--- */
/* резервируем поименованную область nf_my_ops в секции bss */
.comm nf_my_ops,24
.globl init_module
.globl cleanup_module
.section .modinfo
__module_kernel_version: .string "kernel_version=2.4.18-6mdk"
.data
.align 32
int_minimum: .int -2147483648
.text
.align 32
init_module:
pushl %ebp
movl %esp, %ebp
subl $20, %esp
/* Заполняем структуру nf_my_ops = nf_hook_ops
* nf_my_ops+0: struct list_head list - не заполняем
* Заносим адрес нашей главной процедуры our_hook в поле nf_hookfn *hook
*/
movl $our_hook, nf_my_ops+8
/* PF_INET = 2 */
movl $2, nf_my_ops+12
/* NF_IP_PRE_ROUTING = 0 */
movl $0, nf_my_ops+16
/* NF_IP_PRI_FIRST = INT_MIN */
movl $int_minimum, nf_my_ops+20
/* адрес структуры -> в стек */
pushl $nf_my_ops
/* вызываем nf_register_hook */
call nf_register_hook
/* в eax -> 0 - код успешной загрузки модуля */
movl $0, %eax
movl %ebp, %esp
popl %ebp
ret
cleanup_module:
pushl %ebp
movl %esp, %ebp
subl $20, %esp
pushl $nf_my_ops
call nf_unregister_hook
movl %ebp, %esp
popl %ebp
ret
our_hook:
pushl %ebp
movl %esp, %ebp
/* Возвращаемое функцией значение 0 = "NF_DROP" */
movl $0, %eax
popl %ebp
ret
/* ---EOF--- */
Модуль, блокирующий все входящие пакеты с определенного IP адреса:
/* ---drop_all_incoming_packets_from_spec_saddr.s--- */
/* for BSS section */
.comm nf_my_ops,24
.comm sock_buff,4
.globl our_hook
.globl init_module
.globl cleanup_module
.section .modinfo
__module_kernel_version: .string "kernel_version=2.4.18-6mdk"
.section .rodata
.align 32
/* blocked ip = 192.168.16.21 */
.IP: .string "\xC0\xA8\x10\x15"
.data
.align 32
ip_address: .long .IP
int_minimum: .int -2147483648
.text
.align 32
our_hook:
pushl %ebp
movl %esp, %ebp
.check_sk_buff:
movl 12(%ebp), %eax
movl (%eax), %eax
movl %eax, sock_buff
cmpl $0, sock_buff
jne .ip_head
movl $1, %eax
jmp .quit
.ip_head:
movl sock_buff, %eax
cmpl $0, 32(%eax)
jne .check_saddr
movl $1, %eax
jmp .quit
.check_saddr:
movl sock_buff, %eax
movl 32(%eax), %eax
movl 12(%eax), %eax
movl ip_address, %edx
cmpl (%edx), %eax
jne .accept_packet
movl $0, %eax
jmp .quit
.accept_packet:
movl $1, %eax
.quit:
popl %ebp
ret
init_module:
pushl %ebp
movl %esp, %ebp
subl $20, %esp
movl $our_hook, nf_my_ops+8
movl $2, nf_my_ops+12
movl $0, nf_my_ops+16
movl $int_minimum, nf_my_ops+20
pushl $nf_my_ops
call nf_register_hook
movl $0, %eax
movl %ebp, %esp
popl %ebp
ret
cleanup_module:
pushl %ebp
movl %esp, %ebp
subl $20, %esp
pushl $nf_my_ops
call nf_unregister_hook
movl %ebp, %esp
popl %ebp
ret
/* ---EOF--- */
[5. ][Заключение]
С помощью широких возможностей предоставляемых подсистемой Netfilter можно без
особых усилий написать свой собственный фаерволл под требуемые задачи.
to be continued...
---$---
Greetz to all ppl on #m00@EFNet.
---$---
Си и Линукс рулят! Не троньте святое! на сях программеры занимаются математикой и алгоритмами,
а не мастурбацией с системными вызовами! это не M$DO$ чтоб на асме программить, а проблемы с портируемостью? кароче, ребята, Си изучайте, он рулит.
единственное из-за чего я рекламирую линукс формат на своём сайте-личная антимонопольная программа. а так вообще: все сишники и другие программеры языков высокого уровня- отпрыск социальной пирамиды, которому в общесве уготавливается роль слоя служащего диктатуне: хакеры, программисты ОС, которые даже при своей открытости стали настолько гигантски(внесомненно кастовая кочерга для тех кто ещё не потерял совесть и рассудок) что проще научится прогрить на асме. мастурбацией? ой-йой!!!видели бы себя со стороны. ничем нехуже шакалов, правда умудряющихся ещё и мылостыню выпростить иногда.