Пишем сниффер (BSD)

Существует в BSD системах очень удобный интерфейс bpf, что расшифровывается как "Пакетный Фильтр Беркли". Его использует каждый сниффер, вспомните хотя бы tcpdump или ipgrab. Для прямой работы с этим фильтром написана очень удобная библиотека libpcap, которую можно скачать с сайта производителя: www.tcpdump.org. Чем же замечательна эта библиотека?

typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr *,
const u_char *);
char *pcap_lookupdev(char *);
int pcap_lookupnet(char *, bpf_u_int32 *, bpf_u_int32 *, char *);
pcap_t *pcap_open_live(char *, int, int, int, char *);
pcap_t *pcap_open_dead(int, int);
pcap_t *pcap_open_offline(const char *, char *);
void pcap_close(pcap_t *);
int pcap_loop(pcap_t *, int, pcap_handler, u_char *);
int pcap_dispatch(pcap_t *, int, pcap_handler, u_char *);
const u_char*
pcap_next(pcap_t *, struct pcap_pkthdr *);
int pcap_stats(pcap_t *, struct pcap_stat *);
int pcap_setfilter(pcap_t *, struct bpf_program *);
int pcap_getnonblock(pcap_t *, char *);
int pcap_setnonblock(pcap_t *, int, char *);
void pcap_perror(pcap_t *, char *);
char *pcap_strerror(int);
char *pcap_geterr(pcap_t *);
int pcap_compile(pcap_t *, struct bpf_program *, char *, int,
bpf_u_int32);
int pcap_compile_nopcap(int, int, struct bpf_program *,
char *, int, bpf_u_int32);
void pcap_freecode(struct bpf_program *);
int pcap_datalink(pcap_t *);
int pcap_snapshot(pcap_t *);
int pcap_is_swapped(pcap_t *);
int pcap_major_version(pcap_t *);
int pcap_minor_version(pcap_t *);
FILE *pcap_file(pcap_t *);
int pcap_fileno(pcap_t *);
pcap_dumper_t *pcap_dump_open(pcap_t *, const char *);
void pcap_dump_close(pcap_dumper_t *);
void pcap_dump(u_char *, const struct pcap_pkthdr *, const u_char *);
int pcap_findalldevs(pcap_if_t **, char *);
void pcap_freealldevs(pcap_if_t *);
u_int bpf_filter(struct bpf_insn *, u_char *, u_int, u_int);
int bpf_validate(struct bpf_insn *f, int len);
char *bpf_image(struct bpf_insn *, int);
void bpf_dump(struct bpf_program *, int);
это неполный список функций взятых из заголовочного файла pcap.h. Полную информацию о назначении каждой из функций вы можете подчерпнуть из третьей man страницы pcap, моя же цель - предоставить вам достаточную информацию по написанию простого сниффера.

Как вы знаете, для того чтобы сниффер заработал, его нужно запускать из под рута, так как к bpf устройству доступ есть только у него, не забывайте это при тестировании своего творения.

Поиск устройства
Есть две возможности перехвата трафика: это перехват в промиск-моде и вне него. В первом случае вы перехватываете весь трафик в сети (при условии, что нет свитчей, или их память переполнена арп таблицей - они все работают как простые хабы, и отсутствии роутеров), во втором случае, вы сможете перехватывать только исходящий или приходящий трафик (непосредственно через вашу сетевую карту).
Из этого следует, что при установке сниффера следует выбирать тот интерфейс, на котором вероятность перехвата трафика выше. Выполнить поиск интерфейса можно при помощи функции pcap_lookupdev(); Она вернет указатель на массив символьного типа с названием интерфейса. Стоит также отметить, что вернет она первый интерфейс, находящийся в НЕ_ПРОМИСК_МОДЕ, так что не удивляйтесь, если у вас на машине, на которой стоит arpwatch вернется какой-нить "не тот" интерфейс.

После того, как мы отыскали нужное нам устройство, стоит "заценить" его сетевые настройки - это важно для дальнейшей работы сниффера, так как программе следует знать наверняка метрику сети. Все это происходит с вызовом функции

