allasm.ru

    Меню

 

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

Для использования этой статьи на практике вам необходимы следующие инструменты:

  1. Библиотека winpcap \ libpcap (в зависимости от того, на какой операционной системе вы будете работать). Мой выбор Windows, да простят меня фанаты Linux.
  2. Компилятор. Я буду использовать masm32 v8.2.
  3. Любой маломальский редактор.
  4. Сниффер сетевого трафика. Выбирайте сами. Я использовал IP Promiscuous Sniffer.

Обязательно скачайте полную и последнюю версию выше предложенной библиотеки c набором lib файлов.

"..Умереть ничего - если выпить немного.."

- Общая информация -

В былые времена, когда компьютеры были действительно большими, а программисты были действительно умными, организации начали придумывать различные технологии обмена данными, были созданы первые сети. Был придуман протокол TCP/IP. Ученые работали над вопросом построения надлежащей модели этого протокола. Такую модель разработали и назвали, как модель OSI (Open System Interconnection - Взаимодействие открытых систем). OSI была разработана в рамках ISO (International Organization for Standardization - Международная организация по стандартизации). Подробнее об этом читайте соответствующую литературу (см. в конце статьи). OSI и TCP\IP модели делятся на уровни. Каждый уровень имеет свою функцию и свое назначение.

TCP и OSI

К физическому уровню относят разъемы, кабели - носители информации в общем смысле, сигналы.

Уровень связи данных (еще так называемый канальный уровень) организует данные в кадры (frame). Данные обворачиваются заголовочной информацией о физических адресах источника и приемника, тип протокола (связан с интерфейсом сетевой карты (например, Ethernet)). Очень интересно, ведь, получив доступ к этому уровню, можно обволакивать свои данные в дополнительные заголовки или изменять уже существующие заголовки, используемые сетевыми картами в вашей сети. Во многих книгах и руководствах говорят, что об этом уровне можно не заботиться, но именно на этот уровень я обращу все ваше внимание.

Сетевой уровень IP (Internet Protocol). Данные этого протокола пересылаются в элементах называемых датаграммами (datagram). В такой датаграмме содержится заголовок IP содержащий IP-адреса приемника и источника, длину пакета и другие параметры (подробнее смотрите в предложенной литературе ниже).

Транспортный уровень включает в себя TCP (Transmission Control Protocol) и UDP (User Datagram Protocol) протоколы передачи данных. Эти протоколы не являются темой нашей статьи (подробнее о них можно узнать предложенной литературе ниже).

Три верхних уровня соответствуют уровню приложений. Это может быть браузер, FTP сервер, Telnet.. Собственно это нам не интересно, с точки зрения программирования.

Рассмотрим, как пакетируются данные, в зависимости от уровня TCP/IP:

Уровни TCP/IP

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

- Чем черт не шутит -

Рассмотрим канальный уровень поподробнее. Для этого поставим перед собой задачу. Пусть нам необходимо послать пакет канального уровня. ARP (Address Resolution Protocol) протокол прекрасно подойдет для этой цели.

Прежде чем хосты в сети Ethernet откроют соединение, они обязаны знать физические адреса назначения. Для этой цели и используется ARP протокол. Он осуществляет трансляцию между IP-адресом и соответствующим ему физическим адресом. На хосте содержится таблица, называемая ARP таблица. Она содержит в себе список IP-адресов и соответствующие им физические адреса. Существует два типа трансляции - динамический и статический.

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

В статической трансляции никаких широковещательных данных не посылается, и никакие данные не принимаются. Вся ARP таблица заполняется самим пользователем системы.

ARP пакет состоит из Ethernet кадра и ARP кадра. Рассмотрим подробнее эти кадры:

Ethernet

Первое поле Preamble можно опустить - оно заполняется на аппаратном уровне сетевой картой, его не видно в снифферах. Считаем что начало кадра - это следующее поле.

Второе поле Destination Address - здесь находится информация о физическом (далее MAC) адресе хоста-приемника.

Третье поле Source Address - информация о MAC (Media Access Control) адресе источника.

Четвертое поле EtherType - тип Ethernet среды (это может быть Ethernet, Token Ring, Frame relay, ATM).

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

Шестое поле FCS - это циклическая контрольная сумма всего пакета данных. Является замыкающим полем пакета и заполняется на аппаратном уровне сетевой картой. Это поле нельзя увидеть в сниффере.

ARP

Первое поле 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 файла (смотрите в архиве, в конце статьи):

	include 
	includelib 

