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



