Программирование на C++ глазами хакера

Определение пути пакета


Как можно определить путь пакета, по которому он идет от нас до адресата? Если принять во внимание предназначение ICMP-сообщений, то проблема решается просто. Каждый пакет имеет поле TTL (Time To Leave, время жизни). Каждый маршрутизатор уменьшает значение поля на единицу, и когда оно становится равным нулю, пакет считается заблудившимся, и маршрутизатор возвращает ICMP-сообщение об ошибке. Использование этого поля еще упрощает проблему.

Надо направить пакет на сервер с временем жизни, равным 1. Первый же маршрутизатор уменьшит значение на 1 и увидит 0. Это заставит его вернуть ICMP-сообщение об ошибке, по которому можно узнать первый узел, через который проходит пакет. Затем отсылается пакет с временем жизни, равным 2, и определяется второй маршрутизатор (первый пропустит пакет, а второй вернет ICMP-сообщение). Таким образом можно отсылать множество пакетов, пока не достигнем адресата.

Стало быть, есть возможность узнать не только маршрут, но и время отклика каждого маршрутизатора, что позволяет определить слабое звено на пути следования пакета. Связь с каждым из устройств на пути пакета может изменяться в зависимости от нагрузки, поэтому желательно сделать несколько попыток соединения, чтобы определить среднее время отклика.

Конечно же, первый пакет может пойти одним маршрутом, а второй — другим, но чаще всего все пакеты движутся по одному и тому же маршруту.

Сейчас я покажу простейший пример определения пути следования маршрута. Но только ICMP-пакеты я буду посылать не через RAW-сокеты, а через библиотеку icmp.dll. В этой библиотеке есть все необходимые функции для создания сокета и отправки пакета. Нужно только указать адрес, содержимое пакета и его параметры, а все остальное сделают за вас. Таким образом, вы научитесь пользоваться библиотекой icmp.dll и сможете выяснить путь прохождения пакета.

Создайте новое MFC-приложение TraceRote. На главном окне вам понадобится одна строка ввода для указания адреса компьютера, связь с которым необходимо проверить, один компонент типа List Box для отображения информации и кнопка (например, Trace), по которой будет пинговаться удаленный компьютер. По нажатии кнопки будет выполняться код из листинга 6.9.


Листинг 6.9. Определение пути следования пакета
void CTraceRouteDlg::OnBnClickedButton1() { WSADATA wsa; if (WSAStartup(MAKEWORD(1, 1), wsa) != 0) { AfxMessageBox("Can't load a correct version of WinSock"); return; }

hIcmp = LoadLibrary("ICMP.DLL"); if (hIcmp == NULL) { AfxMessageBox("Can't load ICMP DLL"); return; }



pIcmpCreateFile = (lpIcmpCreateFile) GetProcAddress(hIcmp, "IcmpCreateFile"); pIcmpSendEcho = (lpIcmpSendEcho) GetProcAddress(hIcmp, "IcmpSendEcho"); pIcmpCloseHandle = (lpIcmpCloseHandle) GetProcAddress(hIcmp, "IcmpCloseHandle");

in_addr Address; if (pIcmpCreateFile == NULL) { AfxMessageBox("ICMP library error"); return; }

char chHostName[255]; edHostName.GetWindowText(chHostName, 255); LPHOSTENT hp = gethostbyname(chHostName); if (hp== NULL) { AfxMessageBox("Host not found"); return; } unsigned long addr; memcpy(addr, hp-h_addr, hp-h_length);

BOOL bReachedHost = FALSE; for (UCHAR i=1; i=50 !bReachedHost; i++) { Address.S_un.S_addr = 0;

int iPacketSize=32; int iRTT;

HANDLE hIP = pIcmpCreateFile(); if (hIP == INVALID_HANDLE_VALUE) { AfxMessageBox("Could not get a valid ICMP handle"); return; }

unsigned char* pBuf = new unsigned char[iPacketSize]; FillMemory(pBuf, iPacketSize, 80);

int iReplySize = sizeof(ICMP_ECHO_REPLY) + iPacketSize; unsigned char* pReplyBuf = new unsigned char[iReplySize]; ICMP_ECHO_REPLY* pEchoReply = (ICMP_ECHO_REPLY*) pReplyBuf;

IP_OPTION_INFORMATION ipOptionInfo; ZeroMemory(ipOptionInfo, sizeof(IP_OPTION_INFORMATION)); ipOptionInfo.Ttl = i;

DWORD nRecvPackets = pIcmpSendEcho(hIP, addr, pBuf, iPacketSize, ipOptionInfo, pReplyBuf, iReplySize, 30000);

if (nRecvPackets != 1) { AfxMessageBox("Can't ping host"); return; } Address.S_un.S_addr = pEchoReply-Address; iRTT = pEchoReply-RoundTripTime;

pIcmpCloseHandle(hIP);

delete [] pReplyBuf; delete [] pBuf;

char lpszText[255];



hostent* phostent = NULL; phostent = gethostbyaddr((char *)Address.S_un.S_addr, 4, PF_INET);

if (phostent) sprintf(lpszText, "%d: %d ms [%s] (%d.%d.%d.%d)", i, iRTT, phostent-h_name, Address.S_un.S_un_b.s_b1, Address.S_un.S_un_b.s_b2, Address.S_un.S_un_b.s_b3, Address.S_un.S_un_b.s_b4); else sprintf(lpszText, "%d - %d ms (%d.%d.%d.%d)", i, iRTT, Address.S_un.S_un_b.s_b1, Address.S_un.S_un_b.s_b2, Address.S_un.S_un_b.s_b3, Address.S_un.S_un_b.s_b4);

lbMessages.AddString(lpszText);

if (addr == Address.S_un.S_addr) bReachedHost = TRUE; }

if (hIcmp) { FreeLibrary(hIcmp); hIcmp = NULL; }

WSACleanup(); }

Несмотря на то, что используется дополнительная библиотека icmp.dll, библиотеку WinSock надо загрузить в любом случае. К тому же будет использоваться функция gethostbyname для определения IP-адреса, если пользователь укажет символьное имя компьютера. В данном случае будет достаточно первой версии библиотеки, т. к. не будут применяться RAW-сокеты. Таким образом, программа сможет работать и в Windows 98 (без WinSock 2.0).

После этого нужно загрузить динамическую библиотеку icmp.dll с помощью функции LoadLibrary. Она находится в папке windows/system (или windows/system32), поэтому не надо указывать полный путь. Программа без проблем найдет и загрузит библиотеку.

В библиотеке нас будут интересовать следующие процедуры:

IcmpCreateFile — инициализация;

IcmpSendEcho — отправка эхо-пакета;

IcmpCloseHandle — закрытие ICMP.

Прежде чем посылать пакет, следует его проинициализировать с помощью функции IcmpCreateFile. По завершении работы с ICMP нужно вызвать функцию IcmpCloseHandle, чтобы закрыть его.

Теперь в заранее подготовленные переменные запоминаются адреса необходимых процедур из библиотеки:

pIcmpCreateFile=(lpIcmpCreateFile)GetProcAddress(hIcmp,"IcmpCreateFile");
pIcmpSendEcho=(lpIcmpSendEcho)GetProcAddress(hIcmp,"IcmpSendEcho");
pIcmpCloseHandle=(lpIcmpCloseHandle)GetProcAddress(hIcmp,"IcmpCloseHandle");



Если писать программу по всем правилам, то необходимо было бы проверить полученные адреса на равенство нулю. Если хотя бы один адрес функции нулевой, то она не найдена, и дальнейшее ее использование невозможно. Чаще всего такое бывает из-за неправильного написания имени функции. Но может случиться, когда программа загрузит другую библиотеку с таким же именем, в которой вообще нет таких функций. Чтобы этого не произошло, переменная pIcmpCreateFile (она должна содержать адрес функции IcmpCreateFile) проверяется на равенство нулю. Если это так, то загрузилась ошибочная библиотека, и об этом выводится соответствующее сообщение. Остальные переменные не проверяются (в надежде на правильное написание).

Следующим этапом определяется адрес компьютера, путь к которому надо найти, и переводится в IP-адрес. Если в этот момент произошла ошибка, то адрес указан неверно, и дальнейшее выполнение кода невозможно.