В packet.inc файле содержатся объявления прототипов используемых нами функций и структур. Файл packet.lib - это файл библиотеки импорта. Как уже говорилось выше - скачайте его у разработчиков или создайте сами из динамической библиотеки packet.dll.

Объявим используемые переменные:

	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

очищаем буфер для дальнейшего использования:

		mov AdapterLen, sizeof AdapterName		
		invoke RtlZeroMemory,addr AdapterName,AdapterLen

для обращения к сети необходимо узнать имя сетевого адаптера в системе. Функция PacketGetAdapterNames(), экспортируемая из packet.dll (как и все другие начинающиеся со слова Packet), позволяет получить имена всех адаптеров, установленных в системе. Ее прототип следующий:

PacketGetAdapterNames PROTO STDCALL :DWORD, :DWORD

Первый параметр - это адрес буфера куда запишутся имена всех адаптеров.
Второй параметр - Длина буфера.

Вызовем ее:

		lea eax,AdapterLen
		push eax
		lea edi,AdapterName
		push edi
		call PacketGetAdapterNames

обязательно проверяем, не возвратила ли функция ошибку:

		cmp eax,FALSE
		je Error

возвращаемый формат строки имеет следующий вид:

<имя первого устройства>0<имя второго устройства>0<…..>00<описание первого устройства>0<описание второго устройства>0<…..>0000000…..

Возьмем первое попавшееся устройство и запишем его в отдельный буфер (без его описания):

		lea esi,AdapterName		    	
		lea edi,AdapterBuff
	Next:	
		movsb 
		cmp byte ptr [esi],0
		jne Next

покажем имя нашего адаптера:

		invoke MessageBox,hWnd,addr AdapterBuff,addr UsAd,MB_OK

далее открываем адаптер. Фактически winpcap скрывает от нас все тонкости и работает с адаптером как с файлом. Для открытия адаптера используем функцию PacketOpenAdapter(). Она имеет следующий прототип:

PacketOpenAdapter PROTO STDCALL :DWORD

Единственный параметр - это адрес буфера, содержащий имя адаптера. Вызываем:

		lea eax,AdapterBuff
		push eax	
		call PacketOpenAdapter

проверяем, возвратился ли описатель адаптера или произошла ошибка:

		assume eax:ptr ADAPTER
                        .IF [eax].hFile == INVALID_HANDLE_VALUE
			jmp Error
		.ENDIF
		assume eax:nothing

сохраняем описатель адаптера:

		mov lpAdapter,eax

Далее необходимо выделить память для структуры PACKET. Используем функцию PacketAllocatePacket(). Она не имеет параметров. Сразу проверим на ошибку. Если все прошло гладко, то сохраним описатель структуры PACKET:

		call PacketAllocatePacket
		cmp eax,0
		je Error
		mov lpPacket,eax

считываем введенный нами IP-адрес назначения пакета и приведем сразу в little endian формат:

		invoke GetWindowText,hwndEdit,addr IPstr,10h
		invoke inet_addr,addr IPstr

Все подготовлено - можно начинать формировать пакет. Вспомним предыдущие рисунки. Первым заполняется Ethernet кадр. Укажем в нем, что этот пакет должен быть широковещательным. Это поле должно содержать 1 в каждом бите:

		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:

		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. Так и установим:

		mov byte ptr [packetbuff+12],008h  ;|- EtherType
		mov byte ptr [packetbuff+13],006h  ;|-

С Ethernet кадром разобрались. Заполним ARP кадр. Первое поле - тип канала связи. Для Ethernet надо установить в 0001h:

		mov byte ptr [packetbuff+14],000h  ;|- Hardware Type  - Ethernet
		mov byte ptr [packetbuff+15],001h  ;|-

Второе поле - это тип протокола. В нашем случае ARP. По RFC - это 0800h:

		mov byte ptr [packetbuff+16],008h  ;|- Protocol Type  - ARP
		mov byte ptr [packetbuff+17],000h  ;|-

Третье поле соответствует длине физического адреса. Для Ethernet длина этого поля равна 6 байтам. Так и напишем:

		mov byte ptr [packetbuff+18],006h  ;|- Hardware Address Length 

Четвертое поле - длина IP-адреса. Максимум 4 байта для IPv4:

		mov byte ptr [packetbuff+19],004h  ;|- Protocol Address Length

Пятое поле - это тип ARP пакета. Установим как ARP запрос (по RFC 0001h):

		mov byte ptr [packetbuff+20],000h  ;|- Operation (Opcode) - Type ARP
		mov byte ptr [packetbuff+21],001h  ;|

Шестое поле - наш физический адрес. Установим в 0 все байты этого поля:

		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()). Сдвигаем циклически этот регистр, помещая значение побайтно в четырех байтное поле:

 		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 кадре:

		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 побайтно, сдвигаем и в этот раз так же:

		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    	;|-

Кадры заполнены. Еще можете дописать в конец сообщение, например:

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(). Ее прототип следующий:

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

Первый параметр - описатель адаптера.

Второй параметр - количество пакетов.

Устанавливаем:

		push 1
		push lpAdapter
		call PacketSetNumWrites

теперь пошлем пакет 100 раз с интервалом 1 пакет в пол секунды (используем для ожидания функцию Sleep()). Для отсылки используется ключевая функция PacketSendPacket(). Она имеет следующий прототип:

PacketSendPacket PROTO STDCALL :DWORD, :DWORD, :DWORD

Первый параметр - это описатель устройства.

Второй параметр - описатель структуры PACKET

Третий параметр - режим выполнения операции (синхронный \ асинхронный). В нашем случае установим синхронный режим. Пока 100 пакетов не отправится - функция блокирует приложение:

		mov cx,100				
	ckl:	
		push TRUE
		push lpPacket
		push lpAdapter
		call PacketSendPacket
		invoke Sleep,500
		loop ckl

Всю работу сделали. Необходимо прибрать за собой. Функция PacketFreePacket() освобождает память, заказанную под структуру PACKET. Имеет прототип:

PacketFreePacket PROTO STDCALL :DWORD

Единственный параметр - это описатель структуры PACKET

Вызываем:

		push lpPacket
		call PacketFreePacket

Последнее действие с нашей стороны - это освобождение адаптера. Функция PacketCloseAdapter() высвобождает структуру ADAPTER. Прототип функции:

PacketCloseAdapter PROTO STDCALL :DWORD

Единственный параметр - описатель адаптера.

		push lpAdapter
		call PacketCloseAdapter

Все сделали - выходим:

		jmp End_
	Error:

Сюда мы попадем, если на этапе работы программы возникла ошибка. Просто выведем сообщение. Для анализа ошибки используйте функцию GetLastError(). Ограничимся одним пустым сообщением:

		invoke MessageBox,hWnd,NULL,NULL,NULL

	End_:	

Вот и весь код.

Результаты работы нашей программы посмотрим в сниффере. Установим его в режим захвата пакетов с уровня Ethernet и поймаем наши пакеты:

Захват пакетов с уровня Ethernet

Что послали, то и получили.

- Эпилог -

Вы наверняка задались вопросом - "почему именно такой пакет, и именно такие параметры". Ответ очень прост. В реализации протокола ARP есть недочеты. Это одна из них - так называемая разновидность ARP-poisoning. Хост, принимая наши пакеты, отключает себя от сети с ошибкой "Конфликт IP адреса". А все потому, что указанный физический адрес назначения в пакете является широковещательным, а физический адрес источника - ложный. ARP запрос в этом случае просто говорит хосту (то есть всем хостам), получившему этот пакет, что такой IP-адрес в сети уже существует. В итоге хост с этим IP-адресом не сможет ни с кем соединится.

Firewall`ы, как ни странно, видят эти пакеты. Но нам они их не показывают. Ни один из попадавшихся мне широко распространенных firewall`ов не позволял устанавливать для ARP протокола какие-либо правила - например проверка пакета по шаблону или проверка на предмет левых MAC адресов. Выйти из такой ситуации можно разными способами. Например, есть возможность закрыть таблицу ARP (сделать ее статической). Но для большой сети это неприемлемо. И мало того - операционная система Windows 9x-2000 все равно будет обновлять статическую ARP таблицу. Второй вариант - написание фильтра трафика канального уровня на предмет зловредных пакетов.

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

Используемая литература:

  1. "TCP/IP", Dr. Sidnie Feit. McGraw-Hill.
  2. "Microsoft Windows Server 2003 TCP.IP Protocols and Services Technical Reference", Joseph Davies & Thomas Lee. MS Press.
  3. RFC 826 (Address Resolution Protocol).

Ссылки:

  1. winpcap
  2. IP Promiscuous Sniffer
  3. masm32 v8.2.

Файл к статье

  [C] TermoSINteZ