Генератор пакетов на RAW-сокетах в Windows 2000/XP.

Фундаментальной единицей всего сетевого программирования практически во всех операционных системах является сокет. Так же, как функции файлового ввода-вывода определяют интерфейс взаимодействия с файловой системой, сокет соединяет программу с сетью. Существует несколько типов сокетов, однако чаще всего встречаются следующие три типа:

Первые два типа сокетов чаще всего применяются на практике. По работе с ними написано очень много статей и книг, поэтому на них подробно мы останавливаться не будем. В данной будет рассмотрено программирование именно третьего вида сокетов (низкоуровневых сокетов). Иногда бывает необходимо создать пакет, неважно какой – 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 -сокетов – генерация пакетов различной структуры. В следующей статье попытаемся рассмотреть еще одну очень интересную возможность низкоуровневых сокетов – это перехват всех пакетов с сетевой карты.

О всех замеченных недостатках и ошибках, а также свои вопросы и пожелания пишите автору.