pcap_lookupnet(char *, bpf_u_int32 *, bpf_u_int32 *, char *);
первый параметр - указатель на название устройства (помните предыдущий вызов?), второй и третий - маска сети и ее адрес. Стоит подробно остановится на обсуждении четвертого параметра: дело в том, что это буфер для возврата всех ошибок, полученных при вызовах функция pcap’a. Его описывают следующим образом:
char ebuf[PCAP_ERRBUFF_SIZE];
Замете, что данный буфер используется в большинстве функция pcap’a.

Использование устройства
После того, как мы узнали необходимую информацию об устройстве, мы его открываем на чтение. Этим занимается функция

pcap_open_live(char *, int, int, int, char *);
Несколько слов о параметрах: первый - это устройство, второй - количество байт, необходимых для перехвата, третий - бит промиск мода, если он утановлен, то он включается, помните я говорил о вариациях сниффинга? Четвертый – это таймаут (в миллисекундах) и последний - буфер для ошибок. Возвращает эта функция указатель на сессию. Пример:
#include <stdio.h>
#include <pcap.h>

int
main(int argc, char *argv[])
{
char *dev,ebuf[PCAP_ERRBUFF_SIZE];
pcap_t *ptr;
bpf_u_int32 mask,net;

if (argc <2)
dev=pcap_lookupdev(ebuf);
else
dev=argv[1];

printf(“We’v choisen the %s – device\n”,dev);
pcap_lookupnet(dev, &net, &mask, ebuf);
ptr=pcap_open_live(dev, BUFSIZ, 1, 0, ebuf);
}
Собственно наш пример пока ничего не делает, кроме того, что принимает в качестве параметра командной строки название устройства (в случае отсутствия параметра ищет устройство сам), выводит полученную информацию и открывает сессию, переводя сетевую карту в режим прослушивания.

Использование фильтра
Фильтр описывается точно таким же синтаксисом, как и в tcpdump. Описывается он в обычном массиве символьного типа. Принцип следующий: мы вводим наши данные о фильтре в этот массив, а затем, при, так сказать компиляции, нашь фильтр переводится в нужный нам формат для использования:

…….
struct bpf_program bpf_filter;
char filter_buf[]=”foo”;
……..
pcap_compile(ptr, &bpf_filter, filter_buf, 0, net);
pcap_setfilter(ptr, &bpf_filter);
……...
Основной цикл
Процесс перехвата обычно происходит в определенном цикле. За организацию данного цикла отвечают некоторые функции, о которых вы можете подчерпнуть из man страниц. Вот одна из них:
const u_char* 	pcap_next(pcap_t *, struct pcap_pkthdr *);
Первый параметр - это указатель на нашу сессию, второй - указатель на следующую структуру:
struct pcap_pkthdr {
struct timeval ts; /* time stamp */
bpf_u_int32 caplen; /* length of portion present */
bpf_u_int32 len; /* length this packet (off wire) */
};
Данная структура (заголовок пакета) взята из pcap.h. На самом деле, данная функция не создает цикла, она просто берет следующий пакет из очереди и возвращает на него указатель.
int pcap_loop(pcap_t *, int, pcap_handler, u_char *);
Первый параметр – сессия, второй - количество пакетов, которые следует перехватить прежде чем обработать, третий - указатель на функцию-обработчик, который будет обрабатывать что-то (например этот самый пакет), и последний параметр, который может вообще не использоваться, просто указывается NULL (хотя стоит признать тот факт, что некоторые программные продукты его используют: для подробной информации обращайтесь к man). Небольшой пример:
//вырезано
void
analiz(u_char *stuff,const struct pcap_pkthdr *hdr,const u_char *packet)
{
printf(“New packet has arrived!\n”);
}

//часть инициализации вырезана
//сама функция цикла:

pcap_loop(handle, -1, analiz, NULL);
Теперь, если запустить данную программу без фильтрации, то мы сможем увидеть появление данной надписи столько раз, сколько пакетов прийдет к нам на карту:
Aucronix# ./test-snif rl1
New packet has arrived!
New packet has arrived!
New packet has arrived!
New packet has arrived!
New packet has arrived!
………………………..
Для написания большого проекта желательно знать как можно лучше устройство сети, в их познании вам помогут различные rfc. А я на этом заканчиваю, продолжение следует...