Linux windows описание. Struct ICMP_header { struct ICMP{
Struct ICMP_header { struct ICMP{
type : 8 ; byte ICMP_TYPE; тип ошибки
code : 8 ; byte ICMP_code; код ошибки
checksum : 16 ; short ICMP_CKSUM; контрольная сумма
id:16; short ICMP_id; идентификатор ICMP-пакета
msg []; long ICMP_seg; данные(дополнительное описание ошибки)
Таблица 1. – Коды ICMP
Тип | Код | Описание |
Эхо-ответ | ||
Сеть недоступна | ||
Узел недоступен | ||
Протокол недоступен | ||
Порт недоступен | ||
Указанная сеть неизвестна | ||
Указанный узел неизвестен | ||
Доступ к указанной сети запрещен | ||
Доступ к указанному узлу запрещен | ||
Эхо-запрос | ||
Информационный запрос | ||
Ответ на информационный запрос | ||
Запрос адресной маски | ||
Ответ на запрос адресной маски |
Различная комбинация значений типа и кода ошибки позволяют получать большое количество ICMP-запросов и ICMP-ответов. В то же время поле данных позволяет передавать в ICMP-сообщениях информацию о номерах открытых портов, маске подсетей и так далее. Возможные множества значений полей кода и типа ошибки представлены в таблице 1.
В соответствии с порядком движения информации по уровням эталонной модели OSI, протоколы ICMP и UDP, рассматриваемые в этой лабораторной работе, размещают свои заголовки и данные в разделе данные IP-пакета . Так как разработчик непосредственно сам формирует пакеты, ему должен быть представлен доступ к нижним уровням иерархии модели OSI. В этом случае сокет, который будет обеспечивать передачу данных, должен создаваться совершенно с другими значениями параметров, чем это было ранее(лабораторная работа №1). Параметр “type” задающий тип сокета, должен быть проинициализирован значением “Sock-RAW”, который предполагает передачу низкоуровневых данных без подтверждения доставки. Параметр “protokol” инициализируется значением номера протокола, пакета которого размещается в IP-пакете. Однако для этого указанный номер извлекается из соответствующей компоненты встроенной в WinSock структуры типа protoent, куда непосредственно он должен быть занесен с использованием функции getproto by name (“имя_протокола”), результатом выполнения которой как раз и является указатель на структуру типа protoent. Таким образом, общий синтаксис создания сокета для передачи низкоуровневых пакетов имеет вид:
Struct protoent*proto;
int socket;
proto= getprotobyname(“ICMP”);
socket= socket(AF_INET, SOCK_RAW proto -> P_proto);
При “ручном” создании пакетов никакого установления соединения между хостами в сети не требуется. Данные выставляются в сеть и пересылаются протоколами нижних уровней на сторону получателя. Поэтому в библиотеке для передачи сформированных пакетов предусмотрена функция sendto, позволяющая предварительно не создавать соединение. Ее формат имеет следующий вид:
int sendto (socket,buffer,buf_length,option , addr, addr_length),
где параметры socket, buffer, buf_length соответствуют функции send, параметр options может быть задан нулевым, параметр addr является структурой (см. лаб. раб. №1), компоненты которой определяют адрес хоста назначения и номер порта для обмена, параметр add_length определяет длину адресной структуры.
Аналогично прием пакетов без установления соединения осуществляется посредством вызова функции recfrom с указанием в ней вышеупомянутой адресной структуры. Формат вызова функции recfrom следующий:
int recfrom (socket,buffer,buf_length, options,addr,addr_length);
Таким образом сформированные ICMP и UDP пакеты при вызове функции sendto будет автоматически размещаться в заголовке IP-пакета (т.е. формировать IP-пакет вручную не нужно). Однако на приемной стороне записанная в символьный буфер (с помощью функции recfrom) датаграмма может быть приведена к типу strukt IP для последующего исследования ее компонент. Например:
char buf [];
recfrom (socket, buf, len_buf, 0, addr, len_addr );
IP=(struct IP*) buf;
printf (ntohl (IP->IP_SRC));
Примерный вид программы, осуществляющий обмен данными на серверной и клиентской сторонах следующий:
# include <winsock2.h>
main() {
struct sockaddr_in addr;
struct IP *IP;
struct icmp*ICMP;
struct protoent *proto;
int socket_1;
/* инициализация компоненты структуры addr */
proto=getproto byname (“ICMP”);
socket_1=socket(AF_Inet, Sock_RAW, PROTO-> P_PROTO);
ICMP->ICMP_TYPE=8;
ICMP->ICMP_CODE=0;
/* размешение структуры ICMP в буфер */
sendto (socket_1, buf, len_buf, 0 , addr, len_addr);
}
# include <winsock2.h>
main() {
struct sockaddr_in addr;
struct IP*IP;
struct ICMP*ICMP;
struct protoent *proto;
int socket_2;
char buf[]; int hl;
/* инициализация компонент структуры addr */
PROTO= getproto byname (“ICMP”);
Socket_2=socket(AF_INET, SOCK_RAW,PROTO->P_PROTO);
Recfom (SOCKET_2, buf, len_buf, 0, addr, len_addr);
IP=(Struct IP*) buf;
PRINT(inet_NtoA(IP->IP_SRC)Inet_NtoA (IP->IP_DST));
hl=IP->IP_hl;
icmp=(struct ICMP*) (buf+hl);
printf (ICMP->ICMP_code, ICMP_type);
}
ПРИМЕР
//------------------------------------------------------------
#pragma hdrstop
//------------------------------------------------------------
#include <winsock2.h>
#include <iostream>
using namespace std;
#include "ws2tcpip.h"
#pragma argsused
#define DEFAULT_PACKET_SIZE 32
#define DEFAULT_TTL 30
#define MAX_PING_DATA_SIZE 1024
#define MAX_PING_PACKET_SIZE (MAX_PING_DATA_SIZE + sizeof(IPHeader))
// типы ICMP пакетов
#define ICMP_ECHO_REPLY 0
#define ICMP_DEST_UNREACH 3
#define ICMP_TTL_EXPIRE 11
#define ICMP_ECHO_REQUEST 8
// минимальный размер ICMP пакетов в байтах
#define ICMP_MIN 8
struct IPHeader {
BYTE h_len:4; // Length of the header in dwords
BYTE version:4; // Version of IP
BYTE tos; // Type of service
USHORT total_len; // Length of the packet in dwords
USHORT ident; // unique identifier
USHORT flags; // Flags
BYTE ttl; // Time to live
BYTE proto; // Protocol number (TCP, UDP etc)
USHORT checksum; // IP checksum
ULONG source_ip;
ULONG dest_ip;
};
// ICMP заголовок
struct ICMPHeader {
BYTE type; // ICMP packet type
BYTE code; // Type sub code
USHORT checksum;
USHORT id;
USHORT seq;
ULONG timestamp; // not part of ICMP, but we need it
};
USHORT ip_checksum(USHORT* buffer, int size);
int main(int argc, char* argv[])
{
char *host="10.9.101.137";
// инициализация переменных
int seq_no = 0;
ICMPHeader* send_buf=0;
IPHeader* recv_buf = 0;
int ttl = DEFAULT_TTL;
// выбираем размер пакета - или размер структуры ICMPHeader или размер пакета по умолчанию
int packet_size = DEFAULT_PACKET_SIZE;
// packet_size = max(sizeof(ICMPHeader),min(MAX_PING_DATA_SIZE, (unsigned int)packet_size));
packet_size=1024;
// выделяем память под заколовок ICMP пакета
send_buf = (ICMPHeader*)new char[packet_size];
recv_buf = (IPHeader*)new char[MAX_PING_PACKET_SIZE];
// запуск Winsock
WSAData wsaData;
if (WSAStartup(MAKEWORD(2, 1), &wsaData) != 0) {
cerr << "Failed to find Winsock 2.1 or better." << endl;
return 1;
}
// объявление сокетов и структур для отправки
SOCKET sd;
sockaddr_in dest, source;
// создаём сокет
sd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sd == INVALID_SOCKET)
{
cerr << "Failed to create raw socket: " << WSAGetLastError() <<endl;
}
int t=500;
if (setsockopt(sd,SOL_SOCKET,SO_RCVTIMEO,(const char*)&t,sizeof(t)) == SOCKET_ERROR)
{
cerr << "Recieve timout setsockopt failed: " << WSAGetLastError() << endl;
return -1;
}
// setsockopt ///////////////////////////////
// инициализация структуры с информацией о хосте назначения
memset(&dest, 0, sizeof(dest));
// преобразования адреса хоста для структуры назначения
unsigned int addr = inet_addr(host);
if (addr != INADDR_NONE)
{
dest.sin_addr.s_addr = addr;
dest.sin_family = AF_INET;
}
// заполнение полей ICMP пакеты для отправки
send_buf->type = ICMP_ECHO_REQUEST;
send_buf->code = 0;
send_buf->checksum = 0;
send_buf->id = (USHORT)GetCurrentProcessId();
send_buf->seq = seq_no;
send_buf->timestamp = GetTickCount();
send_buf->checksum = ip_checksum((USHORT*)send_buf, packet_size);
// отправка пинг пакета
cout << "Sending " << packet_size << " bytes to " <<
inet_ntoa(dest.sin_addr) << "..." << flush;
int bwrote = sendto(sd, (char*)send_buf, packet_size, 0,
(sockaddr*)&dest, sizeof(dest));
if (bwrote == SOCKET_ERROR) {
cerr << "send failed: " << WSAGetLastError() << endl;
return -1;
}
else if (bwrote < packet_size) {
cout << "sent " << bwrote << " bytes..." << flush;
}
int nom=0;
bool good=true; // параметр для определения был ли приём успешным
while (nom<3)
{
// принимаем пакеты, пока не будет успеха или тотальной ошибки
int fromlen = sizeof(source);
int bread = recvfrom(sd, (char*)recv_buf,
packet_size + sizeof(IPHeader), 0,
(sockaddr*)&source, &fromlen);
if (bread == SOCKET_ERROR)
{
cerr << "read failed: ";
if (WSAGetLastError() == WSAEMSGSIZE)
{
cerr << "buffer too small" << endl;
}
else
{
if (WSAGetLastError()==10060)
{
cerr<<endl<< "Request timed out." << endl;
return -1;
}
else
{
cerr<<"error #"<<WSAGetLastError()<<endl;
return -1;
}
}
good=false; // были ошибки - приём не удался, пакет разбирать не будем
}
else good=true;
if (good) // если приём прошёл успешно, начинаем разбирать пакет
{
// вытаскиваем порядковый номер из ICMP заголовка
// принятого пакета. Сравниваем его, если он не равен
// порядковому номеру присвоенному при отправки,
// Значит была ошибка чтения
unsigned short header_len = recv_buf->h_len * 4;
ICMPHeader* icmphdr = (ICMPHeader*)
((char*)recv_buf + header_len);
if (icmphdr->seq != seq_no) {
cerr << "bad sequence number!" << endl;
continue;
}
// проверяем правильным ли был ответ
if (packet_size < header_len + ICMP_MIN) {
cerr << "too few bytes from " << inet_ntoa(source.sin_addr) <<
endl;
return -1;
}
else if (icmphdr->type != ICMP_ECHO_REPLY) {
if (icmphdr->type != ICMP_TTL_EXPIRE) {
if (icmphdr->type == ICMP_DEST_UNREACH) {
cerr << "Destination unreachable" << endl;
}
else {
cerr << "Unknown ICMP packet type " << int(icmphdr->type) <<
" received" << endl;
}
return -1;
}
// If "TTL expired", fall through. Next test will fail if we
// try it, so we need a way past it.
}
else if (icmphdr->id != (USHORT)GetCurrentProcessId()) {
// должно быть получен ответ от другого пинга запущенного локально
// игнорируем ответ
return -2;
}
// подсчитываем, как долго был пакет в пути
int nHops = int(256 - recv_buf->ttl);
if (nHops == 192) {
// TTL came back 64, so ping was probably to a host on the
// LAN -- call it a single hop.
nHops = 1;
}
else if (nHops == 128) {
// Probably localhost
nHops = 0;
}
// Okay, we ran the gamut, so the packet must be legal -- dump it
cout << endl << packet_size << " bytes from " <<
inet_ntoa(source.sin_addr) << ", icmp_seq " <<
icmphdr->seq << ", ";
if (icmphdr->type == ICMP_TTL_EXPIRE) {
cout << "TTL expired." << endl;
}
else {
cout << nHops << " hop" << (nHops == 1 ? "" : "s");
cout << ", time: " << (GetTickCount() - icmphdr->timestamp) <<
" ms." << endl;
}
return 0;
}
if (!good)
nom++;
}
cin.get();
return 0;
}
//------------------------------------------------------------
USHORT ip_checksum(USHORT* buffer, int size)
{
unsigned long cksum = 0;
// Sum all the words together, adding the final byte if size is odd
while (size > 1) {
cksum += *buffer++;
size -= sizeof(USHORT);
}
if (size) {
cksum += *(UCHAR*)buffer;
}
// Do a little shuffling
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >> 16);
// Return the bitwise complement of the resulting mishmash
return (USHORT)(~cksum);
}
Пример программы осуществляющей посылку ICMP пакета “эхо-запроса” на некоторый хост и получение ответа с этого хоста представлен в приложении А.
Аналогичным образом можно получить и разобрать на компоненты TCP-пакет. Для чего, первоначально, полученный буфер приводится к типу IP пакета, а затем, из него достается структура заголовка TCP пакета, которая для платформ Windows имеет следующий вид:
struct TCP
{
WORD SrcPort; //порт отправителя
WORD DstPort; //порт получателя
DWORD SeqNum; //последовательный номер
DWORD AckNum; //поле содержащее следующий SeqNum
BYTE DataOff; //Поле величины смещения данных
BYTE Flags; //flags (fin,syn,ack,psh,urg...)
WORD Window; //максимальное кол-во пересылаемых байт
WORD Chksum; //проверочная сумма пакета
WORD UrgPtr; //используется для пересылки критических данных
};
Для того, чтобы получить подобный пакет, необходимо выбрать текущий сетевой адаптер, воспользовавшись функцией GetAdaptersInfo , входящей в стандартную библиотеку Windows IPHlpAPI
DWORD GetAdaptersInfo (PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen );где pAdapterInfo – указатель на структуру _IP_ADAPTER_INFO ( модуль IPHlpAPI.h); pOutBufLen – указатель на тип unsigned long.
Структура _IP_ADAPTER_INFO определена в модуле IPHlpAPI.h следующим образом :
typedef struct _IP_ADAPTER_INFO { struct _IP_ADAPTER_INFO *Next; DWORD ComboIndex; char AdapterName[MAX_ADAPTER_NAME_LENGTH + 4]; char Description[MAX_ADAPTER_DESCRIPTION_LENGTH + 4]; UINT AddressLength; BYTE Address[MAX_ADAPTER_ADDRESS_LENGTH]; DWORD Index; UINT Type; UINT DhcpEnabled; PIP_ADDR_STRING CurrentIpAddress; IP_ADDR_STRING IpAddressList; IP_ADDR_STRING GatewayList; IP_ADDR_STRING DhcpServer; BOOL HaveWins; IP_ADDR_STRING PrimaryWinsServer; IP_ADDR_STRING SecondaryWinsServer; time_t LeaseObtained; time_t LeaseExpires; } IP_ADAPTER_INFO, *PIP_ADAPTER_INFO;В ней потребуются следующие поля: Description – описание адаптера в виде, привычном для пользователя, и представляет собой указатель на тип char ; IpAddressList – список IP -адресов, закрепленных за интерфейсом, и соответствующих им сетевых масок. Представляет собой тип IP_ADDR_STRING ; Next – указатель на следующий элемент списка адаптеров.
Требуется описание еще одной структуры - IP_ADDR_STRING . Оно также приведено в модуле IPHlpAPI . h выглядит следующим образом:
typedef struct _IP_ADDR_STRING { struct _IP_ADDR_STRING *Next; IP_ADDRESS_STRING IpAddress; IP_MASK_STRING IpMask; DWORD Context ; } IP_ADDR_STRING , * PIP_ADDR_STRING ;В этой структуре также потребуются не все поля, а лишь два из них: IPAddress – содержит текущий IP -адрес интерфейса. Текущий потому, что интерфейсу может быть поставлено в соответствие несколько IP -адресов, которые могут меняться при динамическом назначении адреса. IpMask – содержит сетевую маску, соответствующую текущему адресу.
Таким образом, текст функции, выбирающей текущий интерфейс, будет выглядеть так:
#include "iptypes.h"
void main()
{
u_long LocalAddrs[10]; //объявление массива, в котором будет хранится наш ip-адрес
u_long LocalMasks[10]; //объявление массива, в котором будет хранится маска
HINSTANCE iphlpapi_dll;
//объявляем указатель на функцию с данными параметрами:
//где pAdapterInfo - указатель на структуру _IP_ADAPTER_INFO ( модуль IPHlpAPI.h);
//pOutBufLen - указатель на тип unsigned long;
//функция для полуения информации о всех интерфейсах и установки указателя pAdapterInfo
DWORD (__stdcall * GetAdaptersInfo)(PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen);
int main(int argc, char* argv[])
{
iphlpapi_dll = LoadLibrary ("iphlpapi.dll"); //подгружаем библиотеку iphlpapi.dll
GetAdaptersInfo = (DWORD (__stdcall *) (PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen))
GetProcAddress (iphlpapi_dll,"GetAdaptersInfo"); //устанавливаем //указатель на функцию GetAdapterInfo, расположенное в iphlpapi.dll
PIP_ADAPTER_INFO pAdapterInfo, pAdapt;
DWORD AdapterInfoSize; //длина информации об адаптере
DWORD Err; //код ошибки
int cnt=0;
sockaddr_in saddr;
AdapterInfoSize = 0;
GetAdaptersInfo(NULL, &AdapterInfoSize); //вызываем GetAdaptersInfo с нулевыми параметрами (инициализируем)
//выделяем память под структуру pAdaptersinfo и установив указатель обнуляем память
pAdapterInfo = (PIP_ADAPTER_INFO) GlobalAlloc(GPTR, AdapterInfoSize);
if (pAdapterInfo == NULL)
{
printf("Error in memory allocation.");
return -1;
}
if ((GetAdaptersInfo(pAdapterInfo, &AdapterInfoSize))!=0) //получаем список устройств
{
printf("Error in function call GetAdaptersInfo()");
return -1;
}
pAdapt = pAdapterInfo; //устанавливаем pAdapt на начало списка устройств
while (pAdapt)
{
printf("Found interfaces:\n\n");
printf("%s", pAdapt->Description); //описание интерфейса
printf("IP-address: %s", pAdapt->IpAddressList.IpAddress.String); //вывод на экран IP интерфейса
printf("NetMask: %s\n", pAdapt->IpAddressList.IpMask.String); //вывод на экран маски интерфейса
LocalAddrs[cnt] = inet_addr(pAdapt->IpAddressList.IpAddress.String); //запоминаем IP интерфейса в массив LocalAddrs
LocalMasks[cnt] = inet_addr(pAdapt->IpAddressList.IpMask.String); //запоминаем маску интерфейса в массив LocalMask
pAdapt = pAdapt->Next; //перевод на след.устройство
cnt++;//увеличиваем индекс массивов
}
}
Затем необходимо создать сокет с соответствующими параметрами
SOCKET socket (AF_INET,SOCK_RAW,IPPROTO_IP);
и выполнить привязку этого сокета к соответсвующему интерфейсу
ZeroMemory(&saddr,sizeof(saddr));// обнуляем переменную saddr
saddr.sin_family = AF_INET ;// задаем семейство адресов
saddr.sin_addr.S_un.S_addr = LocalAddrs [0];// адрес выбранного интерфейса, предыдущей //функцией
//вместо 0 может быть другой индекс - в зависимости от того, через какой
//адаптер хотим принимать трафик
bind(sock,(SOCKADDR*)&saddr,sizeof(SOCKADDR));// привязываем сокет к сетевой карте
//выбор интерфейса, создание и привязка сокета к выбранному адаптеру
И затем перевести сокет в неразборчивый режим работы
ULONG flags = 1; //привязка сокета к интерфейсу и перевод сетевой карты
ioctlsocket(sock,SIO_RCVALL,&flags); //в неразборчивый режим работы
где постоянная SIO_RCVALL, задание которой необходимо для перевода адаптера в неразборчевый режим приема пакетов, равна 0x98000001 и должна объявляться как
#define SIO_RCVALL 0x98000001
После окончания работы программы необходимо обязательно перевести адаптер в нормальный режим работы командой
flags = 0;
ioctlsocket ( sock , SIO_RCVALL ,& flags );//отключаем неразборчивый режим работы //адаптера
Так как нам нет необходимости получать пакет от какого-то определенного хоста мы можем воспользоваться функцией recv, рассматриваемую в предыдущей лабораторной работе, которая примет следующий вид:
int size = recv (sock,buf,1600,0)
где buf – это переменная типа char[1600], 1600 – размер буфера. А переменная size будет равна полученному количеству байт.
3. ВАРИАНТЫ ЗАДАНИЙ
В силу того, что операционная система Windows98 в отличие от Windows 2000/XP поддерживает RAW SOCKET не в полном объеме, она не передает TCP пакеты на уровень доступный программисту. Поэтому для получения пакетов необходимо воспользоваться программой send.exe, передавая ей в качестве параметра ip-адрес того компьютера, на котором вы хотите получить пакет.
Программа send.exe формирует 10 ip-пакетов, которые содержат в качестве данных структуру полностью, соответствующую заголовку tcp пакета, то есть программа, реализующая получение такого пакета ни чем не должна отличаться от программ, выполняющих подобные операции на операционных системах Windows 2000/XP.
Вариант 1.
Реализовать приложение, осуществляющее посылку ICMP пакета “эхо-запроса” на некоторый хост и получение ответа с этого хоста.
Вариант 2.
Реализовать «клиент-серверное» приложение, осуществляющее, посредством RAW SOCKET, пересылку данных, используя UDP протокол.
Вариант 3.
Реализовать программу, получающую и разбирающую TCP пакет, посланный с другого хоста. Вывести на экран порты отправителя, получателя, значение поля flags TCP заголовка.
4. СОДЕРЖАНИЕ ОТЧЁТА
4.1. Цель работы;
4.2. Вариант задания;
4.3. Текст разработанной программы.
4.4. Распечатка окон разработанной программы, демонстрирующих ее работу.
4.5. Выводы.
5. КОНТРОЛЬНЫЕ ВОПРОСЫ
5.1. Изобразить форматы пакетов основных сетевых протоколов.
5.2. Записать компоненты структур, определяющих пакеты сетевых протоколов.
5.3. Объяснить назначение и создание «сырых» сокетов.
5.4. Каковы способы передачи и получения данных при работе с «сырыми» сокетами.
ЛАБОРАТОРНАЯ РАБОТА №3
“Исследование возможностей нестандартного использования протокола ARP.”
1. ЦЕЛЬ РАБОТЫ
Изучить работу ARP протокола. Выяснить его недостатки и преимущества. Получить практические навыки написания программ для работы с ARP таблицей с помощью библиотеки WinPcap.
2. ОСНОВНЫЕ ПОЛОЖЕНИЯ
2.1. Ложный ARP-сервер в сети Internet (атака типа Man-in-the-Middle)
Анализ безопасности протокола ARP показывает, что, перехватив на атакующем хосте внутри данного сегмента сети широковещательный ARP-запрос, можно послать ложный ARP-ответ, в котором объявить себя искомым хостом (например, маршрутизатором), и в дальнейшем активно контролировать сетевой трафик дезинформированного хоста, воздействуя на него по схеме "ложный объект РВС". Этапы атаки:
1. Ожидание ARP-запроса.
2. При получении такого запроса - передача по сети на запросивший хост ложного ARP-ответа, где указывается адрес сетевого адаптера атакующей станции (ложного ARP-сервера) или тот Ethernet-адрес, на котором будет принимать пакеты ложный ARP-сервер. Совершенно необязательно указывать в ложном ARP-ответе свой настоящий Ethernet-адрес, так как при работе непосредственно с сетевым адаптером его можно запрограммировать на прием пакетов на любой Ethernet-адрес.
3. Прием, анализ, воздействие на пакеты обмена и передача их между взаимодействующими хостами.
Так как поисковый ARP-запрос кроме атакующего получит и маршрутизатор, то в его таблице окажется соответствующая запись об IP- и Ethernet-адресе атакуемого хоста. Следовательно, когда на маршрутизатор придет пакет, направленный на IP-адрес атакуемого хоста, он будет передан не на ложный ARP-сервер, а непосредственно на хост. При этом схема передачи пакетов в этом случае будет следующая:
1. Атакованный хост передает пакеты на ложный ARP-сервер.
2. Ложный ARP-сервер посылает принятые от атакованного хоста пакеты на маршрутизатор.
3. Маршрутизатор, в случае получения ответа на запрос, адресует его непосредственно на атакованный хост, минуя ложный ARP-сервер.
В этом случае последняя фаза, связанная с приемом, анализом, воздействием на пакеты обмена и передачей их между атакованным хостом и, например, маршрутизатором (или любым другим хостом в том же сегменте) будет проходить уже не в режиме полного перехвата пакетов ложным сервером (мостовая схема), а в режиме "полуперехвата" (петлевая схема). Действительно, в режиме полного перехвата маршрут всех пакетов, отправляемых как в одну, так и в другую сторону, обязательно проходит через ложный сервер (мост); в режиме "полуперехвата" маршрут пакетов образует петлю (рисунок 1).
Рисунок 1. - Петлевая схема перехвата информации ложным ARP-сервером
2.2. ARP атака
Как еще можно нестандартно использовать ARP протокол? Ответ прост - дело в том, что большинство операционных систем ARP-ответ заносят сразу же без проверки (посылала ли система запрос) в ARP таблицу (исключением является Solaris, который игнорирует ARP ответы, если не посылался ARP запрос).
Чем грозит такая ситуация? Если интерфейс получит данные о том, что какому-то IP соответствует MAC, который на самом деле не существует, то IP-датаграммы к данному хосту будут вкладываться в кадр с фальшивым MAC. Это приведет к тому, что ни один сетевой интерфейс в локальной сети не будет воспринимать этот пакет. Таким образом, все данные уйдут в "никуда". Для реализации подобной атаки необходимо периодически (интервал зависит от операционной системы) посылать ложные ARP ответы. В результате атакуемый хост не сможет установить соединение с хостом, IP адрес которого указан в ложном ARP ответе. Данная атака применима даже если уже установлено соединение между двумя хостами. После посылки даже одного ложного ARP ответа соединение будет разорвано по таймауту. Для того чтобы два хоста не смогли обмениваться пакетами друг с другом, необходимо направить ложные ARP ответы на один из хостов. Если же ставить целью полностью отключить хост от сети, то необходимо периодически посылать ложные ARP ответы от всех хостов в сети. Тогда на атакуемом хосте сложится впечатление, что поврежден кабель или вышла из строя сетевая карта. Однако это впечатление легко рассеять, достаточно лишь запустить сниффер и убедиться что интерфейс работает, и пакеты отправляются и получаются.
Демострация атаки:
На атакуемом хосте запускаем ping с ключом -t (до прерывания пользователем). Через некоторое время посылается ложный ARP-ответ, а затем ARP ответ с правильным MAC-адресом. Посылать ARP-ответ можно либо с помощью сниффера NetXRay, либо специально написанными для этого программами. Вот что будет результатом:
>ping -t 10.0.0.1
Обмен пакетами с 10.0.0.1 по 32 байт:
Ответ от 10.0.0.1: число байт=32 время<10мс TTL=128
Ответ от 10.0.0.1: число байт=32 время<10мс TTL=128
Ответ от 10.0.0.1: число байт=32 время<10мс TTL=128
Ответ от 10.0.0.1: число байт=32 время<10мс TTL=128
Превышен интервал ожидания для запроса.
Превышен интервал ожидания для запроса.
Превышен интервал ожидания для запроса.
Превышен интервал ожидания для запроса.
Ответ от 10.0.0.1: число байт=32 время<10мс TTL=128
Ответ от 10.0.0.1: число байт=32 время<10мс TTL=128
Ответ от 10.0.0.1: число байт=32 время<10мс TTL=128
Ответ от 10.0.0.1: число байт=32 время<10мс TTL=128
Как видим, возникает таймаут посланных пакетов. А какая будет ситуация, если послать только один ARP-ответ с несуществующим MAC в данном случае? В этом случае хост не сможет установить соединение с хостом с IP-адресом 10.0.0.1 до тех пор пока не будет прервано выполнение команды ping. Дело в том, что при попытке послать данные другим приложением по этому же IP-адресу, в кеше ARP-таблицы будет найдено соответствие MAC и IP (повторяющаяся команда ping не дает этим данным устареть). Если же мы прекратим пинговать хост, то через некоторое время (для многих систем 35-40 секунд) элемент ARP-таблицы будет удален из нее, и при последующих попытках какого-нибудь приложения установить соединение или послать датаграмму к хосту, будет послан ARP-ответ, и в таблицу соответствия MAC и IP адреса занесутся верные данные.
Как защитить себя от данной атаки? Необходимо статически прописать элементы в ARP таблице. Это не совсем удобно (учитывая то, что в локальной сети может быть много хостов), но тогда можно быть уверенным, что данная атака на ваш хост не принесет желаемого результата для атакующего. Однако определить какой именно хост является атакующим не представляется возможным.
2.3. Структура ARP пакета
Каждому устройству в сети Ethernet соответствует уникальный шестибайтовый MAC-адрес. Единицей передачи данных в такой сети является кадр, который имеет определённую структуру и несёт в себе информацию о получателе, отправителе и сами данные.
struct ETHERNET_FRAME
{
unsigned char dest[6];// MAC-адрес получателя
unsigned char src[6]; // MAC-адрес отправителя
unsigned short type; // версия: IPv4 0x0800, IPv6 0x86DD, ARP 0x0806
unsigned char data[]; // данные
};
Кадр может иметь размер от 60 до 1514 байт, из которых первые 14 байт являются служебными. Когда требуется передать большее количество данных, они разбиваются на фрагменты и последовательно направляются в сеть. Кадр передаётся по сети, и получает его каждое устройство этой сети. Значение поля структуры с именем type определяет тип и версию "полезного груза" в кадре. Завершающая секция кадра служит для проверки целостности передаваемых данных и использует код циклического контроля (CRC32 - cyclic redundancy check). Это чрезвычайно мощная хэш-функция для выявления искажённости числовых данных. Обычно она аппаратно реализована в сетевой плате.
Теперь обратим внимание на полезный груз, который несёт кадр, а именно поле структуры data[]. Чаще всего в качестве данных может быть IP-пакет или ARP-пакет. ARP (address resolution protocol) - это служебный вспомогательный протокол, который осуществляет динамическую трансляцию физических MAC-адресов в логические IP-адреса на основе широковещательной рассылки запросов.
struct ETHERNET_ARP
{
unsigned short hrd; // Тип аппаратуры (Ethernet), 0x0001.
unsigned short pro; // Протокол (IP), 0x0800.
unsigned char hln; // Длина аппаратного адреса (MAC), 6 байт.
unsigned char pln; // Длина адреса протокола IP, 4 байта.
unsigned short op; // Вид операции {Запрос, Ответ} = {1, 2}.
unsigned char sha[6];// Аппаратный адрес (MAC) отправителя.
unsigned char spa[4];// IP-адрес отправителя.
unsigned char tha[6];// Аппаратный адрес (MAC) получателя.
unsigned char tpa[4];// IP-адрес получателя.
};
Пакет состоит из заголовка, служебной информации (options) и данных. На языке С этот заголовок выглядит в виде вот такой структуры:
typedef struct _IPHeader
{
unsigned char verlen; // версия и длина заголовка
unsigned char tos; // тип сервиса
unsigned short length; // длина всего пакета
unsigned short id; // Id
unsigned short offset; // флаги и смещения
unsigned char ttl; // время жизни
unsigned char protocol; // протокол
unsigned short xsum; // контрольная сумма
unsigned long src; // IP-адрес отправителя
unsigned long dest; // IP-адрес назначения
}IPHeader;
Подробно IP протокол описан в RFC за номером 791. Для нас особый интерес представляют поля заголовка protocol, src и dest. Два последних поля - это хорошо известные IP-адреса отправителя и получателя пакета. Например, шестнадцатеричное значение адреса 0x0000140A соответствует 10.20.0.0.
2.4. Алгоритм программы для прослушивания сети
1. Определение активного сетевого адаптера.
2. Перевод сетевого адаптера в неразборчивый режим (т.е. в режим приема всех пакетов идущих от всех хостов).
3. Формирование фильтра для приема только нужных пакетов.
4. Формирование буфера или файла, в который будут сохранятся приходящие отфильтрованные пакеты.
2.5. Архитектура захвата пакетов для Windows WinPCAP
2.5.1. Библиотека захвата пакетов LIBPCAP
pcap_loop (pcap_t *p, int cnt,
Действия данной функции заключаются в том, что она считывает пакеты до тех пор, пока не обнулится счетчик cnt или не возникнет ошибка, и не прекращает работы при окончании времени ожидания. Отрицательное значение cnt заставит функцию работать бесконечно, до возникновения первой ошибки.
U_CHAR pcap_next (pcap_t *p, struct bpf_program *fp)
Функция возвращает указатель на следующий принятый пакет.
INT pcap_major_version (pcap_t *p)
Функция возвращает старшее число номера версии PCAP, записываемого в файл.
INT pcap_minor_version (pcap_t *p)
Функция возвращает младшее число номера версии PCAP, записываемого в файл.
INT pcap_stats (pcap_t *p, struct pcap_stat *ps)
Функция возвращает 0 и заполняет структуру pcap_stat значениями, которые несут различную статистическую информацию о входящих пакетах с момента запуска процесса до момента вызова этой функции. При возникновении какой-либо ошибки, а также в случае, когда используемый драйвер не поддерживает статистический режим, функция возвращает значение “–1”. При этом код описание ошибки можно получить с помощью функций pcap_perror() или pcap_geterr().
VOID pcap_perror (pcap_t *p, char *prefix)
Функция выводит текст последней возникшей ошибки библиотеки PCAP на устройстве stderr с префиксом, определяемым переменной prefix.
CHAR *pcap_geterr (pcap_t *p)
Функция возвращает строку с описанием последней ошибки библиотеки PCAP.
CHAR *pcap_strerror (int error)
Функция используется в том случае, когда strerror по каким-либо причинам недоступно.
VOID pcap_close (pcap_t *p)
Функция закрывает файл, связанный с адаптером p, и высвобождает занимаемые библиотекой ресурсы.
2.5.2. Библиотека PACKET.DLL
UINT bh_datalen – реальная длина захваченного пакета;
USHORT bh_hdrlen – размер структуры bpf_hdr.
Структура bpf_stat используется для получения статистической информации о текущей сессии:
UINT bs_recv – число пакетов, принятых адаптером с момента начала
сессии;
ULONG PacketGetAdapterNames (PTSTR pStr, PULONG BufferSize) – предназначена для получения информации об адаптерах, установленных в системе.Функция опрашивает регистр ОС, производит OID-вызовы драйвера пакетов и записывает имена установленных сетевых адаптеров и их описание в заданный пользователем буфер pStr. BufferSize – размер этого буфера. Формат данных, записываемых в буфер, отличен для версий Windows 95/98 и WindowsNT/2000, из-за разницы в кодировках строк у этих ОС (Windows 95/98 использует кодировку ASCII,
Windows NT/2000 – UNICODE).
LPADAPTER PacketOpenAdapter (LPSTR AdapterName) – предназначена для инициализации адаптера. Функции передается имя адаптера в качестве аргумента AdapterName (получено с помощью PacketGetAdapterNames), результатом функции является указатель на структуру ADAPTER открытого адаптера.
VOID PacketCloseAdapter (LPADAPTER lpAdapter) – высвобождает структуру
ADAPTER, связанную с указателем lpAdapter, и закрывает адаптер, связанный с ней.
LPPACKET PacketAllocatePacket (void) – определяет положение структуры PACKET, инициализированной функцией PacketInitPacket, и возвращает указатель на нее.
VOID PacketInitPacket (LPPACKET lpPacket, PVOID Buffer, UINT Length) – инициализирует структуру PACKET и имеет следующие аргументы:
lpPacket – указатель на инициализируемую структуру;
Buffer – указатель на буфер, задаваемый пользователем и содержащий данные пакета;
Length – длина буфера – максимальный размер данных, которые могут быть переданы драйвером приложению за один сеанс чтения.
VOID PacketFreePacket (LPPACKET lpPacket) – высвобождает структуру PCAKET, связанную с указателем lpPacket.
VOID PacketReceivePacket (lpAdapter AdapterObject, LPPACKET lpPacket, BOOLEAN Sync) – выполняет захват группы пакетов, и имеет следующие аргументы:
AdapterObject – указатель на структуру ADAPTER, определяющую адаптер, который будет задействован в текущей сессии;
lpPacket – указатель на структуру PACKET, используемую для записи принятых пакетов;
Sync – флаг, определяющий режим выполнения операции. Если выбран синхронный режим (True), функция блокирует программу до завершения операции. Если выбран асинхронный режим (False), блокировки не происходит. В последнем случае необходимо использовать функцию PacketWaitPaket для корректного выполнения операции.
Число принятых пакетов зависит от количества пакетов, сохраненных в буфере драйвера, размера этих пакетов и размера буфера, связанного со структурой
lpPacket. Ниже показан формат передачи данных приложению драйвером.
Пакеты сохраняются в буфере структуры lpPacket. Каждый пакет имеет трейлер, состоящий из структуры bpf_hdr и содержащий информацию о длине пакета и времени его приема. Поле Padding используется для выравнивания данных в буфере. Поля bf_datalen и bf_hdrlen структуры bpf_hdr используются для извлечения пакетов из буфера. Заметим, что Pcap извлекает каждый пакет до того, как передать его приложению.
BOOLEAN PacketWaitPacket (LPADAPTER AdapterObject, LPPACKET lpPacket) – используется для корректного завершения операции ввода/вывода драйвера захвата пакетов. Она является блокирующей в том случае, если драйвер выполняет операцию ввода/вывода. Функция возвращает значение True, если операция завершена успешно, в противном случае – False. Используя функцию GETLASTERROR, можно получить код возникшей ошибки.
BOOLEAN PacketSendPacket (LPADAPTER AdapterObject, LPPACKET lpPacket, BOOLEAN Sync) – позволяет передать сформированный пользователем пакет ПРОИЗВОЛЬНОЙ СТРУКТУРЫ в сеть через адаптер, заданный переменной AdapterObject. При этом пользователь программным образом создает заголовок пакета, заполняет его данными и отправляет его в сеть «как есть». Формировать структуру bpf_hdr перед заголовком отправляемого пакета не нужно. Также нет необходимости рассчитывать CRC пакета, поскольку она будет автоматически рассчитана сетевым интерфейсом и помещена в конце блока данных. Функция имеет те же аргументы, что и PacketReceivePacket. Возможности данной функции дополняет функция PacketSetNumWrites, которая устанавливает число повторов передачи одного пакета при вызове функции PacketSendPacket.
BOOLEAN PacketResetAdapter (LPADAPTER AdapterObject) - сбрасывает адаптер, указанный в качестве аргумента.
BOOLEAN PacketSetHwFilter (LPADAPTER AdapterObject, ULONG Filter) – устанавливает аппаратный (hardware) фильтр входящих пакетов. Константы, с помощью которых задается фильтр, объявлены в файле ntddndis.h. В качестве аргументов функции задается адаптер, на который устанавливается фильтр, и идентификатор фильтра. Функция возвращает значение True, если операция выполнена успешно. Ниже перечислены наиболее часто используемые фильтры:
NDIS_PACKET_TYPE_PROMISCUOUS: каждый входящий пакет принимается адаптером;
NDIS_PACKET_TYPE_DIRECTED: принимаются пакеты, предназначенные для данной рабочей станции;
NDIS_PACKET_TYPE_BROADCAST: принимаются только широковещательные запросы;
NDIS_PACKET_TYPE_MULTICAST: принимаются пакеты, предназначенные группе, которой принадлежит рабочая станция;
NDIS_PACKET_TYPE_ALL_MULTICAST: принимаются пакеты любой группы.
BOOLEAN PacketRequest (LPADAPTER AdapterObject, BOOLEAN Set,
PACKET_OID_DATA OidData) – предназначена для выполнения запроса/установки параметров адаптера AdapterObject. Эта функция используется для получения значений различных параметров сетевого адаптера (размер внутреннего буфера, скорость соединения, значение счетчика пакетов и др.) или их изменения. Второй аргумент определяет тип операции (Set=1 – установка параметра, Set=0 – запрос на получение значения параметра). Третий аргумент – указатель на структуру PACKET_OID_DATA, определяющую параметр адаптера. Функция возвращает True, если операция была выполнена без ошибок.
BOOLEAN PacketSetBuff (LPADAPTER AdapterObject, int dim) - устанавливает новый размер буфера драйвера, связанного с адаптером AdapterObject. dim – новый размер буфера. Функция возвращает True, если операция была выполнена успешно, False – если для выполнения операции недостаточно памяти. При установке нового размера буфера все данные, находящиеся в нем, стираются.
2.5.3. Win Api функции необходимые для работы с ARP таблицей
MIB_IPNETTABLE – функция непосредственно работающая с ARP таблицей. Используемые этой функцией параметры:
dwNumEntries - количество записей в таблице.
Table – указатель на таблицу ARP записей, описанную как массив структур MIB_IPNETROW.
Структура MIB_IPNETROW состоит из следующих полей:
dwIndex – индекс используемого адаптера.
dwPhysAddrLen – длинна МАС адреса.
bPhysAddr – МАС адрес.
dwAddr – IP адрес.
dwType – тип ARP записи.