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

         

ARP-протокол


Я уже говорили о том, что перед обращением к компьютеру по локальной сети необходимо узнать его МАС-адрес. Для этого существует ARP-протокол, который по IP-адресу ищет МАС-адрес. Происходит это автоматически и незаметно для рядового пользователя, но иногда появляется возможность ручного управления таблицей ARP. В ОС Windows для этих целей есть утилита с одноименным названием аrр, но она консольная, и работать с ней не очень удобно. Сейчас я покажу простую графическую утилиту, на примере которой и объясню функции работы с данным протоколом.

Создайте новое MFC-приложение на основе диалога. Внешний вид главного диалогового окна представлен на 6.9. Здесь используются две строки ввода для указания IP- и МАС-адреса. По нажатии кнопки Add будет добавляться новая ARP-запись, в которой по указанному IP-адресу будет определяться МАС-адрес. По нажатии кнопки Update в списке List Box будет отображаться таблица ARP. Кнопка Delete будет использоваться для удаления записи из таблицы.

В обработчик события кнопки Update нужно написать код из листинга 6.10.

Листинг 6.10. Отображение таблицы ARP
void CARPApplicationDlg::OnBnClickedButton1() { DWORD dwStatus; PMIB_IPNETTABLE pIpArpTab=NULL;

DWORD dwActualSize = 0; GetIpNetTable(pIpArpTab, dwActualSize, true);

pIpArpTab = (PMIB_IPNETTABLE) malloc(dwActualSize); if (GetIpNetTable(pIpArpTab, dwActualSize, true) != NO_ERROR) { if (pIpArpTab) free (pIpArpTab); return; }

DWORD i, dwCurrIndex; char sPhysAddr[256], sType[256], sAddr[256]; PMIB_IPADDRTABLE pIpAddrTable = NULL; char Str[255];

dwActualSize = 0; GetIpAddrTable(pIpAddrTable, dwActualSize, true); pIpAddrTable = (PMIB_IPADDRTABLE) malloc(dwActualSize); GetIpAddrTable(pIpAddrTable, dwActualSize, true);

dwCurrIndex = -100;

for (i = 0; i pIpArpTab-dwNumEntries; ++i) { if (pIpArpTab-table[i].dwIndex != dwCurrIndex) { dwCurrIndex = pIpArpTab-table[i].dwIndex;

struct in_addr in_ad; sAddr[0] = '\n'; for (int i = 0; i pIpAddrTable-dwNumEntries; i++) { if (dwCurrIndex != pIpAddrTable-table[i].dwIndex) continue;




in_ad.s_addr = pIpAddrTable-table[i].dwAddr; strcpy(sAddr, inet_ntoa(in_ad)); }

sprintf(Str,"Interface: %s on Interface 0x%X", sAddr, dwCurrIndex); lbMessages.AddString(Str); lbMessages.AddString(" Internet Address | Physical Address | Type"); }

AddrToStr(pIpArpTab-table[i].bPhysAddr, pIpArpTab-table[i].dwPhysAddrLen, sPhysAddr);

switch (pIpArpTab-table[i].dwType) { case 1: strcpy(sType,"Other"); break; case 2: strcpy(sType,"Invalidated"); break; case 3: strcpy(sType,"Dynamic"); break; case 4: strcpy(sType,"Static"); break; default: strcpy(sType,""); }

struct in_addr in_ad; in_ad.s_addr = pIpArpTab-table[i].dwAddr; sprintf(Str, " %-16s | %-17s | %-11s", inet_ntoa(in_ad), sPhysAddr, sType); lbMessages.AddString(Str); }

free(pIpArpTab); }

Посмотрите внимательно на код. Обратите внимание, что в данном случае не загружается библиотека WinSock. К проекту подключается заголовочный файл winsock.h, потому что используются типы данных, которые объявлены в нем. Но обращение к ним происходит только при компиляции. В данном случае не применяются библиотечные функции, необходимые на этапе выполнения.

Если вы добавите в программу код, который будет содержать хотя бы одну функцию из библиотеки WinSock (например, определение имени хоста по IP-адресу), то прежде чем ее использовать, необходимо будет загрузить библиотеку.

Первым делом из системы получена таблица соответствия IP-адресов их физическим МАС-адресам. Это и есть ничто иное, как ARP-таблица. Функция для получения этой таблицы имеет следующий вид:

DWORD GetIpNetTable( PMIB_IPNETTABLE pIpNetTable, PULONG pdwSize, BOOL border );

ARP-таблица описывается следующими параметрами:

указатель на структуру типа MIB_IPNETTABLE, в которой будет размещена таблица;

размер структуры. Если он нулевой, то функция вернет объем памяти, требуемый для таблицы;

сортировка — если параметр равен true, то таблица будет упорядоченной.



Структура MIB_IPNETTABLE, которая задается в качестве первого параметра, имеет следующий вид:

typedef struct _MIB_IPNETTABLE { DWORD dwNumEntries; MIB_IPNETROW table[ANY_SIZE]; } MIB_IPNETTABLE, *PMIB_IPNETTABLE;

Первый параметр указывает на количество записей в таблице, а второй — это структура, содержащая данные таблицы:

typedef struct _MIB_IPNETROW { DWORD dwIndex; DWORD dwPhysAddrLen; BYTE bPhysAddr[MAXLEN_PHYSADDR]; DWORD dwAddr; DWORD dwType; } MIB_IPNETROW, *PMIB_IPNETROW;

Структура с данными таблицы описывается набором параметров:

dwIndex — индекс адаптера;

dwPhysAddrLen — длина физического адреса;

bPhysAddr — физический адрес;

dwAddr — IP-адрес;

dwType — тип записи. В свою очередь может принимать такие значения:

4 — статический. Если запись добавлена вручную с помощью функций, которые будут рассмотрены позже;

3 — динамический. Записи с адресами, которые получены автоматически с помощью протокола ARP (действительны в течение определенного времени, а потом автоматически уничтожаются);

2 — неправильный. Записи с ошибками;

1 — другой.

В принципе, ARP-таблица уже создана. Если в компьютере установлена только одна сетевая карта, то этого достаточно, потому что все записи будут относиться к ней. Если установлена хотя бы две сетевые карты, то часть записей будет принадлежать одному интерфейсу, а остальные — второму.

Чтобы картина была полной, необходимо показать, какие записи к какому интерфейсу относятся. Для этого есть индекс интерфейса в структуре MIB_IPNETROW, но этот индекс абсолютно ничего не скажет конечному пользователю. Но в сочетании с IP-адресом адаптера это станет более информативно.

А вот IP-адреса адаптера у нас пока нет. Чтобы его узнать, нужно получить таблицу соответствия IP-адресов адаптерам. Это можно сделать с помощью функции GetIpAddrTable. Функция похожа на GetIpNetTable:

DWORD GetIpAddrTable ( PMIB_IPADDRTABLE pIpAddrTable, PULONG pdwSize, BOOL bOrder );

И так же имеет три параметра: указатель на структуру типа MIB_IPADDRTABLE (pIpAddrTable), размер структуры (pdwSize) и флаг сортировки (bOrder).



Первый параметр — это структура следующего вида:

typedef struct _MIB_IPADDRTABLE { DWORD dwNumEntries; MIB_IPADDRROW table[ANY_SIZE]; } MIB_IPADDRTABLE, *PMIB_IPADDRTABLE;

У этой структуры два параметра:

dwNumEntries — количество структур, указанных во втором параметре;

table — массив структур типа MIB_IPADDRROW.

Структура MIB_IPADDRROW описывается следующим образом:

typedef struct _MIB_IPADDRROW { DWORD dwAddr; DWORDIF_INDEX dwIndex; DWORD dwMask; DWORD dwBCastAddr; DWORD dwReasmSize; unsigned short unused1; unsigned short wType; } MIB_IPADDRROW, *PMIB_IPADDRROW;

В этой структуре доступны следующие параметры:

dwAddr — IP-адрес;

dwIndex — индекс адаптера, с которым связан IP-адрес;

dwMask — маска для IP-адреса;

dwBCastAddr — широковещательный адрес. Чаще всего это IP-адрес, в котором нулевое значение номера узла. Например, если у вас IP-адрес 192.168.4.7, то широковещательный адрес будет 192.168.4.0;

dwReasmSize — максимальный размер получаемых пакетов;

unused1 — зарезервировано;

wType — тип адреса, может принимать следующие значения:

MIB_IPADDR_PRIMARY — основной IP-адрес;

MIB_IPADDR_DYNAMIC — динамический адрес;

MIB_IPADDR_DISCONNECTED — адрес на отключенном интерфейсе, например, отсутствует сетевой кабель;

MIB_IPADDR_DELETED — адрес в процессе удаления;

MIB_IPADDR_TRANSIENT — временный адрес.

Когда получены необходимые данные, запускается цикл перебора всех строк в таблице ARP. Но прежде чем выводить на экран информацию о строке, надо проверить, к какому интерфейсу она относится.

При получении данных был выбран режим сортировки записей, поэтому можно надеяться, что вначале идут записи одного интерфейса, а потом другого. Поэтому перед циклом в переменную dwCurrIndex занесено значение —100. Интерфейса с таким номером точно не будет. На первом же шаге цикла будет видно, что запись из ARP-таблицы не относится к интерфейсу с номером —100, и необходимо вывести на экран IP-адрес сетевой карты, к которой относится эта запись. Для этого по параметру dwIndex ищется запись в таблице соответствия IP-адресов номерам интерфейса. Если запись найдена (а она должна быть найдена), то выводится заголовок таблицы, который будет выглядеть примерно так:



Interface: 192.168.1.100 on Interface 0x10000003
Internet Address | Physical Address | Type

Затем выводится информация из ARP-таблицы, пока не встретится запись, относящаяся к другому интерфейсу. Тогда снова выводится заголовок и т.д.

На 6.9 вы можете увидеть пример работы программы на моем компьютере. Обращаю ваше внимание, что у вас может и не быть ARP-записей, потому что они существуют только при работе в локальной сети. При выходе в Интернет по модему протокол ARP не используется.



6.9. Результат работы программы ARPApplication

Если вы пока не знаете, как применить программу, то у меня уже были случаи, когда она оказалась незаменима. Допустим, что нужно узнать МАС-адрес компьютера в локальной сети, который находится от вас очень далеко. Можно пойти к этому компьютеру и посмотреть адрес с помощью утилиты ipconfig, а можно произвести следующие действия:

Выполнить программу Ping для проверки связи с удаленным компьютером. В этот момент отсылаются эхо-пакеты, которым также нужно знать МАС-адрес, и для этого задействуется ARP-протокол.

Запустить программу просмотра ARP-таблицы и там посмотреть МАС-адрес нужного компьютера.

Теперь посмотрите, как можно добавлять новые записи в таблицу ARP. Напоминаю, что все записи, добавленные программно, становятся статичными и не уничтожаются автоматически на протяжении всей работы ОС. По нажатии кнопки Add будет выполняться код из листинга 6.11.

Листинг 6.11. Добавление новой записи в таблицу ARP
void CARPApplicationDlg::OnBnClickedButton2() { char sPhysAddr[255], sInetAddr[255], sMacAddr[255], sInterface[255];

edIPAddress.GetWindowText(sInetAddr, 255); edMacAddress.GetWindowText(sMacAddr, 255); edInterface.GetWindowText(sInterface, 255);

if (sInetAddr == NULL || sMacAddr == NULL || sInterface == NULL) { AfxMessageBox("Fill IP address, MAC address and Interface"); return; }

DWORD dwInetAddr; dwInetAddr = inet_addr(sInetAddr); if (dwInetAddr == INADDR_NONE) { AfxMessageBox("Bad IP Address"); return; }



StrToMACAddr(sMacAddr, sPhysAddr);

MIB_IPNETROW arpRow; sscanf(sInterface, "%X",(arpRow.dwIndex));

arpRow.dwPhysAddrLen = 6; memcpy(arpRow.bPhysAddr, sPhysAddr, 6); arpRow.dwAddr = dwInetAddr; arpRow.dwType = MIB_IPNET_TYPE_STATIC;

if (SetIpNetEntry(arpRow) != NO_ERROR) AfxMessageBox("Couldn't add ARP record"); }

Самое главное здесь — это функция SetIpNetEntry , которая добавляет новую ARP-запись и выглядит следующим образом:

DWORD SetIpNetEntry ( PMIB_IPNETROW pArpEntry );

В качестве единственного параметра функции указывается структура типа MIB_IPNETROW, которую мы уже использовали при получении данных ARP-таблицы. В этой структуре необходимо указать четыре параметра: интерфейс (dwIndex), МАС-адрес (bPhysAddr) и IP-адрес (dwInetAddr), запись которого надо добавить, и тип записи (в поле dwType значение MIB_IPNET_TYPE_STATIC). Остальные поля в этой функции не используются, и их заполнять не надо.

Теперь посмотрите на функцию удаления. По нажатии кнопки Delete выполняется код из листинга 6.12.

Листинг 6.12. Удаление записи из ARP-таблицы
void CARPApplicationDlg::OnBnClickedButton3() { char sInetAddr[255], sInterface[255];

edIPAddress.GetWindowText(sInetAddr, 255); edInterface.GetWindowText(sInterface, 255);

if (sInetAddr == NULL || sInterface == NULL) { AfxMessageBox("Fill IP address and Interface"); return; }

DWORD dwInetAddr; dwInetAddr = inet_addr(sInetAddr); if (dwInetAddr == INADDR_NONE) { printf("IpArp: Bad Argument %s\n", sInetAddr); return; }

MIB_IPNETROW arpEntry;

sscanf(sInterface, "%X",(arpEntry.dwIndex)); arpEntry.dwAddr = dwInetAddr;

if (DeleteIpNetEntry(arpEntry) != NO_ERROR) AfxMessageBox("Couldn't delete ARP record"); }

Для удаления записи используется функция DeleteIpNetEntry, которая выглядит следующим образом:

DWORD DeleteIpNetEntry( PMIB_IPNETROW pArpEntry );

У нее один параметр в виде структуры PMIB_IPNETROW, в которой нужно указывать только интерфейс и IP-адрес, запись которого надо удалить.

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

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