Вот теперь можно переходить к пингованию удаленного компьютера. Так как может возникнуть необходимость послать несколько пакетов с разным временем жизни, запускается цикл от 1 до 50. Использование в данном случае цикла while, который выполнялся бы, пока пинг не дойдет до нужного компьютера, не рекомендуется, т.к. появляется вероятность возникновения бесконечного цикла.

Внутри цикла инициализируется ICMP-пакет с помощью функции IcmpCreateFile. Результатом будет указатель на созданный объект, который понадобится при посылке эхо-пакета, поэтому он сохраняется в переменной hIP типа HANDLE:

HANDLE hIP = pIcmpCreateFile(); if (hIP == INVALID_HANDLE_VALUE) { AfxMessageBox("Could not get a valid ICMP handle"); return; }

Если результат равен INVALID_HANDLE_VALUE, то во время инициализации произошла ошибка, и дальнейшее выполнение невозможно.

После этого выделяется буфер для данных, который, как и в случае с пин-гом, заполняется символом с кодом 80. Далее создается пакет типа ICMP_ECHO_REPLY, в котором возвращается информация, полученная от маршрутизатора или компьютера. Нужно также создать пакет типа IP_OPTION_INFORMATION, в котором указывается время жизни пакета (параметр Ttl).



Когда все подготовлено, можно отправлять ICMP-пакет с помощью функции IcmpSendEcho, у которой 8 параметров:

указатель ICMP (получен во время инициализации);

адрес компьютера;

буфер с данными;

размер пакета (с учетом объема посылаемых данных);

IP-пакет с указанием времени жизни (на первом шаге он будет равен единице, потом двум и т.д.);

буфер для хранения структуры типа ICMP_ECHO_REPLY, в которую будет записан результирующий пакет;

размер буфера;

время ожидания ответа.

В качестве результата функция возвращает количество принятых пакетов. В нашем случае он один. Если возвращаемое значение равно нулю, то маршрутизатор или компьютер не ответили ICMP-пакетом, и невозможно выяснить его параметры.

Время ответа можно получить из параметра RoundTripTime структуры ICMP_ECHO_REPLY, а адрес сетевого устройства, ответившего на запрос, — из параметра Address.

После работы не забывайте закрывать указатель на созданный ICMP с помощью IcmpCloseHandle.

Теперь можно выводить полученную информацию. Для удобства восприятия в программе реализован перевод IP-адреса в символьное имя с помощью функции gethostbyaddr. У этой функции три параметра:

IP-адрес компьютера, символьное имя которого надо определить;

длина адреса;

семейство протокола. От этого зависит формат предоставляемого адреса.

Далее проверяется, если поступил ответ от искомого компьютера, то цикл прерывается, иначе нужно увеличить на единицу время жизни пакета и повторить посылку ICMP-пакета:

if (addr == Address.S_un.S_addr) bReachedHost = TRUE;

По окончании работы нужно выгрузить из памяти библиотеку icmp.dll и освободить библиотеку WinSock:

if (hIcmp) { FreeLibrary(hIcmp); hIcmp = NULL; } WSACleanup();

Запустите программу TraceRoute. На 6.8 показано окно с результатами ее работы.



6.8. Окно с результатом работы программы TraceRoute

Я постарался сделать пример простым, а логику — прямолинейной, чтобы вам легче было разобраться. При этом, если в процессе работы происходит ошибка, то по выходе из программы библиотеки icmp и winsock остаются загруженными. Самый простой способ избавиться от этого недостатка — производить загрузку библиотек при старте, а выгрузку — при выходе из программы, и если библиотеки не загружены, то можно отключать кнопки отправки пакета, чтобы пользователь не смог ими воспользоваться.



В Интернете можно найти заголовочные файлы для библиотеки icmp.dll, которые могут еще больше упростить этот пример. Но я не стал их использовать, чтобы ничего не ускользнуло от вашего внимания.

Примечание
Исходный код примера, описанного в этом разделе, вы можете найти на компакт - диске в каталоге \Demo\Chapter6\TraceRoute.

Содержание раздела