Тропа повествований привела меня к протоколу самого нижнего уровня из всех – протокол преобразования сетевых адресов ARP. Этот мир прикрыт железным куполом контролёра сетевой карты и большинству рядовых пользователей просто не нужен – пусть с ним разбираются инженеры. Но мы не входим в число рядовых, поэтому рассмотрим его с точки зрения не обывателей, а Low-Level программиста.
На самом деле, чем дальше мы по стеку отдаляемся от физических устройств типа кабель и сетевая карта, тем больше погружаемся в логическую абстракцию виртуальной памяти системы, которая уводит от реальной действительности. Например, вот как выглядит структурная схема типичного сетевого адаптера.. Он состоит из двух модулей: PHY (Physical, физический) и МАС (Medium Access Control) – их связывает независимый интерфейс MII или Media Independent Interface:
За исключением некоторых нюансов, модуль PHY не доступен для программиста и работает на аппаратном уровне. Главная его задача – обеспечить качественный доступ к линии передачи. Нужно сказать, что дело это не из простых и требует кодирования бинарного потока данных таким образом, чтобы максимально уменьшить в нём кол-во единиц, не потеряв при этом информации. В противном случае, даже на частоте 100 Мбит/сек (100Base-TX) частые всплески единиц превратят линию передачи в антенну, которая будет создавать помехи себе и окружающим.
Поэтому модулю PHY приходится напречь свои ягодицы, трижды кодируя один поток различными методами. В древних 10 Мбитных сетях "10Base-T" применялся только манчестерский код и этого было достаточно. С появлением 100Base-TX к нему добавили кодеры NRZI + MLT3 (двуполярная дифференциальная передача +/0/-, что снижает размах сигнала на линии), а гигабитные адаптеры 1000Base-FX кодируют фреймы ещё более продвинутым алгоритмом РАМ-5. Одним словом, чем дальше – тем страшнее:
В отличии от PHY, модуль МАС расположен ближе к оперативной памяти и подобраться к нему на программном уровне проще - он управляет массовым доступом к линии передачи. В своей тушке, МАС имеет клиента LLC - "Logical Link Control", который управляет логическими связями. Блок логики контролёра нельзя недооценивать – это мозг сетевой карты со своими регистрами среди которых выделяются: DA – регистры данных/адреса, Control – регистр управления и Status – информационный регистр (и по совместительству регистр ошибок). Доступ к регистрам контролёра осуществляется через конфигурационное пространство PCI.
• В модуле МАС, у стучащихся в карту фреймов проверяется контрольная сумма и если логика обнаружит несовпадение, контролёр отбрасывает пакет или генерит прерывание процессору об ошибке. Посредством прерываний сигнализируются и факты окончания приёма/передачи данных, чтобы процессор знал, когда можно забрать пакет у карты.
• МАС-контролёр имеет встроенный FIFO-буфер (First-In-First-Out – первым зашёл, первым выйдешь) для хранения принимаемых с уровня IP пакетов данных. Это 48-Кбайтный "Пинг-Понг" буфер, когда один и тот-же буфф используется и для приёма и для передачи, переключаясь в зависимости от режима работы.
• Когда карта работает в режиме передачи данных Transmit, блок фильтра добавляет в пакеты Ethernet-заголовок в виде: МАС-адресов отправителя/получателя, типа кадра, и его контрольной суммы – этот процесс известен как "инкапсуляция канального уровня". Именно на этом тёплом уровне беззаботно живёт протокол ARP, который всего-то меняет значение поля Type в заголовке на 0x0806, вместо дефолтных 0x0800. Заголовок Ethernet-кадра и возможные варианты его поля Type перечислены ниже (см.инклуд):
• Манипулируя регистром управления контролёра, сетевую карту можно перевести в неразборчивый режим "Promiscuous". В этом режиме фильтр-пакетов в LLC уже не проверяет MAC-адрес получателя, что позволяет собирать пакеты адресуемые не только нашему узлу, но и чужие. Эту фишку взяли на вооружение воины тёмных сил типа: Wireshark, Nmap, WinDump, etc. В следующей части мы попробуем написать свой сниффер, тогда и рассмотрим promisc подробней. Из доков на эту тему, можно полистать
В современной архитектуре материнских плат, шина PCI является основной транспортной магистралью, на которой висят буквально все физические девайсы. Для каждого PCI-устройства, система выделяет своё конфигурационное CFG-пространство в памяти размером мин 256 байт, и макс 4 Кб. Таким образом, каждый контролёр имеет свой базовый адрес в памяти, на который в cfg указывает его BAR – Base Address Register. Получив базу любого из контролёров, мы можем управлять его регистрами с одним ограничением – нужно спуститься на уровень драйверов, т.к. юзеру туда дорога закрыта.
5.4.0. Что такое МАС-адрес сетевого адаптера
Начиная с транспортного уровня(2) стека TCP/IP, все остальные уровни находятся в оперативной памяти компьютера – это протоколы IP, ICMP, IGMP, TCP, UDP и как саранча заселившая прикладной уровень(4) протоколы типа http, telnet, etc.. К прикладным протоколам мы подбираемся через их порты (макс. 65535 портов, а значит столько-же возможных протоколов). Однако на следующем уровне TCP/IP фигирирует IP-адрес и порты уходят уже на второй план. В свою очередь, когда приходит время передать пакет по физ.линии связи, теперь уже IP-адрес отправляется на скамейку запасных и системным механизмам требуется исключительно МАС-адрес конкретной сетевой карты.
МАС зашит в ПЗУ контролёра сети и представляет из себя уникальный номер, который присвоил данному контролёру, его производитель. Он кодируется шестью байтами (48-бит), где старшие три определяют производителя, и младшие три – порядковой номер изделия. В настоящее время, распределением физических адресов занимается международная организация IEEE - она и присваивает каждому производителю свой 24-битный код OUI (Organization Unique Identifier). Оставшиеся 24 бита МАС-адреса, производитель может использовать в качестве порядкового номера своего контролёра.
Трёх-байтный код OUI позволяет назначить ID одному из 16.777.215 производителей (значение 0xFFFFFF в десятичном). И соответственно каждый производитель может изготовить столько-же своих контролёров. Когда разработчик исчерпает этот лимит, он сообщает об этом в IEEE, и зарегистрировав у себя, она выделяет производителю новый код из своего резерва. В сети можно найти
Довольно быстро производители пришли к выводу, что организация IEEE берёт завышенную мзду за свои услуги. Надвигалась буря и чтобы оправдать доверие, IEEE отдаёт два бита из своего OUI в распоряжение протокола канального уровня(1). Теперь это "управляющие биты" и в старшей половине МАС-адреса под ID производителя осталось только 22, из 24-х бит:
• Бит(0) старшего байта является признаком или "индивидуального", или "группового" адреса I/G – нуль означает индивидуальный Unicast-адрес. Если-же он равен единице, значит кадр адресуется группе узлов – это т.н. Multicast (групповая рассылка). Если все 24-бита OUI заполнены единицами, то получателями являются все станции текущей сети – такую адресацию назвали Broadcast или широковещательной.
• Бит(1) структуры OUI назвали U/L. Этот маяк зажигается, когда аппаратный адрес карты задаёт сам администратор сети вручную (бывает и такое). Но в большинстве случаях он сброшен, что говорит о назначении МАС-адреса организацией IEEE.
Кстати судя по докам, в природе имеется два вида представления МАС-адреса – канонический (байты резделяются чертой, что характерно для Win), и бит-реверсный (байты разделяются двоеточием). Последний присущь Unix-системам, и тогда два управляющих бита становятся старшими в байте. Другими словами, одна и та-же карта в Win будет иметь один МАС, и она-же установленная в Unix будет иметь этот-же МАС, только в бит-реверсном виде:
5.4.1. Описание протокола ARP
Ну теперь ближе к делу…
Протокол ARP занимается тем, что по IP-адресу удалённого узла, вычисляет его МАС-адрес. Такое преобразование требуется когда пакет поступает с транспортного уровня(2) стека протоколов, на канальный уровень(1). В этот момент, протокол ARP посылает в сеть широковещательный "Broadcast запрос", в котором прописан только IP-адрес получателя. Все узлы сети получают этот запрос (см.рис.выше), и сравнивают свои IP с тем, что указан в запросе. Если IP не совпал, узел тупо игнорирует пакет, а если совпал – добавляет в него-же свой МАС-адрес, и возвращает его отправителю:
На рисунке, красным выделены поля, которые модифицируются в запросе. Удалённый узел меняет только код операции с единицы на 2, и вставляет в полученный запрос свой МАС. Отправитель анализирует ответ и по коду(2) делает вывод, что он валидный.
Теперь этот МАС заносится в ARP-таблицу и хранится в ней всего 10 минут (на винде). Если в течении этого времени к удалённому узлу обращений больше не было, его МАС из таблицы отправляется прямиком в топку (коммутаторы и маршрутизаторы хранят записи в ARP-таблицах дольше.. около 30 минут). ARP-запросы не могут маршрутизироваться, т.е. никогда не выходят за пределы маршрутизатора своей сети. Если запрашиваемый хост находится в удалённой сети, отправителю нужно узнать сначала аппаратный адрес порта маршрутизатора, и только потом выйти во-внешнюю сеть.
5.4.2. Практика – собираем свою ARP-таблицу
Под виндой, как и в случае с протоколом ICMP, сетевые сокеты на этом уровне не доступны, и мастдай предлагает нам альтернативу в виде нескольких API из той-же библиотеки iphlpapi.dll - здесь нужно выделить функцию SendARP(). Она отличается от остальным тем, что именно посылает широковещательный запрос, а не читает готовые данные из ARP-таблицы. Посмотрим на её прототип:
Как видим, функция простая.. Фиктивный source адрес-отправителя подставлять в неё мы не сможем – всё равно Win запихает туда валидный наш IP, вычислив его на автомате. Поэтому можно установить этот аргумент хоть в ноль. А вот IP-получателя нужно будет задать явно, используя функцию Winsock gethostbyname(). Но это скучно, поэтому мы пойдём другим путём и напишем небольшой ARP-спуфер.
Суть в том, что как упоминалось выше, ARP-запросы не покидают пределов своей сети. Поэтому мы узнаем свой IP и обнулив последний его байт, получим наименьший IP своей сети, т.е. применим к нему маску. Теперь начиная с нуля и до самого подвала(255) можем в цикле спуфить ARP-запросами свою сеть, и если узел с очередным IP обнаружится, то клюнет на запрос и вернёт нам свой МАС. Пример реализации подобного рода софтины, представлен ниже:
Плюс такого способа в том, что мы сможем получить МАС-адреса буквально всех участников в своей сети. Например в ARP-таблице и кэш нет аппаратных адресов тех узлов, к которым мы ещё ни разу не обращались – эта таблица заполняется динамически, по конкретным запросам и они хранятся там всего 10 минут. А в предложенном способе таблица уже не нужна, т.к. мы пелингуем каждый узел индивидуально. Для работы с таблицами есть свои функции из той-же iphlpapi.dll – вот из список и назначение:
5.4.3. Практика – собираем информацию об активных адаптерах
Бывают ситуации, когда нужно получить сведения обо всех имеющихся интерфейсах локальной машины (паспорт и данные сетевого адаптера), например в случае, если хотим прослушивать трафик конкретной сети, если их несколько. Здесь можно воспользоваться функцией GetAdaptersInfo() опять из той-же либы iphlpapi.dll. По сравнению с предыдущей SendARP() эта функция сложней, зато и возвращает намного больше информации, включая и МАС-адрес выбранного интерфейса:
Буфером для неё является структура IP_ADAPTER_INFO, которая имеет вложенную структуру IP_ADDR_STRING для хранения назначенного адаптеру IP-адреса в текстовом виде. Если посчитать кол-во символов в строке с макс. IPv4-адресом 255.255.255.255, то она занимает ровно один 16-байтный параграф памяти. Для хранения такой строки и ввели структуру IP_ADDR_STRING – вот её члены:
Ого.. Да сюда не только айпишник сбрасывается, а бонусум ещё и маска подсети – как раз то-что доктор прописал. Одному адаптеру может быть назначено несколько IP-адресов, поэтому структура имеет поле "Next", в котором лежит указатель на следующую строку с IP-адресом (т.н. список связей). Если IP один, то в поле next будет лежать дырка от бублика. Но это только вложенная структура.. Теперь посмотрим на основную:
Судя по содержимому, здесь уже есть где развернуться..
Тут и мак, и айпи, и шлюз, и dhcp-сервер и ещё куча всего – нормальный куш..
Чтобы не забивать пространство исходника, я не буду выводить все поля структуры, а сделаю упор только на основные. Если что не понятно, можете спросить у всё-знающего MSDN, или оставить вопрос в этой теме. Вот исходник и результат его работы на моём узле:
В следующей части поговорим о том, какие возможности предоставляют сырые RAW-сокеты под виндой, на чём и закончим обзор протоколов стека TCP/IP. В операционной системе имеются гораздо более интересные механизмы для низкоуровневых программистов ассемблера, чем сеть – их и подвергнем трепанации, медленно но верно продвигаясь в глубь. Снифте иногда этот раздел форума, может и найдёте для себя что-то полезное.
На самом деле, чем дальше мы по стеку отдаляемся от физических устройств типа кабель и сетевая карта, тем больше погружаемся в логическую абстракцию виртуальной памяти системы, которая уводит от реальной действительности. Например, вот как выглядит структурная схема типичного сетевого адаптера.. Он состоит из двух модулей: PHY (Physical, физический) и МАС (Medium Access Control) – их связывает независимый интерфейс MII или Media Independent Interface:
За исключением некоторых нюансов, модуль PHY не доступен для программиста и работает на аппаратном уровне. Главная его задача – обеспечить качественный доступ к линии передачи. Нужно сказать, что дело это не из простых и требует кодирования бинарного потока данных таким образом, чтобы максимально уменьшить в нём кол-во единиц, не потеряв при этом информации. В противном случае, даже на частоте 100 Мбит/сек (100Base-TX) частые всплески единиц превратят линию передачи в антенну, которая будет создавать помехи себе и окружающим.
Поэтому модулю PHY приходится напречь свои ягодицы, трижды кодируя один поток различными методами. В древних 10 Мбитных сетях "10Base-T" применялся только манчестерский код и этого было достаточно. С появлением 100Base-TX к нему добавили кодеры NRZI + MLT3 (двуполярная дифференциальная передача +/0/-, что снижает размах сигнала на линии), а гигабитные адаптеры 1000Base-FX кодируют фреймы ещё более продвинутым алгоритмом РАМ-5. Одним словом, чем дальше – тем страшнее:
В отличии от PHY, модуль МАС расположен ближе к оперативной памяти и подобраться к нему на программном уровне проще - он управляет массовым доступом к линии передачи. В своей тушке, МАС имеет клиента LLC - "Logical Link Control", который управляет логическими связями. Блок логики контролёра нельзя недооценивать – это мозг сетевой карты со своими регистрами среди которых выделяются: DA – регистры данных/адреса, Control – регистр управления и Status – информационный регистр (и по совместительству регистр ошибок). Доступ к регистрам контролёра осуществляется через конфигурационное пространство PCI.
• В модуле МАС, у стучащихся в карту фреймов проверяется контрольная сумма и если логика обнаружит несовпадение, контролёр отбрасывает пакет или генерит прерывание процессору об ошибке. Посредством прерываний сигнализируются и факты окончания приёма/передачи данных, чтобы процессор знал, когда можно забрать пакет у карты.
• МАС-контролёр имеет встроенный FIFO-буфер (First-In-First-Out – первым зашёл, первым выйдешь) для хранения принимаемых с уровня IP пакетов данных. Это 48-Кбайтный "Пинг-Понг" буфер, когда один и тот-же буфф используется и для приёма и для передачи, переключаясь в зависимости от режима работы.
• Когда карта работает в режиме передачи данных Transmit, блок фильтра добавляет в пакеты Ethernet-заголовок в виде: МАС-адресов отправителя/получателя, типа кадра, и его контрольной суммы – этот процесс известен как "инкапсуляция канального уровня". Именно на этом тёплом уровне беззаботно живёт протокол ARP, который всего-то меняет значение поля Type в заголовке на 0x0806, вместо дефолтных 0x0800. Заголовок Ethernet-кадра и возможные варианты его поля Type перечислены ниже (см.инклуд):
• Манипулируя регистром управления контролёра, сетевую карту можно перевести в неразборчивый режим "Promiscuous". В этом режиме фильтр-пакетов в LLC уже не проверяет MAC-адрес получателя, что позволяет собирать пакеты адресуемые не только нашему узлу, но и чужие. Эту фишку взяли на вооружение воины тёмных сил типа: Wireshark, Nmap, WinDump, etc. В следующей части мы попробуем написать свой сниффер, тогда и рассмотрим promisc подробней. Из доков на эту тему, можно полистать
Ссылка скрыта от гостей
В современной архитектуре материнских плат, шина PCI является основной транспортной магистралью, на которой висят буквально все физические девайсы. Для каждого PCI-устройства, система выделяет своё конфигурационное CFG-пространство в памяти размером мин 256 байт, и макс 4 Кб. Таким образом, каждый контролёр имеет свой базовый адрес в памяти, на который в cfg указывает его BAR – Base Address Register. Получив базу любого из контролёров, мы можем управлять его регистрами с одним ограничением – нужно спуститься на уровень драйверов, т.к. юзеру туда дорога закрыта.
5.4.0. Что такое МАС-адрес сетевого адаптера
Начиная с транспортного уровня(2) стека TCP/IP, все остальные уровни находятся в оперативной памяти компьютера – это протоколы IP, ICMP, IGMP, TCP, UDP и как саранча заселившая прикладной уровень(4) протоколы типа http, telnet, etc.. К прикладным протоколам мы подбираемся через их порты (макс. 65535 портов, а значит столько-же возможных протоколов). Однако на следующем уровне TCP/IP фигирирует IP-адрес и порты уходят уже на второй план. В свою очередь, когда приходит время передать пакет по физ.линии связи, теперь уже IP-адрес отправляется на скамейку запасных и системным механизмам требуется исключительно МАС-адрес конкретной сетевой карты.
МАС зашит в ПЗУ контролёра сети и представляет из себя уникальный номер, который присвоил данному контролёру, его производитель. Он кодируется шестью байтами (48-бит), где старшие три определяют производителя, и младшие три – порядковой номер изделия. В настоящее время, распределением физических адресов занимается международная организация IEEE - она и присваивает каждому производителю свой 24-битный код OUI (Organization Unique Identifier). Оставшиеся 24 бита МАС-адреса, производитель может использовать в качестве порядкового номера своего контролёра.
Трёх-байтный код OUI позволяет назначить ID одному из 16.777.215 производителей (значение 0xFFFFFF в десятичном). И соответственно каждый производитель может изготовить столько-же своих контролёров. Когда разработчик исчерпает этот лимит, он сообщает об этом в IEEE, и зарегистрировав у себя, она выделяет производителю новый код из своего резерва. В сети можно найти
Ссылка скрыта от гостей
, в которых перечислены коды OUI производителей. Пример из этой базы для моего МАС-адреса выглядит так:
C-подобный:
00-19-66 Asiarock Technology Limited
P.O. Box957, Offshore Incorporations Centre
Road Town Tortola VG
Довольно быстро производители пришли к выводу, что организация IEEE берёт завышенную мзду за свои услуги. Надвигалась буря и чтобы оправдать доверие, IEEE отдаёт два бита из своего OUI в распоряжение протокола канального уровня(1). Теперь это "управляющие биты" и в старшей половине МАС-адреса под ID производителя осталось только 22, из 24-х бит:
• Бит(0) старшего байта является признаком или "индивидуального", или "группового" адреса I/G – нуль означает индивидуальный Unicast-адрес. Если-же он равен единице, значит кадр адресуется группе узлов – это т.н. Multicast (групповая рассылка). Если все 24-бита OUI заполнены единицами, то получателями являются все станции текущей сети – такую адресацию назвали Broadcast или широковещательной.
• Бит(1) структуры OUI назвали U/L. Этот маяк зажигается, когда аппаратный адрес карты задаёт сам администратор сети вручную (бывает и такое). Но в большинстве случаях он сброшен, что говорит о назначении МАС-адреса организацией IEEE.
Кстати судя по докам, в природе имеется два вида представления МАС-адреса – канонический (байты резделяются чертой, что характерно для Win), и бит-реверсный (байты разделяются двоеточием). Последний присущь Unix-системам, и тогда два управляющих бита становятся старшими в байте. Другими словами, одна и та-же карта в Win будет иметь один МАС, и она-же установленная в Unix будет иметь этот-же МАС, только в бит-реверсном виде:
C-подобный:
0000.1000 – 0000.0000 – 0010.0111 – 0000.0000 - 0001.0100 – 1101.0101 = 08-00-27-00-14-D5 (Win)
0001.0000 : 0000.0000 : 1110.0100 : 0000.0000 : 0010.1000 : 1010.1011 = 10:00:E4:00:28:AB (Unix)
5.4.1. Описание протокола ARP
Ну теперь ближе к делу…
Протокол ARP занимается тем, что по IP-адресу удалённого узла, вычисляет его МАС-адрес. Такое преобразование требуется когда пакет поступает с транспортного уровня(2) стека протоколов, на канальный уровень(1). В этот момент, протокол ARP посылает в сеть широковещательный "Broadcast запрос", в котором прописан только IP-адрес получателя. Все узлы сети получают этот запрос (см.рис.выше), и сравнивают свои IP с тем, что указан в запросе. Если IP не совпал, узел тупо игнорирует пакет, а если совпал – добавляет в него-же свой МАС-адрес, и возвращает его отправителю:
На рисунке, красным выделены поля, которые модифицируются в запросе. Удалённый узел меняет только код операции с единицы на 2, и вставляет в полученный запрос свой МАС. Отправитель анализирует ответ и по коду(2) делает вывод, что он валидный.
Теперь этот МАС заносится в ARP-таблицу и хранится в ней всего 10 минут (на винде). Если в течении этого времени к удалённому узлу обращений больше не было, его МАС из таблицы отправляется прямиком в топку (коммутаторы и маршрутизаторы хранят записи в ARP-таблицах дольше.. около 30 минут). ARP-запросы не могут маршрутизироваться, т.е. никогда не выходят за пределы маршрутизатора своей сети. Если запрашиваемый хост находится в удалённой сети, отправителю нужно узнать сначала аппаратный адрес порта маршрутизатора, и только потом выйти во-внешнюю сеть.
5.4.2. Практика – собираем свою ARP-таблицу
Под виндой, как и в случае с протоколом ICMP, сетевые сокеты на этом уровне не доступны, и мастдай предлагает нам альтернативу в виде нескольких API из той-же библиотеки iphlpapi.dll - здесь нужно выделить функцию SendARP(). Она отличается от остальным тем, что именно посылает широковещательный запрос, а не читает готовые данные из ARP-таблицы. Посмотрим на её прототип:
C-подобный:
SendARP (
DestIP ; IP-адрес получателя
SrcIP ; IP-адрес отправителя
pMacAddr ; приёмный буфер под МАС
PhyAddrLen ; его длина (мин 8 байт)
);
Как видим, функция простая.. Фиктивный source адрес-отправителя подставлять в неё мы не сможем – всё равно Win запихает туда валидный наш IP, вычислив его на автомате. Поэтому можно установить этот аргумент хоть в ноль. А вот IP-получателя нужно будет задать явно, используя функцию Winsock gethostbyname(). Но это скучно, поэтому мы пойдём другим путём и напишем небольшой ARP-спуфер.
Суть в том, что как упоминалось выше, ARP-запросы не покидают пределов своей сети. Поэтому мы узнаем свой IP и обнулив последний его байт, получим наименьший IP своей сети, т.е. применим к нему маску. Теперь начиная с нуля и до самого подвала(255) можем в цикле спуфить ARP-запросами свою сеть, и если узел с очередным IP обнаружится, то клюнет на запрос и вернёт нам свой МАС. Пример реализации подобного рода софтины, представлен ниже:
C-подобный:
format pe console
entry start
include 'win32ax.inc'
;----------
.data
wsa WSADATA
capt db 13,10,' ARP example v0.1'
db 13,10,' **********************'
db 13,10,' MAC database..',13,10,0
ipMac db 13,10,' Host-> %s MAC-> %02X-%02X-%02X-%02X-%02X-%02X',0
succes db 13,10
db 13,10,' ~~~~~~'
db 13,10,' ARP successful..'
db 13,10,' Press any key for exit...',0
frmt db '%s',0 ; для scanf()
host dd 0 ; 4 байта для хранения IP-адреса
buff db 32 dup(0) ; под строку с IP-адресом (он-же приёмный буфф)
bSize dd 8 ; длина буфера
count dd 6 ; счётчик ARP-запросов
;----------
.code
start:
invoke WSAStartup,0x0101,wsa
cinvoke printf,capt
;//---- Вычисляем свой IP-адрес ---------
invoke gethostname,buff,32 ; возвращает локальное имя узла
invoke gethostbyname,buff ; возвращает адрес структуры "hostent"
mov eax,[eax+12] ;
mov eax,[eax] ;
mov eax,[eax] ; EAX = наш IP-адрес
mov [host],eax ; запомнить его..
;//---- Циклический ARP-спуфинг ---------
@mac: mov eax,[host] ; берём IP назначения
push eax ; +
invoke SendARP,eax,0,buff,bSize ;// отправляем ARP-запрос!
;//---- Вывод инфы об очередном IP/МАС ---------
;// функция SendARP() сбросила МАС клиента в наш буфер
pop eax ; -
invoke inet_ntoa,eax ; IP в строку
push eax ; запомнить указатель на неё
xor eax,eax ; МАС из буфера в строку
mov esi,buff ; адрес буфа
lodsb ; берём от туда первый байт
mov ebx,eax ; запоминаем в EABX
lodsb ; второй байт..
mov ecx,eax ;
lodsb ; третий..
mov edx,eax ;
lodsb ; червёртый
mov edi,eax ;
lodsb ; пятый..
mov ebp,eax ;
lodsb ; и шестой
mov esi,eax ;
; Вывести всю бодягу на консоль
pop eax
cinvoke printf,ipMac,eax,ebx,ecx,edx,edi,ebp,esi
;//---- Очистить 8 байтный буфф для следующего мак'а ---
mov ecx,2 ; повторов
mov edi,buff ; что очищаем
xor eax,eax ; чем забиваем (нуль)
rep stosd ; из EAX в EDI, ECX-раз..
;//---- Меняем IP на следующий ---------
rol [host],8 ; прокрутить влево на 8 бит
inc [host] ; увеличить на 1 мл.байт
ror [host],8 ; восстановить порядок вправо на 8 бит
mov [bSize],8 ; восстановить размер приёмного буфера
dec [count] ; уменьшить счётчик повторов цикла
jnz @mac ; повторить, если счётчик не нуль..
;//---- GAME over !!! ---------//
cinvoke printf,succes ; мессага ОК!
@exit: cinvoke scanf,frmt,frmt+5 ; ждём клаву..
invoke ExitProcess,0 ; на выход!
;//--- Секция импорта ------
section '.idata' import data readable ;
library kernel32,'kernel32.dll',\ ; импортируемые библиотеки
user32,'user32.dll',\
wsock32,'wsock32.dll',\ ; ^^^
msvcrt,'msvcrt.dll',\ ; ^^^
iphlp,'iphlpapi.dll' ; ^^^
import iphlp,SendARP,'SendARP'
import msvcrt, printf,'printf',scanf,'scanf'
include 'api\kernel32.inc'
include 'api\user32.inc'
include 'api\wsock32.inc'
Плюс такого способа в том, что мы сможем получить МАС-адреса буквально всех участников в своей сети. Например в ARP-таблице и кэш нет аппаратных адресов тех узлов, к которым мы ещё ни разу не обращались – эта таблица заполняется динамически, по конкретным запросам и они хранятся там всего 10 минут. А в предложенном способе таблица уже не нужна, т.к. мы пелингуем каждый узел индивидуально. Для работы с таблицами есть свои функции из той-же iphlpapi.dll – вот из список и назначение:
- GetIpNetTable() извлекает таблицу ARP на локальном компьютере.
- CreateIpNetEntry() создает запись ARP в таблице.
- DeleteIpNetEntry() удаляет запись ARP из таблицы.
- SetIpNetEntry() изменяет существующую запись ARP.
- FlushIpNetTable() удаляет все записи ARP для указанного интерфейса из таблицы.
5.4.3. Практика – собираем информацию об активных адаптерах
Бывают ситуации, когда нужно получить сведения обо всех имеющихся интерфейсах локальной машины (паспорт и данные сетевого адаптера), например в случае, если хотим прослушивать трафик конкретной сети, если их несколько. Здесь можно воспользоваться функцией GetAdaptersInfo() опять из той-же либы iphlpapi.dll. По сравнению с предыдущей SendARP() эта функция сложней, зато и возвращает намного больше информации, включая и МАС-адрес выбранного интерфейса:
C-подобный:
GetAdaptersInfo (
AdapterInfo ; адрес приёмного буфера
SizePointer ; адрес переменной с размером этого буфера
);
Буфером для неё является структура IP_ADAPTER_INFO, которая имеет вложенную структуру IP_ADDR_STRING для хранения назначенного адаптеру IP-адреса в текстовом виде. Если посчитать кол-во символов в строке с макс. IPv4-адресом 255.255.255.255, то она занимает ровно один 16-байтный параграф памяти. Для хранения такой строки и ввели структуру IP_ADDR_STRING – вот её члены:
C-подобный:
struct IP_ADDR_STRING
Next dd 0 ; указатель на сл.адрес
IpAddress db 16 dup(0) ; строка с IP-адресом
IpMask db 16 dup(0) ; строка с маской сети
Context dd 0 ; запись сетевой таблицы NTE
ends
Ого.. Да сюда не только айпишник сбрасывается, а бонусум ещё и маска подсети – как раз то-что доктор прописал. Одному адаптеру может быть назначено несколько IP-адресов, поэтому структура имеет поле "Next", в котором лежит указатель на следующую строку с IP-адресом (т.н. список связей). Если IP один, то в поле next будет лежать дырка от бублика. Но это только вложенная структура.. Теперь посмотрим на основную:
C-подобный:
struct IP_ADAPTER_INFO ;// Всего 640 байт
Next dd 0 ; указатель на сл.структуру в цепочке
ComboIndex dd 0 ; резерв..
AdapterName db 260 dup(0) ; UID адаптера (регистрационное число для реестра)
Description db 132 dup(0) ; имя адаптера в текстовом виде
AddressLength dd 0 ; длина МАС-адреса в байта =6
Address db 8 dup(0) ; МАС-адрес адаптера
Index dd 0 ; индекс (меняется при вкл/выкл)
Type dd 0
DhcpEnabled dd 0
CurrentIpAddr dd 0 ; резерв..
IpAddressList IP_ADDR_STRING ; вложеные структуры
GatewayList IP_ADDR_STRING ;
DhcpServer IP_ADDR_STRING ;
HaveWins dd 0 ; флаг активности WINS
PriWinsServer IP_ADDR_STRING ;
SecWinsServer IP_ADDR_STRING ;
LeaseObtained dd 0,0 ; структура "time_t" 8-байт
LeaseExpires dd 0,0 ; ^^^^^
ends
Судя по содержимому, здесь уже есть где развернуться..
Тут и мак, и айпи, и шлюз, и dhcp-сервер и ещё куча всего – нормальный куш..
Чтобы не забивать пространство исходника, я не буду выводить все поля структуры, а сделаю упор только на основные. Если что не понятно, можете спросить у всё-знающего MSDN, или оставить вопрос в этой теме. Вот исходник и результат его работы на моём узле:
C-подобный:
format pe console
entry start
include 'win32ax.inc'
;----------
.data
struct IP_ADDR_STRING
Next dd 0 ; указатель на сл.адрес
IpAddress db 16 dup(0) ; строка с IP-адресом
IpMask db 16 dup(0) ; строка с маской сети
Context dd 0 ; запись сетевой таблицы NTE
ends
struct IP_ADAPTER_INFO ;// Всего 640 байт
Next dd 0 ; указатель на сл.структуру в цепочке
ComboIndex dd 0 ; резерв..
AdapterName db 260 dup(0) ; UID адаптера
Description db 132 dup(0) ; имя адаптера
AddressLength dd 0 ; длина МАС-адреса в байта =6
Address db 8 dup(0) ; МАС-адрес адаптера
Index dd 0 ; индекс (меняется при вкл/выкл)
Type dd 0
DhcpEnabled dd 0
CurrentIpAddr dd 0 ; резерв..
IpAddressList IP_ADDR_STRING ; вложеные структуры
GatewayList IP_ADDR_STRING ;
DhcpServer IP_ADDR_STRING ;
HaveWins dd 0 ; флаг активности WINS
PriWinsServer IP_ADDR_STRING ;
SecWinsServer IP_ADDR_STRING ;
LeaseObtained dd 0,0 ; структура "time_t" 8-байт
LeaseExpires dd 0,0 ; ^^^^^
ends
capt db 13,10,' Adapters info v0.1'
db 13,10,' **********************',0
nicName db 13,10
db 13,10,' Name....: %s',0
nicUin db 13,10,' UIName..: %s',0
nicMac db 13,10,' MAC.....: ',0
nicIp db 13,10,' Addr....: %s',0
nicMask db 13,10,' Mask....: %s',0
nicGtw db 13,10,' Gateway.: %s',0
succes db 13,10
db 13,10,' ~~~~~~'
db 13,10,' Operation successful'
db 13,10,' Press any key for exit...',0
flag db 1 ; флаг последней структуры в списке
size dd 3072 ; размер приёмного буфера 3 Кб
len dd 0 ; будет смещением на сл.структуру
addrStr IP_ADDR_STRING ; определяем вложеную структуру
align 16 ; выравнивание буфера на параграф
buff IP_ADAPTER_INFO ; приёмный буфер! (до конца секции-данных)
;----------
.code
start:
;//--- Зовём на помощь функцию! ----------
;// она возвращает инфу сразу обо всех активных адаптерах
cinvoke printf,capt
invoke GetAdaptersInfo,buff,size
;//--- Цикл вывода информации на экран ---
@printInfo:
mov esi,buff ; адрес буффа
add esi,[len] ; прибавить текущее смещение
cmp dword[esi],0 ; проверить поле "Next" на нуль
jnz @next ; пропустить, если не нуль
dec [flag] ; значит последняя структура - сбросить флаг
@next: mov esi,buff.Description ; берём имя адаптера
add esi,[len] ; + текущее смещение
cinvoke printf,nicName,esi ; на консоль его!
mov esi,buff.AdapterName ; UIN адаптера
add esi,[len] ;
cinvoke printf,nicUin,esi ;
cinvoke printf,nicMac ; МАС выводить будем в цикле.
mov esi,buff.Address ; указатель на него
add esi,[len] ;
mov ecx,6 ; длина в байтах
and eax,0 ; EAX = 0 (сбросить все биты)
@mac: lodsb ; AL = очередной байт из ESI
push eax ecx esi ; + + + printf() портит все регистры
cinvoke printf,<'%02X:',0>,eax ; вывести очередной байт МАС'a
pop esi ecx eax ; - - -
loop @mac ; промотать ECX-раз..
mov esi,buff.IpAddressList.IpAddress ; IP через вложеную структуру
add esi,[len] ;
cinvoke printf,nicIp,esi ;
mov esi,buff.IpAddressList.IpMask ; маска подсети от туда-же
add esi,[len] ;
cinvoke printf,nicMask,esi ;
mov esi,buff.GatewayList.IpAddress ; шлюз по-умолчанию
add esi,[len] ;
cinvoke printf,nicGtw,esi ;
;//--- Перемещаем указатель на сл.структуру "IP_ADAPTER_INFO" в буфере ---
add [len],640 ; прибавить к смещению длину "IP_ADAPTER_INFO"
cmp [flag],0 ; проверить флаг на False
jnz @printInfo ; повторить, если не нуль..
;//--- Конец программы -----------
@exit: cinvoke printf,succes
cinvoke scanf,nicIp,nicIp ; ждём клаву..
invoke ExitProcess,0 ; на выход!
;//****************************************
;//--- Секция импорта ---------------------
;//****************************************
section '.idata' import data readable ;
library kernel32,'kernel32.dll',\ ; импортируемые библиотеки
user32,'user32.dll',\ ; ^^^
wsock32,'wsock32.dll',\ ; ^^^
msvcrt,'msvcrt.dll',\ ; ^^^
iphlp,'iphlpapi.dll' ; ^^^
import iphlp,GetAdaptersInfo,'GetAdaptersInfo'
import msvcrt, printf,'printf',scanf,'scanf'
include 'api\kernel32.inc'
include 'api\user32.inc'
include 'api\wsock32.inc'
В следующей части поговорим о том, какие возможности предоставляют сырые RAW-сокеты под виндой, на чём и закончим обзор протоколов стека TCP/IP. В операционной системе имеются гораздо более интересные механизмы для низкоуровневых программистов ассемблера, чем сеть – их и подвергнем трепанации, медленно но верно продвигаясь в глубь. Снифте иногда этот раздел форума, может и найдёте для себя что-то полезное.
Последнее редактирование: