Генератор пакетов на RAW-сокетах в Windows 2000/XP.
Фундаментальной единицей всего сетевого программирования практически во всех операционных системах является сокет. Так же, как функции файлового ввода-вывода определяют интерфейс взаимодействия с файловой системой, сокет соединяет программу с сетью. Существует несколько типов сокетов, однако чаще всего встречаются следующие три типа:
- SOCK _ STREAM – обеспечивает надежный дуплексный протокол на основе установления логического соединения. Если говорит о семействе протоколов TCP / IP , которое сейчас получило самое широкое распространение, то это TCP .
- SOCK _ DGRAM – обеспечивает надежный сервис доставки датаграмм. В рамках TCP / IP это будет протокол UDP .
- SOCK _ RAW – предоставляет доступ практически ко всем служебным полям заголовков протоколов, таких как: IP , TCP , UDP и ICMP .
Первые два типа сокетов чаще всего применяются на практике. По работе с ними написано очень много статей и книг, поэтому на них подробно мы останавливаться не будем. В данной будет рассмотрено программирование именно третьего вида сокетов (низкоуровневых сокетов). Иногда бывает необходимо создать пакет, неважно какой – IP , TCP, UDP или ICMP , с заданными служебными полями, будь то для сканирования портов, прохождения через файрвол, определения операционной системы или просто проведения DOS -атаки. В этот момент к нам на помощь приходят низкоуровневые («сырые») сокеты. В данной статье мы рассмотрим основы работы с данным типом сокетов и в ходе статьи напишем генератор IP , TCP , UDP и ICMP пакетов. Предполагается что читатель знаком с языком программирования С, в частности, имеет представление о работе с простыми сокетами и основами стека протоколов TCP / IP . Для работы с RAW -сокетам, прежде всего, нам необходима библиотека WinSock 2.2. Далее рассмотрим основные структуры, используемые при написании генератора пакетов. Данная стpyктypа содеpжит IP адpес 4-ой веpсии
struct in_addr { in_addr_t s_addr; };
Стpyктypа, содеpжащая адpеснyю инфоpмацию о сокете
struct sockaddr_in { u_int8_t sin_len; sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr; int8_t sin_zero[8]; };
Далее коротко рассмотрим формат заголовков используемых протоколов, а также структуры, описывающие эти заголовки.
Протокол IP (RFC791).
Заголовок данного протокола описывается следующей структурой:
struct ip_header { unsigned char version; // номер версии протокола unsigned char tos; // тип сервиса unsigned short length; // общая длина пакета unsigned short id ; // идентификатор пакета unsigned short flags; // флаги unsigned char ttl ; // Время жизни пакета unsigned char proto; // Протокол верхнего уровня unsigned short crc; // CRC заголовка unsigned int src_addr; // IP- адрес отправителя unsigned int dst_addr; // IP- адрес получателя };
Протокол TCP ( RFC 793).
Заголовок данного протокола описывается следующей структурой:
struct tcp_header { unsigned short src_port; // порт отправителя unsigned short dst_port; // порт получателя unsigned int seq_n; // номер очереди unsigned int ack _ n ; // номер подтверждения unsigned char offset; // смещение unsigned char flags; // флаги unsigned short win; // окно unsigned short crc ; // контрольная сумма заголовка unsigned short urg _ ptr ; // указатель срочности };
Протокол UDP ( RFC 768).
Заголовок данного протокола описывается следующей структурой:
struct udp_header { unsigned short src _ port ; // номер порта отправителя unsigned short dst _ port ; // номер порта получателя unsigned short length; // длина датаграммы unsigned short crc ; // контрольная сумма заголовка };
Протокол ICMP ( RFC 792).
Заголовок данного протокола описывается следующей структурой:
struct icmp_header { unsigned char type; // тип ICMP- пакета unsigned char code; // код ICMP- пакета unsigned short crc ; // контрольная сумма unsigned long orig_timestamp; // дополнительные поля unsigned long recv_timestamp; // уточняющие тип unsigned long trns_timestamp; //ICMP- пакета };
Также необходимо ввести структуру псевдозаголовка, которая позволит вычислять контрольную сумму в TCP и UDP пакетах.
struct pseudo_header { unsigned int src_addr; // адрес отправителя unsigned int dst_addr; // адрес получателя unsigned char zero ; //начальная установка unsigned char proto; // протокол unsigned short length; // длина заголовка };
Далее требуется вспомнить порядок байт при работе с сокетами. Сyществyют сетевой(network) и хостовый(host) поpядки байта. Hапpимеp адpеса источника и назначения, поpты источника и назначения должны быть в сетевом поpядке байта. Для пеpевода с одного поpядка байта использyются следyющие фyнкции:
htons() -- "Host to Network Short"
htonl() -- "Host to Network Long"
ntohs() -- "Network to Host Short"
ntohl() -- "Network to Host Long"
" h " - означает хост, " n " - сеть, " s " - тип short , " l " - тип long .
Hапpимеp для того чтобы пеpевести поpт назначения (25) из хостового поpядка байта в сетевой мы сделаем так:
htons(25);
Вы можете использовать эти фyнкции для пеpевода любого из полей TCP/IP заголовка в сетевой поpядок байта и обpатно, но для IP адpеса использyется дpyгая фyнкция. В слyчае если y Вас есть стpока типа: "192.168.1.1" и Вам необходимо пеpевести ее в сетевой поpядок байта, то использyем однy из следyющих фyнкций:
in_addr_t inet_addr(const char *cp); int inet_aton(const char *cp, struct in_addr *addr);
Использование последней функции пpедпочтительней, посколькy inet_addr устарела.
Далее поговорим о подсчете контрольных сумм в генерируемых пакетах. Контрольную сумму в IP и ICMP пакетах считает следующая функция:
unsigned short rs_crc (unsigned short * buffer, int length) { unsigned long crc = 0; // Вычисление CRC while (length > 1) { crc += *buffer++; length -= sizeof (unsigned short); } if (length) crc += *(unsigned char*) buffer; // Закончить вычисления crc = (crc >> 16) + (crc & 0xffff); crc += (crc >> 16); //Смещение CRC , если необходимо if (1) crc = crc << 1; // Возвращаем инвертированное значение if (Fdata->crash_crc) return ( unsigned short )( crc ); //Если указана опция исказить CRC , //то возвращаем не инвертированное значение CRC else return (unsigned short)(~crc); }
Для подсчета CRC в TCP и UDP пакетах необходимо воспользоваться следующей функцией, которая сначала создает псевдозаголовок, а уже потом вычисляет контрольную сумму.
unsigned short rs_pseudo_crc(char *data, int data_length,unsigned int src_addr, unsigned int dst_addr, int packet_length, unsigned char proto) { char * buffer; unsigned int full_length; unsigned char header_length; struct pseudo_header ph; unsigned short p_crc = 0; // Заполнение структуры псевдозаголовка ph.src_addr = src_addr; ph.dst_addr = dst_addr; ph.zero = 0; ph.proto = proto; ph.length = htons (packet_length); header_length = sizeof (struct pseudo_header); full_length = header_length + data_length; buffer =(char *) calloc (full_length, sizeof (char)); // Генерация псевдозаголовка memcpy (buffer, &ph, header_length); memcpy (buffer + header_length, data, data_length); // Вычисление CRC. p_crc = rs_crc ((unsigned short*) buffer, full_length); free (buffer); return p_crc; }
Теперь когда мы имеем всю необходимую справочную информацию, можем приступать непосредственно к написанию нашего генератора пакетов.
Первым шагом будет инициализация библиотеки WinSock 2.2.
int rs_init (int v_major, int v_minor) { WSADATA wsadata; // Инициализация WinSock заданной версии if (WSAStartup(MAKEWORD(v_major, v_minor), &wsadata)) { ShowMessage(" Ошибка инициализаци WinSock"); return 1; } // Проверка версии WinSock if (LOBYTE(wsadata.wVersion) != v_minor || HIBYTE(wsadata.wVersion) != v_major) { rs_exit (); ShowMessage ("Неверная версия WinSock "); return 1; } return 0; }
Далее необходимо объявить структуры заголовков создаваемых пакетов. После этого производится заполнение заголовка IP пакета. На следующем шаге созданем RAW -сокет, и если нет вложений протоколов верхнего уровня в пакет IP , то отправляем его, используя следующую функцию:
int rs_send_ip (SOCKET s, struct ip_header iph, unsigned char * data, int data_length, unsigned short dst_port_raw) { char * buffer; int result; sockaddr_in target; unsigned char header_length; unsigned int packet_length; memset (&target, 0, sizeof (target)); target.sin_family = AF_INET; target.sin_addr.s_addr = iph.dst_addr; target.sin_port = dst_port_raw; // Вычисление длины и заголовка пакета header_length = sizeof (struct ip_header); packet_length = header_length + data_length; // Установка CRC. iph.crc = 0; // Заполнение некоторых полей заголовка IP . iph.version = header_length / 4 + (unsigned char) atoi ("4") * 16; // Если длина пакета не задана , то //длина пакета приравнивается к длине заголовка if (!iph.length) iph.length = htons (packet_length); buffer =(char *) calloc (packet_length, sizeof (char)); // Копирование заголовка пакета в буфер ( CRC равно 0). memcpy (buffer, &iph, sizeof (struct ip_header)); // Копирование данных в буфер if (data) memcpy (buffer + header_length, data, data_length); // Вычисление CRC. iph.crc = rs_crc ((unsigned short *) buffer, packet_length); // Копирование заголовка пакета в буфер ( CRC посчитана). memcpy (buffer, &iph, sizeof (struct ip_header)); // Отправка IP пакета в сеть. result = sendto (s, buffer, packet_length, 0,(struct sockaddr *) &target, sizeof (target)); free (buffer); return result; }
Если в IP пакет вложен пакет протокола более высокого уровня, то сначала вызываем функцию заполнения и отправки пакета протокола более высокого уровня, которая в свою очередь вызовет функцию генерации и отправки IP пакета в сеть, причем в качестве вложенных данных в IP пакет будет передан сформированный заголовок протокола более высокого уровня. Для генерации пакетов TCP , UDP и ICMP служат следующие функции, соответственно:
int rs_send_tcp (SOCKET s, struct ip_header iph, struct tcp_header tcph, unsigned char * data, int data_length) { char * buffer; int result; unsigned char header_length; unsigned int packet_length; // вычисление длин пакета и заголовка. header_length = sizeof (struct tcp_header); packet_length = header_length + data_length; // Установка CRC. tcph.crc = 0; // Установка поля offset . tcph.offset = (header_length / 4) << 4; buffer =(char *) calloc (packet_length, sizeof (char)); // Копирование заголовка пакета в буфер ( CRC равно 0). memcpy (buffer, &tcph, sizeof (struct tcp_header)); // Копирование протокола более высокого уровня (данных) if (data) memcpy (buffer + header_length, data, data_length); // Вычисление CRC. tcph.crc = rs_pseudo_crc (buffer, packet_length, iph.src_addr, iph.dst_addr, packet_length, IPPROTO_TCP); // Копирование заголовка пакета в буфер ( CRC посчитано). memcpy (buffer, &tcph, sizeof (struct tcp_header)); // Посылка IP пакета (в качестве данных передан заголовок TCP ) result = rs_send_ip (s, iph, buffer, packet_length, tcph.dst_port); free (buffer); return result; }
int rs_send_udp (SOCKET s, struct ip_header iph, struct udp_header udph, unsigned char * data, int data_length) { char * buffer; int result; unsigned char header_length; unsigned int packet_length; //вычисление длин пакета и заголовка. header_length = sizeof (struct udp_header); packet_length = header_length + data_length; // Установка CRC. udph.crc = 0; // Если длина пакета не задана , то //длина пакета приравнивается к длине заголовка if (!udph.length) udph.length = htons (packet_length); buffer =(char *) calloc (packet_length, sizeof (char)); // Копирование заголовка пакета в буфер ( CRC равно 0). memcpy (buffer, &udph, sizeof (struct udp_header)); // Копирование протокола более высокого уровня (данных) if (data) memcpy (buffer + header_length, data, data_length); // Вычисление CRC. udph.crc = rs_pseudo_crc (buffer, packet_length, iph.src_addr, iph.dst_addr, packet_length, IPPROTO_UDP); // Копирование заголовка пакета в буфер ( CRC посчитана). memcpy (buffer, &udph, sizeof (struct udp_header)); // Отправка IP пакета со вложенным UDP пакетом. result = rs_send_ip (s, iph, buffer, packet_length, udph.dst_port); free (buffer); return result; } int rs_send_icmp (SOCKET s, struct ip_header iph, struct icmp_header icmph, unsigned char * data, int data_length) { char * buffer; int result; unsigned char header_length; unsigned int packet_length; data_length = 0; // вычисление длин пакета и заголовка. header_length = sizeof (struct icmp_header); packet_length = header_length + data_length; icmph.crc = 0; buffer = (char *)calloc (packet_length, sizeof (char)); // Копирование заголовка пакета в буфер ( CRC равно 0). memcpy (buffer, &icmph, sizeof (struct icmp_header)); // Вычисление CRC. icmph.crc = rs_crc ((unsigned short *) buffer, packet_length); // Копирование заголовка пакета в буфер ( CRC посчитана). memcpy (buffer, &icmph, sizeof (struct icmp_header)); // Отправка IP пакета со вложенным ICMP пакетом. result = rs_send_ip (s, iph, buffer, packet_length, 0); free (buffer); return result; }
В итоге написан полноценный генератор пакетов на чистых сокетах без использования никаких дополнительных библиотек, например, таких, как WinPcap . Однако стоит сказать, что для работы с RAW -сокетами, также как и для работы с WinPcap необходимы права администратора.
Полнeую версию рассмотренного генератора пакетов с исходным кодом можно скачать здесь (zip-архив 19 Kбайт). Данная программа написана на C ++ Builder 6, однако никто не запрещает переделать ее под любой другой компилятор.
В данной статье мы рассмотрели одну из возможностей RAW -сокетов – генерация пакетов различной структуры. В следующей статье попытаемся рассмотреть еще одну очень интересную возможность низкоуровневых сокетов – это перехват всех пакетов с сетевой карты.
О всех замеченных недостатках и ошибках, а также свои вопросы и пожелания пишите автору.