Защита от хакеров корпоративных сетей
Шрифт:
libnet_destroy_packet(&newpacket);
Если функция libnet_init_packet была аналогична функции malloc, то это просто свободно распространяемое приложение с лучшим названием.
Трах-тарах! Только что пакет был отослан. Что теперь дальше?
Возвращение пакета: подбор обратного трафика./* Get the next packet from the queue, */
while (1) {
packet = (u_char *) pcap_next(pcap, &pkthdr);
if (packet) {Обратите внимание на то, что pcap_next — простая функция: учитывая активный дескриптор файла libcpap и место для размещения пакета, функция pcap_next возвращает адрес памяти захваченного пакета. Эта память доступна для чтения и записи, что и было использовано в примере. Следует сделать некоторое замечание. То ли из-за опции IOCTL, то
/*
* Make packet parseable – switching on
* eth->ether_type and ip->ip_p is also a valid
* strategy. All structs are defined in
* /usr/include/libnet/libnet-headers.h
*/
/* Layer 1: libnet_ethernet_hdr structs */
(char *)eth = (char *)packet;
/* Layer 2: libnet_arp_hdr / libnet_ip_hdr structs */
(char *)arp = (char *)ip = (char *)packet + LIBNET_ETH_H;
/*
* Layer 3: libnet_icmp_hdr / libnet_tcp_hdr /
* libnet_udp_hdr structs
*/
(char *)icmp = (char *)tcp = (char *)udp = (char *)packet +
LIBNET_ETH_H
+ LIBNET_IP_H;Используемая в данном случае линия поведения очень проста. Каждая структура пакета выравнивается в области памяти, отведенной для его размещения, причем эта область точно такая же, как если этот пакет был заданного типа. Было бы глупо заполнить структуры неправильными данными, полагаясь лишь на выбор области памяти указателями eth->ether_type (на уровне канала передачи данных) или ip->ip_p (на сетевом уровне), которые указывают на структуру описания пакета. Если так сделать, то при ошибке в разборе пакетов будут утеряны важные данные segfaults. Например, при попытке получить порядковый номер TCP-пакета из области памяти, в которой на самом деле хранится UDP-пакет и у которого нет такого значения, ничего хорошего не получится. Но с другой стороны, рассмотренный способ достаточно гибок, потому что в общем случае только ядро операционной системы отважится прочитать эти данные. В конце концов, программе DoxRoute не очень интересно, как пользователь будет читать данный пакет. Одно важное предостережение относительно разбора пакетов: при захвате пакетов интерфейсу локального хоста не доступен заголовок Ethernet, поэтому не следует при чтении данных локального хоста пользоваться смещением LIBNET_ETH_H:
/* Handle ARPs: */
if (ntohs(eth->ether_type) == ETHERTYPE_ARP &&
arp->ar_op == htons(ARPOP_REQUEST) &&
!memcmp(arp->ar_tpa, user_ip, IPV4_ADDR_LEN)) {
/*
* IF: The ethernet header reports this as an
* ARP packet, the ARP header shows it a
* request for translation, and the address
* being searched for corresponds to this
* “stack”...
*
*/В этом месте программа ищет запросы ARP. Первое, что нужно сделать, – это удостовериться в том, что полученные данные действительно являются пакетом, содержащим нужный запрос. Для этого требуется пара надоедливых вещей. Во-первых, необходимо изменить порядок байтов в данных, на которые указывает указатель eth->ether_type, по крайней мере на системах с прямым порядком байтов. (Вполне возможно, что предложенный способ плохо работает на системах с обратным порядком байтов.) Это выполняется при помощи вызова функции ntohs, которая управляет переключением от сети к хосту. Затем следует проверить, что удаленная сторона осуществляет повторный запрос, повторно переключая порядок используемых байтов. На сей раз используется функция htons для представления ARP-запроса в присущий для сети формат. Наконец, коснемся вопроса о том, соответствует ли найденный ARP-запрос IP-адресу, присутствие которого искалось в сети для ее фальсификации. Это осуществляется при помощи инвертирования результата работы функции memcmp, возвращающей первый байт, которым отличаются два буфера. Причем нулевой код возврата означает, что различие между буферами не найдено, точно то, что нам и хотелось. Таким способом код возврата инвертируется в единичное значение:
memcpy(eth->ether_dhost, eth->ether_shost, ETHER_ADDR_LEN); memcpy(eth->ether_shost, user_mac, ETHER_ADDR_LEN);
Одной из действительно крутых вещей, которые можно сделать благодаря совместимости буферов libpcap и libnet, является
memcpy(arp->ar_tha, arp->ar_sha, ETHER_ADDR_LEN); memcpy(arp->ar_sha, user_mac, ETHER_ADDR_LEN);
Ах, акронимы! Какой замечательный способ начинать день! ARP-акронимы на самом деле не слишком плохи – tha и sha не означают ничего больше, кроме как «адрес хоста получателя» и «адрес хоста отправителя». В копировании на месте важно то, что это точный эквивалент только что сделанного на уровне Ethernet: «Отправитель ARP в поле user_mac информирует ARP-получателя, кто послал ARP запрос последним». Автор надеется, что читатель не удивится избыточности протокола:
arp->ar_op = htons(ARPOP_REPLY);
memcpy(test_ip, arp->ar_spa, IPV4_ADDR_LEN);
memcpy(arp->ar_spa, arp->ar_tpa, IPV4_ADDR_LEN);
memcpy(arp->ar_tpa, test_ip, IPV4_ADDR_LEN);Наконец, после преобразования пакета из формата запроса в формат ответа была выполнена замена IP-адресов. В рассматриваемом случае замена выполнена при помощи использования не требующей особого пояснения временной переменной. (Команда XOR тоже сгодилась бы, но автор ленив, а читатель должен был это понять.) После всех манипуляций был получен разумно законченный и правильный вариант ответа на ARP-запрос, который отправляется с инвертированными IP-адресами, ARP-адресами аппаратных средств и правильными характеристиками Ethernet-пакета. Бум, сделано:
i = libnet_write_link_layer(l, dev, packet, pkthdr.caplen);
if (verbose)
fprintf(stdout, “ARP: Wrote %i bytes\n”, i);Структура pkthdr полезна. Фактически это небольшая коллекция метаданных в момент их захвата. Они все потребуются для дальнейшей обработки. Элемент структуры caplen хранит длину захваченных данных. Он полностью подходит для функции установления связи и записи данных, которая нуждается в счетчике числа предполагаемых для отправки байтов. Поскольку при модификации пакетов situ длина их данных в общем случае не будет изменяться (хотя такое и могло произойти), знание оригинальной длины пакета позволяет узнать точное количество отосланных обратно данных. Также поможет и то, что рассматривается протокол с фиксированным размером пакетов, как, например, FTP, а не протокол с пакетами переменной длины, как, например, DNS:
/* Handle ARP replies (responding with upstream IP) */
} else if (eth->ether_type == ntohs(ETHERTYPE_ARP) &&
arp->ar_op == htons(ARPOP_REPLY) &&
!memcmp(arp->ar_spa, upstream_ip, IPV4_ADDR_LEN)){Ниже представлен тот же самый процесс, что и при прослушивании запросов ARPOP_REQUEST, только теперь осуществляется проверка полей ARPOP_REPLY:
memcpy(upstream_mac, arp->ar_sha, ETHER_ADDR_LEN);
if (verbose)
fprintf(stdout, “Router Found: %hu.%hu.%hu.%hu at
%X:%X:%X:%X:%X:%X\n”,
upstream_ip[0], upstream_ip[1], upstream_ip[2],
upstream_ip[3],
upstream_mac[0], upstream_mac[1], upstream_mac[2],
upstream_mac[3], upstream_mac[4], upstream_mac[5]);Помните путь назад, когда был отослан ARP-запрос на поиск нашего маршрутизатора? Ниже показано, как обработать ответ. Следует взять предложенный MAC-адрес, запомнить его в буфере upstream_mac, копируя его из данных, на которые указывает указатель arp->ar_sha. И все, сделано. Отметим, что в действительности такой подход, когда не учитываются состояния программы (обработка без памяти), уязвим к атакам спуфинга. В любое время злоумышленник может безнаказанно послать в адрес программы незатребованный ответ на ARP-запрос, для того чтобы он был использован для обновления величины upstream_mac. Для борьбы с этим есть неплохие способы, например для предотвращения обновления соединения можно использовать триггерную переменную, для реагирования на вышедшие из строя сайты – предусмотреть монитор маршрутизатора и т. д., но рассмотрение подобных способов выходит за рамки этой главы: