Доброго времени суток, дружище! Настало
время релиза обещанной мной следующей
статьи о создании собственного сниффера (предыдущую
статью читайте тут).
Думаю, она будет предпоследней :))
Сегодня мы рассмотрим замечательный
инструмент, библиотеку pcap, реализованную
для всех Windows-платформ (95, 98, ME, NT, 2000, XP и даже
CE) и для большинства UNIX-систем. Объединяющие
интерфейсы, libpcap и WinPCap едва различаются и, с
помощью pcap, ты сможешь написать сниффер,
переносимый на уровне исходных текстов
практически куда угодно. Например, сниффер
с поддержкой BPF-фильтрации (собственно, это
всё, что может понадобится от сниффера для
любых задач), приведённый в конце статьи,
компилируется одинаково хорошо подо все
доступные мне платформы. За исключением,
пожалуй, возможных warning'ов по поводу
несовпадения типов.
БИБЛИОТЕКА ПИКАП
Если ты пишешь под UNIX, тебе понадобится
пакет libpcap, желательно посвежее. Libpcap
немного эволюционирует: рассматриваемые
здесь приёмы действительны для версии 0.7.1.
Обрати внимание, в зависимости от версии,
libpcap по-дефолту встает в разные места (в /usr/lib,
в /usr/local/lib, etc), и тебе необходимо будет
добиться синхронизации версий, а также
решить, хочешь ли ты включать объектный код
libpcap в свою программу (а это плюс 90kb и выше),
или тебя устроят динамически подгружаемые
библиотеки. Кроме того, некоторые функции в
реализации под различные UNIX-платформы
ведут себя по-разному (это касается,
например, поддержки детального описания
интерфейсов), поэтому я настоятельно
рекомендую тебе читать документацию.
С Windows-боксами все проще. С ними всегда все
просто :)) Иди на сайт http://winpcap.polito.it
и качай пакет WinPCap версии 3.0a. В обязательном
порядке необходимо скачать и установить
WinPCap_3_0_a.exe, содержащий wpcap.dll (реализацию pcap
под Windows), packet.dll (сервис-провайдер для wpcap.dll)
и необходимые NDIS-драйвера уровня ядра.
WPdpack_3_0_a.zip содержит документацию к WinPCap,
набор примеров, include-файлы и
скомпилированные экспортные файлы для
wpcap.dll и packet.dll. В версии 3.0a есть небольшой
глючок, препятствующий нормальной линковке
wpcap.dll и packet.dll для систем NT/2000/XP (по крайней
мере, для моих cygwin и lccwin32). Чтобы его
исправить, качай WPcapSrc_3_0_a.zip и пересобери
экспортные файлы, исправив соответствующие
def-настройки.
Учти несколько важных моментов:
1) Программы, использующие pcap, должны быть
либо суидными, либо выполняться из-под рута
(или "Администратора" :))
2) Сниффить диалап-соединения в системах NT/2000/XP
посредством PacketX невозможно. Создатели WinPCap
списывают это на криворукость MicroSoft, и здесь
я рекомендую обратиться к PPP-драйверам
программ вроде CommView и их API, но это уже
отдельный разговор.
ДОКУМЕНТАЦИЯ К WINPCAP
...заслуживает очень теплых слов 🙂 Я получил
огромное удовольствие, изучая эту
документацию. Качественная навигация,
отличные примеры, глубокое фундаментальное
изложение понятий и технологий. По большому
счету, этой документации вполне достаточно,
чтобы полностью овладеть пикапом. Для самых
маленьких (для таких, как я, которые пишут
демо-программы, коцая текст из примеров в
документации) есть раздел "WinPcap tutorial: a step
by step guide to program WinPcap". Я сделаю небольшое
изложение этого раздела и расскажу об
основных приемах для создания
функционального сниффера на основе pcap'а.
1. OBTAINING THE DEVICE LIST
int pcap_findalldevs(pcap_if_t **alldevs, char *errbuf); -- функция,
собирающая информацию о доступных
интерфейсах, которые можно пытаться
прослушивать. Помимо интересующих нас
сетевых адаптеров, сюда попадают
всевозможные инфракрасные устройства,
параллельные порты, etc.
Параметр pcap_if_t **alldevs -- односвязный список.
Элементы разбирать не буду, они прекрасно
проиллюстрированы в документации. Отмечу
лишь, что функция pcap_findalldevs() сама выделяет
нужную память под **alldevp. Признак конца
списка -- значение NULL в очередном pcap_if *next из
выбранного **alldevs.
Проход по списку можно осуществлять так:
pcap_if_t *devlist;
pcap_if_t *d;
if(pcap_findalldevs(&devlist,errbuf) == -1)
{ handling error here; }
for(d=devlist;d;d=d->next)
{ some code here; }
**alldevs -- штучка интерфейсная, нужная, пожалуй,
лишь для общения с юзером. И, если нет нужды
хранить её в памяти, можно воспользоваться
специальным деструктором, pcap_freealldevs(pcap_if_t
*alldevp).
Что касается имен интерфейсов, в Windows они,
мягко скажем, не очень обычные, и задавать
их с командной строки не очень удобно. В
моей Win2k PRO, например, сетевой адаптер
называется так: \Device\NPF_{3960C746-75FE-44A8-9F3F-05790FC0810C}.
Стало быть, приличный сниффер нуждается в
наличии фичи либо с автоопределением
адаптера, либо с возможностью его выбора
юзером в диалоговом окне.
2. OPENING AN ADAPTER
pcap_t* pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char
*errbuf); открывает устройство char *device и
связывает его с дескриптором типа pcap_t *.
Параметр int snaplen задаёт количество байт,
необходимая снимать с каждого входящего
кадра данных. int promisc, будучи установленным в
ненулевое значение, переводит устройство в
promiscuous mode, int to_ms -- таймаут на ожидание кадра
данных в миллисекундах, char *errbuf -- ASCII-расшифровка
возникшей ошибки, заполняется при возврате
значения NULL.
void pcap_close(pcap_t *p); -- деструктор для
дескриптора устройства и связанной с ним
памяти.
3. ATTACHING A FILTER
Как я уже рассказывал в предыдущих
публикациях, существует технология BPF-фильтрации,
которой можно воспользоваться для весьма
дотошной детализации фильтрации входящих
кадров данных на аппаратном уровне (в
данном случае, на уровне сетевого адаптера).
Как известно, всякий раз при получении
кадра данных в свои буфера, сетевое
устройство генерирует прерывание
процессору. Фильтрация просто необходима:
сниффер без фильтра в сети высокой
пропускной способности (выше 10 Mbit/sec), с
более-менее навороченным выводом, способен
загрузить систему под 100%.
Библиотека pcap предоставляет очень простой
интерфейс к BPF-фильтрации, сосредоточенный
в двух основных функциях:
int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize,
bpf_u_int32 netmask);
int pcap_setfilter(pcap_t *p, struct bpf_program *fp).
Первая функция "компилирует" строку char
*str, состоящую из набора regular expressions для
фильтра в BPF-псевдокод и помещает его в
список struct bpf_program *fp, применяя фильтр к
сетевой маске bpf_u_int32 netmask и "сжимая" его,
отбрасывая избыточные данные, в случае
позитивного значения int optimize. Вторая
функция применяет полученный псевдокод к
указанному дескриптору устройства. Память
под bpf_program * выделяется автоматически, но
деструкторов, насколько я понимаю, не
предусмотрено, и тебе придётся
реализовывать их самостоятельно.
Синтаксис регулярных выражений для pcap_compilte()
можно найти в мэне tcpdump'а и в документации к
WinPCap'у.
4. CAPTURING THE PACKETS
Существует множество способов чтения
кадров данных с открытого (и, опционально,
зафильтрованного) интерфейса. Самый лучший,
с точки зрения сбережения ресурсов, это
функция с хендлером, вызываемым при
появлении нового сообщения (или пачки
сообщений).
Я говорю о функции int pcap_loop(pcap_t *p, int cnt, pcap_handler
callback, u_char *user). pcap_t *p -- это дескриптор
открытого интерфейса. int cnt -- количество
кадров данных, которое необходимо
прослушать перед тем, как передать
управление дальше. pcap_handler callback -- это ни что
иное, как ссылка на функцию-хэндлер, куда
передается управление при получении
следующего кадра данных. u_char *user -- это буфер
обмена между pcap_loop() и её хэндлером.
Сам хэндлер таков: void (*pcap_handler)(u_char *user, const
struct pcap_pkthdr *pkt_header, const u_char *pkt_data). В *pkt_header'е
содержатся реквизиты входящего кадра
данных (время поступления, размер, etc). u_char
*pkt_data -- это сам кадр, начиная с заголовка
локального уровня.
Демо-сниффер реализует данный подход таким
образом:
while(!enough) {
rc = pcap_dispatch(iface,number,packet_handler,NULL);
if(rc == -1) {
printf("pcap_dispatch() failed!\n");
return 1;
}
}
5. SENDING THE PACKETS
Возможность отправки данных в открытый
интерфейс посредством pcap существует лишь в
WinPCap и реализована в виде функции int pcap_sendpacket(pcap_t
*p, u_char *buf, int size).
Здесь всё просто. u_char *buf -- это кадр данных,
который необходимо отправить. int size -- его
размер. Контрольную сумму локального
уровня высчитывать и вставлять не надо, это
делает драйвер сетевого уровня
автоматически.
PACKET.DLL
Ты уже, наверное, догадался, packet.dll -- это
интерфейс низкого уровня для библиотеки
WinPCap, можно сказать, его сервис-провайдер.
Логично предположить, что реализовывать и
сниффинг, и спуффинг непосредственно на
уровне PacketX лучше, чем на WinPCap. Ты получаешь
больше возможностей, больше свободы, больше
контроля. Я напишу последнюю,
заключительную статью как раз о PacketX.
DEMO-SNIFFER
Я написал небольшой демо-сниффер,
реализующий описанные выше прием. Win-версия
зависит от WinPCap версии 3.0а, а unix-версия -- от
наличия libpcap. Бинарник под windows можно взять здесь,
а исходники -- здесь.
Небольшой парсинг для сниффера я накоцал из
библиотеки, которую пишу под свои задачи,
поэтому не удивляйтесь некоторой
неадекватности в коде программы. И читайте
pcap-sniffer.exe --help :))
Последний момент: в некоторых версиях libpcap (сюда
относится версия под Linux), описание
интерфейсов не поддерживается. Посему,
желающие компилировать прогу под линукс
должны исключить соответствующий код (обработку
параметра --list).
С уважением, [Privacy]