Коллектив авторов - Защита от хакеров корпоративных сетей
char errbuf[255];
int do_checksum = 0;
int verbose = 0;
int i = 0;Установка значений по умолчанию. Одна важная особенность, присущая всем программам, заключается в задании их поведения по умолчанию. Тем самым минимизируется объем знаний, необходимых программе во время ее первого запуска. Например, Web-серверам не обязательно указывать имя домашней страницы всякий раз, когда кто-либо подключается к http://www.host.com. По умолчанию если не указано ничего другого, то при подключении по этому адресу пользователю возвращается ответ, как если бы он запросил http://www.host.com/index.html. Точно так же следует установить значения по умолчанию для маршрутизации пакетов:
/* Set Broadcast MAC to FF:FF:FF:FF:FF:FF*/
bcast_mac[0] = 0xFF;
bcast_mac[1] = 0xFF;
bcast_mac[2] = 0xFF;
bcast_mac[3] = 0xFF;
bcast_mac[4] = 0xFF;
bcast_mac[5] = 0xFF;Иногда выбор установленных по умолчанию значений не представляет большого труда. Основные стандарты Ethernet определяют, что при задании в пакетах MAC-адреса FF: FF: FF: FF: FF: FF эти пакеты должны получить все хосты данной подсети. Локальная сеть на основе протокола Ethernet только недавно стала переключаемой средой, поэтому ранее использование адреса FF: FF: FF: FF: FF: FF больше носило характер «рекомендательного» сообщения для сетевых плат, которые должны были передать этот пакет операционной системе даже в том случае, если это сообщение не было адресовано определенному хосту. Ныне сетевые платы не видят сетевой трафик до тех пор, пока переключатель не посчитает, что он предназначен заданному хосту. В программе широковещательная рассылка MAC-адресов осуществляется следующим образом. Во многих протоколах предусмотрен запрос ко всем хостам локальной подсети. Для наших целей наиболее уместен протокол ARP:
/* Set Default Userspace MAC Address to 00:E0:B0:B0:D0:D0 */
user_mac[0] = 0x00;
user_mac[1] = 0xE0;
user_mac[2] = 0xB0;
user_mac[3] = 0xB0;
user_mac[4] = 0xD0;
user_mac[5] = 0xD0;Покажем, как можно в сети создать виртуальную сетевую карту, определив по умолчанию ее сетевой адрес отправителя. В действительности можно использовать любой адрес. Тривиальным и зачастую хорошим решением была бы рандомизация этого значения. Но рандомизация подразумевает, что нельзя будет по желанию запускать и останавливать маршрутизатор. Каждый раз при старте маршрутизатора в фоновом режиме хосты должны будут повторно назначить IP-адрес шлюза. Для этого им нужно будет заняться поиском новых обслуживаемых MAC-адресов. (Если читатель решит реализовать рандомизацию, то ему следует позаботиться о том, чтобы младшие значащие биты первого байта user_mac[0] не были установлены. А если они будут установлены, то в результате будет получен групповой MAC-адрес в локальной сети, использование которого приводит к очень интересным результатам.)
/* Set Default Upstream IP */
upstream_ip[0] = 10;
upstream_ip[1] = 0;
upstream_ip[2] = 1;
upstream_ip[3] = 254;Програма DoxRoute не является законченной реализацией маршрутизатора. Это лишь его скелет. Фактически пакеты только отсылаются реальному шлюзу. Исходя из опыта, адрес 10.0.1.254 обычно используется для шлюзования пакетов частных сетей, в которых должна быть выполнена программа DoxRoute. Кстати, вовсе не случайно переменной user_ip не устанавливается значение по умолчанию. Причина подобных действий известна как политика добрососедства: когда это возможно, то не следует рушить существующих систем. Любой отправленный IP-адрес может иметь вполне определенный смысл для уже развернутых систем. Вместо этого пусть пользователь найдет свободный IP-адрес и займется анализом проходящего через него сетевого трафика. Более сложная реализация предусматривала бы для нахождения свободного адреса использование протокола динамической конфигурации хоста DHCP, но это наложило бы довольно серьезные ограничения для клиентов, пожелавших направить сетевой трафик через отдельный мобильный маршрутизатор.
/* Set Default Interface */ snprintf(dev, sizeof(dev), “%s”, pcap_lookupdev(NULL));
В оперативной странице руководства сказано, что «pcap_lookupdev() возвращает указатель на сетевое устройство, который может быть использован функциями pcap_open_live() и pcap_lookupnet(). В случае ошибки возвращается значение NULL и в переменную errbuf записывается соответствующее сообщение об ошибке». Это немного непонятно. На самом деле возвращается указатель на строку, содержащую имя устройства, которую мы покорно запоминаем для возможного будущего использования. Командная строка: использование параметров командной строки для того, чтобы избежать жестко запрограммированных зависимостей. Ах, функция getopt. Эта стандартная функция очень полезна для разбора командной строки в стиле UNIX. Но ее работа не столь понятна, как, допустим, написание программ с ее помощью. Пример добротного использования функции getopt приведен ниже:
/* Parse Options */
while ((opt = getopt(argc, argv, «i:r:R:m:cv»)) != EOF) {
switch (opt) {
case “i”: /* Interface */
snprintf(dev, sizeof(dev), “%s”, optarg);
break;
case “v”:
verbose = 1;
break;Устанавливается цикл разбора всех параметров командной строки. Переменной цикла является счетчик аргументов, который каждый раз уменьшается на единицу и который указывает на первый найденный аргумент командной строки. Кроме того, задается строка, определяющая анализируемые флаги. В любой командной строке программы различают два основных вида параметров. Первый задает дополнительные аргументы, как, например, doxroute – i eth0. Второй является полностью законченным и самодостаточным, как, например, doxroute -v. Функция getopt представляет оба эти типа параметров как i: v. Двоеточие после i является признаком анализируемого аргумента, а указатель optarg должен указывать на аргумент. Отсутствие двоеточия после v означает, что простое присутствие флажка является достаточным поводом для завершения работы (в этом случае для большинства приложений установка глобальной переменной в единицу активизирует генерацию диагностики):
case “r”: /* Router IP */
sscanf(optarg, “%hu.%hu.%hu.%hu”,
&upstream_ip[0], &upstream_ip[1],
&upstream_ip[2],
&upstream_ip[3]);
break;
case “R”: /* Router MAC */
sscanf(optarg, “%X:%X:%X:%X:%X:%X”,
&upstream_mac[0], &upstream_mac[1],
&upstream_mac[2],
&upstream_mac[3], &upstream_mac[4],
&upstream_mac[5]);
break;
case “m”: /* Userspace MAC */
sscanf(optarg, “%X:%X:%X:%X:%X:%X”,
&user_mac[0], &user_mac[1], &user_mac[2],
&user_mac[3], &user_mac[4],
&user_mac[5]);
break;Для анализа адресов применены не самые хорошие способы, но тем не менее они работают. Такой способ решения был выбран для того, чтобы противостоять взлому из-за ошибок обработки типов:
case “c”: /* Checksum */
do_checksum = 1;
break;
default:
usage();
}
}
/* Retrieve Userspace IP Address */
if (argv[optind] != NULL) {
sscanf(argv[optind], “%hu.%hu.%hu.%hu”,
&user_ip[0], &user_ip[1], &user_ip[2],
&user_ip[3]);
} else
usage();Чего функция getopt не может предусмотреть, так это отсутствия флажков. Другими словами, ситуацию отсутствия флажков следует предусмотреть именно в этом месте. Здесь же (в функции usage) можно затребовать наиболее важные данные для работы программы – реализовать тайный прием IP-адресов. Следует отметить, что, как правило, функция usage() почти всегда завершает программу с флагом ошибки. Обычно это свидетельствует о неправильных действиях пользователя и необходимости вызова RTFM для уточнения ошибки. Запуск Libpcap. Следующее, что потребуется сделать для подготовки к фактическому мониторингу сети с целью поиска интересного трафика, – это спланировать свою реакцию:
/* Begin sniffing */
pcap = pcap_open_live(dev, 65535, promisc, 5, NULL);
if (pcap == NULL) {
perror(“pcap_open_live”);
exit(EXIT_FAILURE);
}При отсутствии ошибок открывается окно главного интерфейса с указанием спецификаций максимально возможного захвата без учета размера захватываемых данных. Перехватываются все доступные этому интерфейсу пакеты независимо от того, адресованы они одобренному ядром операционной системы MAC-адресу или нет. Для синтаксического анализа пакетов используется минимальная задержка, понижающая риск возможных ошибок:
if (ioctl(pcap_fileno(pcap), BIOCIMMEDIATE, &immediate)) {
/*perror(“Couldn’t set BPF to Immediate Mode.”); */
}Прежде чем пакет будет передан на обработку, устанавливается задержка 5 мс. Это специально сделано для успешного завершения обработки пакета на платформах с недостаточной скоростью работы. Быстродействие – это хорошо, но в действительности гораздо интереснее иметь дело с каждым пакетом в момент его поступления. Сказанное Linux выполняет в любом случае, но операционные системы типа BSD и, возможно, некоторые другие платформы для определения режима Immediate Mode используют опцию управления вводом / выводом IOCTL. Этот режим является своего рода отдаленным родственником опции сокета TCP_NODELAY, которая вынуждает обрабатывать каждый сегмент данных настолько быстро, насколько это возможно, в противоположность тому, когда только определенное количество данных может передаваться на следующий уровень обработки.
Опция IOCTL настолько сильно улучшает производительность, что просто непонятно, каким образом на некоторых платформах можно обходиться без нее. В целом флажок BIOCIMMEDIATE сообщает библиотеке libpcap о необходимости блокировки чтения и установке буфера минимально возможного размера. При этом гарантируется максимальное время обработки пакетов маршрутизатором. Это хорошая вещь.