Взгляд на сеть из другой галактики — Архив WASM.RU
Данная статья познакомит вас с интересным миром, летающим в сетевом кабеле, наподобие витых пар, телефонной лапши, оптоволокна и т.д. и т.п. Нет, я не буду расписывать, для чего нужны эти сетевые кабели. Не буду писать вообще про них ни строчки. Но с миром, обитающим в них - познакомлю.
Для использования этой статьи на практике вам необходимы следующие инструменты:
- Библиотека winpcap \ libpcap (в зависимости от того, на какой операционной системе вы будете работать). Мой выбор Windows, да простят меня фанаты Linux.
- Компилятор. Я буду использовать masm32 v8.2.
- Любой маломальский редактор.
- Сниффер сетевого трафика. Выбирайте сами. Я использовал IP Promiscuous Sniffer.
Обязательно скачайте полную и последнюю версию выше предложенной библиотеки c набором lib файлов.
"..Умереть ничего - если выпить немного.."
- Общая информация -
В былые времена, когда компьютеры были действительно большими, а программисты были действительно умными, организации начали придумывать различные технологии обмена данными, были созданы первые сети. Был придуман протокол TCP/IP. Ученые работали над вопросом построения надлежащей модели этого протокола. Такую модель разработали и назвали, как модель OSI (Open System Interconnection - Взаимодействие открытых систем). OSI была разработана в рамках ISO (International Organization for Standardization - Международная организация по стандартизации). Подробнее об этом читайте соответствующую литературу (см. в конце статьи). OSI и TCP\IP модели делятся на уровни. Каждый уровень имеет свою функцию и свое назначение.
К физическому уровню относят разъемы, кабели - носители информации в общем смысле, сигналы.
Уровень связи данных (еще так называемый канальный уровень) организует данные в кадры (frame). Данные обворачиваются заголовочной информацией о физических адресах источника и приемника, тип протокола (связан с интерфейсом сетевой карты (например, Ethernet)). Очень интересно, ведь, получив доступ к этому уровню, можно обволакивать свои данные в дополнительные заголовки или изменять уже существующие заголовки, используемые сетевыми картами в вашей сети. Во многих книгах и руководствах говорят, что об этом уровне можно не заботиться, но именно на этот уровень я обращу все ваше внимание.
Сетевой уровень IP (Internet Protocol). Данные этого протокола пересылаются в элементах называемых датаграммами (datagram). В такой датаграмме содержится заголовок IP содержащий IP-адреса приемника и источника, длину пакета и другие параметры (подробнее смотрите в предложенной литературе ниже).
Транспортный уровень включает в себя TCP (Transmission Control Protocol) и UDP (User Datagram Protocol) протоколы передачи данных. Эти протоколы не являются темой нашей статьи (подробнее о них можно узнать предложенной литературе ниже).
Три верхних уровня соответствуют уровню приложений. Это может быть браузер, FTP сервер, Telnet.. Собственно это нам не интересно, с точки зрения программирования.
Рассмотрим, как пакетируются данные, в зависимости от уровня TCP/IP:
Обратите внимание на то, почему нам так интересен канальный уровень. Он полностью контролирует все вышестоящие уровни и, кроме того, позволяет на своем уровне менять и добавлять параметры для каждого вышестоящего уровня.
- Чем черт не шутит -
Рассмотрим канальный уровень поподробнее. Для этого поставим перед собой задачу. Пусть нам необходимо послать пакет канального уровня. ARP (Address Resolution Protocol) протокол прекрасно подойдет для этой цели.
Прежде чем хосты в сети Ethernet откроют соединение, они обязаны знать физические адреса назначения. Для этой цели и используется ARP протокол. Он осуществляет трансляцию между IP-адресом и соответствующим ему физическим адресом. На хосте содержится таблица, называемая ARP таблица. Она содержит в себе список IP-адресов и соответствующие им физические адреса. Существует два типа трансляции - динамический и статический.
При динамической трансляции хост посылает широковещательный пакет ARP содержащий искомый IP-адрес. Целевой хост узнает свой IP-адрес и принимает этот запрос. При этом изменяется таблица - в нее включается IP-адрес и физический адрес отправителя. После этого хост передает отправителю свой физический адрес. Отправитель, получив такой ответ, обновляет свою таблицу ARP и становится готовым к пересылке данных по локальной сети.
В статической трансляции никаких широковещательных данных не посылается, и никакие данные не принимаются. Вся ARP таблица заполняется самим пользователем системы.
ARP пакет состоит из Ethernet кадра и ARP кадра. Рассмотрим подробнее эти кадры:
Первое поле Preamble можно опустить - оно заполняется на аппаратном уровне сетевой картой, его не видно в снифферах. Считаем что начало кадра - это следующее поле.
Второе поле Destination Address - здесь находится информация о физическом (далее MAC) адресе хоста-приемника.
Третье поле Source Address - информация о MAC (Media Access Control) адресе источника.
Четвертое поле EtherType - тип Ethernet среды (это может быть Ethernet, Token Ring, Frame relay, ATM).
Пятое поле Payload - это поле, по сути, является полезной нагрузкой - может содержать все что угодно. Если мы хотим послать ARP пакет, то в этом поле должна содержаться полная информация ARP кадра, то есть его заголовок и его полезная нагрузка.
Шестое поле FCS - это циклическая контрольная сумма всего пакета данных. Является замыкающим полем пакета и заполняется на аппаратном уровне сетевой картой. Это поле нельзя увидеть в сниффере.
Первое поле Hardware Type - указывает на тип канала связи данных (Ethernet, Token Ring, Frame relay, ATM).
Второе поле Protocol Type - содержит тип протокола. В нашем случае это ARP протокол.
Третье поле Hardware Address Length - содержит длину поля физического адреса (MAC адреса).
Четвертое поле Protocol Address Length - содержит длину поля протокольного адреса (IP адреса).
Пятое поле Operation - указывает на тип ARP кадра. Это может быть кадр запроса, кадр ответа, и обратные варианты запроса и ответа.
Шестое поле Sender Hardware Address - сюда записывается MAC адрес отправителя.
Седьмое поле Sender Protocol Address - в него записывается IP адрес отправителя.
Восьмое поле Target Hardware Address - соответственно MAC адрес получателя.
Девятое поле Target Protocol Address - соответственно IP адрес получателя.
В итоге, мы должны собрать эти два кадра вместе, добавить полезную нагрузку, если это необходимо, и у нас получится ARP пакет.
- Думай, что делаешь -
Теперь мы имеем достаточное представление организации данных, можно перейти к практической части. Тут есть одна сложность. Windows не позволяет получить доступ к канальному уровню, без дополнительных усилий и временных затрат. К счастью, опытные разработчики уже решили этот вопрос за нас - нам не придется писать протокольный драйвер NDIS. И этим решением является библиотека winpcap. Она позволяет осуществлять наши цели, не задумываясь о том, как же устроены механизмы взаимодействия драйверов сетевой карты и подсистемы ввода/вывода. Нам дается возможность, послать в сеть пакет как есть (ну почти, если не учитывать аппаратно добавляемые поля).
Создадим простенькое приложение. Это будет окно, на котором находятся кнопка и поле ввода. В поле ввода мы вводим IP-адрес того, кто должен получить ARP пакет. Опущу код создания окна. Это вы должны уметь делать сами. Рассмотрим действия, вызываемые по нажатию кнопки.
Для начала подключим 2 файла (смотрите в архиве, в конце статьи):
Код (Text):
include <packet.inc> includelib <packet.lib>В packet.inc файле содержатся объявления прототипов используемых нами функций и структур. Файл packet.lib - это файл библиотеки импорта. Как уже говорилось выше - скачайте его у разработчиков или создайте сами из динамической библиотеки packet.dll.
Объявим используемые переменные:
Код (Text):
UsAd db "Using Adapter",0 lpAdapter LPADAPTER ? ;Описатель сетевого адаптера lpPacket LPPACKET ? ;Описатель пакета AdapterBuff db 256 dup (?) ;Буфер для имени адаптера AdapterName db 512 dup(?) ;Буфер для всех имен адаптеров в системе AdapterLen dd ? ;Длина буфера AdapterName packetbuff db 100 dup(?) ;Буфер в котором формируется пакет IPstr db 11h dup(?) ;Буфер содержащие введенный IP-адрес ….. .IF ax==BN_CLICKEDочищаем буфер для дальнейшего использования:
Код (Text):
mov AdapterLen, sizeof AdapterName invoke RtlZeroMemory,addr AdapterName,AdapterLenдля обращения к сети необходимо узнать имя сетевого адаптера в системе. Функция PacketGetAdapterNames(), экспортируемая из packet.dll (как и все другие начинающиеся со слова Packet), позволяет получить имена всех адаптеров, установленных в системе. Ее прототип следующий:
PacketGetAdapterNames PROTO STDCALL :DWORD, :DWORD
Первый параметр - это адрес буфера куда запишутся имена всех адаптеров.
Второй параметр - Длина буфера.Вызовем ее:
Код (Text):
lea eax,AdapterLen push eax lea edi,AdapterName push edi call PacketGetAdapterNamesобязательно проверяем, не возвратила ли функция ошибку:
Код (Text):
cmp eax,FALSE je Errorвозвращаемый формат строки имеет следующий вид:
<имя первого устройства>0<имя второго устройства>0<…..>00<описание первого устройства>0<описание второго устройства>0<…..>0000000…..
Возьмем первое попавшееся устройство и запишем его в отдельный буфер (без его описания):
Код (Text):
lea esi,AdapterName lea edi,AdapterBuff Next: movsb cmp byte ptr [esi],0 jne Nextпокажем имя нашего адаптера:
Код (Text):
invoke MessageBox,hWnd,addr AdapterBuff,addr UsAd,MB_OKдалее открываем адаптер. Фактически winpcap скрывает от нас все тонкости и работает с адаптером как с файлом. Для открытия адаптера используем функцию PacketOpenAdapter(). Она имеет следующий прототип:
PacketOpenAdapter PROTO STDCALL :DWORD
Единственный параметр - это адрес буфера, содержащий имя адаптера. Вызываем:
Код (Text):
lea eax,AdapterBuff push eax call PacketOpenAdapterпроверяем, возвратился ли описатель адаптера или произошла ошибка:
Код (Text):
assume eax:ptr ADAPTER .IF [eax].hFile == INVALID_HANDLE_VALUE jmp Error .ENDIF assume eax:nothingсохраняем описатель адаптера:
Код (Text):
mov lpAdapter,eaxДалее необходимо выделить память для структуры PACKET. Используем функцию PacketAllocatePacket(). Она не имеет параметров. Сразу проверим на ошибку. Если все прошло гладко, то сохраним описатель структуры PACKET:
Код (Text):
call PacketAllocatePacket cmp eax,0 je Error mov lpPacket,eaxсчитываем введенный нами IP-адрес назначения пакета и приведем сразу в little endian формат:
Код (Text):
invoke GetWindowText,hwndEdit,addr IPstr,10h invoke inet_addr,addr IPstrВсе подготовлено - можно начинать формировать пакет. Вспомним предыдущие рисунки. Первым заполняется Ethernet кадр. Укажем в нем, что этот пакет должен быть широковещательным. Это поле должно содержать 1 в каждом бите:
Код (Text):
mov byte ptr [packetbuff+00],0FFh ;|- mov byte ptr [packetbuff+01],0FFh ;| mov byte ptr [packetbuff+02],0FFh ;| Destination Address mov byte ptr [packetbuff+03],0FFh ;| mov byte ptr [packetbuff+04],0FFh ;| mov byte ptr [packetbuff+05],0FFh ;|-Второе поле - это наш физический адрес. Не будем подставлять туда реальный. Установим его в 0:
Код (Text):
mov byte ptr [packetbuff+06],000h ;|- mov byte ptr [packetbuff+07],000h ;| mov byte ptr [packetbuff+08],000h ;| Source Address mov byte ptr [packetbuff+09],000h ;| mov byte ptr [packetbuff+10],000h ;| mov byte ptr [packetbuff+11],000h ;|-Третье поле - это тип среды - по RFC тип среды Ethernet равен 0806h. Так и установим:
Код (Text):
mov byte ptr [packetbuff+12],008h ;|- EtherType mov byte ptr [packetbuff+13],006h ;|-С Ethernet кадром разобрались. Заполним ARP кадр. Первое поле - тип канала связи. Для Ethernet надо установить в 0001h:
Код (Text):
mov byte ptr [packetbuff+14],000h ;|- Hardware Type - Ethernet mov byte ptr [packetbuff+15],001h ;|-Второе поле - это тип протокола. В нашем случае ARP. По RFC - это 0800h:
Код (Text):
mov byte ptr [packetbuff+16],008h ;|- Protocol Type - ARP mov byte ptr [packetbuff+17],000h ;|-Третье поле соответствует длине физического адреса. Для Ethernet длина этого поля равна 6 байтам. Так и напишем:
Код (Text):
mov byte ptr [packetbuff+18],006h ;|- Hardware Address LengthЧетвертое поле - длина IP-адреса. Максимум 4 байта для IPv4:
Код (Text):
mov byte ptr [packetbuff+19],004h ;|- Protocol Address LengthПятое поле - это тип ARP пакета. Установим как ARP запрос (по RFC 0001h):
Код (Text):
mov byte ptr [packetbuff+20],000h ;|- Operation (Opcode) - Type ARP mov byte ptr [packetbuff+21],001h ;|Шестое поле - наш физический адрес. Установим в 0 все байты этого поля:
Код (Text):
mov byte ptr [packetbuff+22],000h ;|- mov byte ptr [packetbuff+23],000h ;| mov byte ptr [packetbuff+24],000h ;| Sender Hardware Address mov byte ptr [packetbuff+25],000h ;| mov byte ptr [packetbuff+26],000h ;| mov byte ptr [packetbuff+27],000h ;|-Седьмое поле - наш IP-адрес. Думаете, свой адрес будем писать? Ошибаетесь. Запишем сюда IP-адрес самого получателя, который мы ввели в поле ввода. Он содержится в соответствующем на виде в регистре eax (ранее подготовили функцией inet_addr()). Сдвигаем циклически этот регистр, помещая значение побайтно в четырех байтное поле:
Код (Text):
mov byte ptr [packetbuff+28],al ;|- ror eax,8 ;| mov byte ptr [packetbuff+29],al ;| Sender Protocol Address ror eax,8 ;| mov byte ptr [packetbuff+30],al ;| ror eax,8 ;| mov byte ptr [packetbuff+31],al ;|-Восьмое поле - физический адрес получателя. Вспомните, что мы туда писали в Ethernet кадре:
Код (Text):
mov byte ptr [packetbuff+32],0FFh ;|- mov byte ptr [packetbuff+33],0FFh ;| mov byte ptr [packetbuff+34],0FFh ;| Target Hardware Address mov byte ptr [packetbuff+35],0FFh ;| mov byte ptr [packetbuff+36],0FFh ;| mov byte ptr [packetbuff+37],0FFh ;|-Девятое поле - IP-адрес получателя заполним его корректно. Сдвигая в прошлый раз eax побайтно, сдвигаем и в этот раз так же:
Код (Text):
ror eax,8 mov byte ptr [packetbuff+38],al ;|- ror eax,8 ;| mov byte ptr [packetbuff+39],al ;| Target Protocol Address ror eax,8 ;| mov byte ptr [packetbuff+40],al ;| ror eax,8 ;| mov byte ptr [packetbuff+41],al ;|-Кадры заполнены. Еще можете дописать в конец сообщение, например:
Код (Text):
mov byte ptr [packetbuff+42],'M' mov byte ptr [packetbuff+43],'a' mov byte ptr [packetbuff+44],'r' mov byte ptr [packetbuff+45],'i' mov byte ptr [packetbuff+46],'a' mov byte ptr [packetbuff+47],'n' mov byte ptr [packetbuff+48],'n' mov byte ptr [packetbuff+49],'a'Пакет сформирован. Необходимо его послать. Первая функция, которая поможет нам это сделать - PacketInitPacket(). Ее прототип следующий:
Код (Text):
PacketInitPacket PROTO STDCALL :DWORD, :DWORD, :DWORDПервый параметр - это описатель структуры PACKET.
Второй параметр - адрес буфера содержащего пакет.
Третий параметр - длина пакета.
Вызываем функцию:
push 50 lea eax,packetbuff push eax push lpPacket call PacketInitPacket
еще одна функция PacketSetNumWrites() (необязательная). Она устанавливает количество отправленных за 1 сеанс пакетов. По умолчанию оно равно единице. Можете указать больше. Функция имеет прототип:
PacketSetNumWrites PROTO STDCALL :DWORD, :DWORD
Первый параметр - описатель адаптера.
Второй параметр - количество пакетов.
Устанавливаем:
Код (Text):
push 1 push lpAdapter call PacketSetNumWritesтеперь пошлем пакет 100 раз с интервалом 1 пакет в пол секунды (используем для ожидания функцию Sleep()). Для отсылки используется ключевая функция PacketSendPacket(). Она имеет следующий прототип:
PacketSendPacket PROTO STDCALL :DWORD, :DWORD, :DWORD
Первый параметр - это описатель устройства.
Второй параметр - описатель структуры PACKET
Третий параметр - режим выполнения операции (синхронный \ асинхронный). В нашем случае установим синхронный режим. Пока 100 пакетов не отправится - функция блокирует приложение:
Код (Text):
mov cx,100 ckl: push TRUE push lpPacket push lpAdapter call PacketSendPacket invoke Sleep,500 loop cklВсю работу сделали. Необходимо прибрать за собой. Функция PacketFreePacket() освобождает память, заказанную под структуру PACKET. Имеет прототип:
PacketFreePacket PROTO STDCALL :DWORD
Единственный параметр - это описатель структуры PACKET
Вызываем:
Код (Text):
push lpPacket call PacketFreePacketПоследнее действие с нашей стороны - это освобождение адаптера. Функция PacketCloseAdapter() высвобождает структуру ADAPTER. Прототип функции:
Код (Text):
PacketCloseAdapter PROTO STDCALL :DWORDЕдинственный параметр - описатель адаптера.
Код (Text):
push lpAdapter call PacketCloseAdapterВсе сделали - выходим:
Код (Text):
jmp End_ Error:Сюда мы попадем, если на этапе работы программы возникла ошибка. Просто выведем сообщение. Для анализа ошибки используйте функцию GetLastError(). Ограничимся одним пустым сообщением:
Код (Text):
invoke MessageBox,hWnd,NULL,NULL,NULL End_:Вот и весь код.
Результаты работы нашей программы посмотрим в сниффере. Установим его в режим захвата пакетов с уровня Ethernet и поймаем наши пакеты:
Что послали, то и получили.
- Эпилог -
Вы наверняка задались вопросом - "почему именно такой пакет, и именно такие параметры". Ответ очень прост. В реализации протокола ARP есть недочеты. Это одна из них - так называемая разновидность ARP-poisoning. Хост, принимая наши пакеты, отключает себя от сети с ошибкой "Конфликт IP адреса". А все потому, что указанный физический адрес назначения в пакете является широковещательным, а физический адрес источника - ложный. ARP запрос в этом случае просто говорит хосту (то есть всем хостам), получившему этот пакет, что такой IP-адрес в сети уже существует. В итоге хост с этим IP-адресом не сможет ни с кем соединится.
Firewall`ы, как ни странно, видят эти пакеты. Но нам они их не показывают. Ни один из попадавшихся мне широко распространенных firewall`ов не позволял устанавливать для ARP протокола какие-либо правила - например проверка пакета по шаблону или проверка на предмет левых MAC адресов. Выйти из такой ситуации можно разными способами. Например, есть возможность закрыть таблицу ARP (сделать ее статической). Но для большой сети это неприемлемо. И мало того - операционная система Windows 9x-2000 все равно будет обновлять статическую ARP таблицу. Второй вариант - написание фильтра трафика канального уровня на предмет зловредных пакетов.
Под конец добавлю, что я не несу ответственности за то, как будет использован этот материал. Вся информация в этой статье несет сугубо информативный характер в помощь администраторам.
Используемая литература:
- "TCP/IP", Dr. Sidnie Feit. McGraw-Hill.
- "Microsoft Windows Server 2003 TCP.IP Protocols and Services Technical Reference", Joseph Davies & Thomas Lee. MS Press.
- RFC 826 (Address Resolution Protocol).
Ссылки:
Файл к статье © TermoSINteZ
Взгляд на сеть из другой галактики
Дата публикации 15 ноя 2005