| RSS



Меню

Bookmark and Share


Статистика
Ваш IP: 3.137.187.44
Вы используете: v





Сейчас на сайте:

Тех поддержка ->

Облако тэгов
ОС видио Tor Обратная сторона антенна 4.6 php libc rand() эксплоит Windows Server 2008 FreeBSD Jail Elastix QIP Virtual chroot Limit kernel proc sysctl Tune freeBSD bridge Boot LiveCD Disk Bluetooth GEO game DirectX emulator Python Shell червь Conficker вирус троян лаборатория касперского пиратство Apple iPhone Microsoft twitter социальная сеть анонимность лицензия Open Source уязвимость MySQL база данных Закон Франция Пират Skype мобильный Deutsche Telekom хакер trend micro кибератака Германия робот Персональные данные Ноутбук Интернет китай цензура windows vista acer Linux патент браузер Firefox Internet Explorer Opera Safari Intel Oracle патч Банкомат IBM США Dell Ford MAC контроль Internet кибербезопасность приговор Mozilla Chrome безопасность Госдума СМИ Windows 8 взлом Пентагон Украина Facebook Cisco Cloud Windows XP нетбук торрент музыка Биометрический Nokia Hardware Manager ФБР IP-адрес sms RSA java Google Captcha Symantec Спам Антивирус тест Anti-Malware Windows 7 операционная система windows провайдер авторское право rapidshare UNIX свиной грипп шантаж Дети ipod копирайт McAfee HTTPS icann студент Норвегия New York Times YouTube Warner Music КНДР Ubuntu AMD ATI касперский Россия РФ сервер хостинг Wi-Fi суд пароль блог фишинг одноклассники Медведев контрафакт мошенник sony Gps по JavaScript Хакеры Yahoo фас компьютер софт Минкомсвязи Сбой мошенничество Доктор ВЕб Вконтакте ie8 исходный код МВД фильтр порнография свобода слова казахстан Autodesk сисадмин Gmail кредитная карта LiveJournal шифрование Deep Purple банк HTML5 Нанотехнологии wikipedia выборы DNS bind KaZaA Android Basic атака Mac OS X домен ФСБ прокуратура уголовное дело ICQ Sophos Google Voice ошибка DARPA военные сайт турция конференция спамер Полиция Koobface Великобритания IRC белоруссия Грузия Bittorrent Европа Dr.WEB Linux Mint Билл Гейтс спецслужбы Royal Bank of Scotland смартфон Canonical F-Secure Symbian фильм Microsoft Office Новая Зеландия Adobe Австралия IDC Internet Explorer 9 iPad Ирландия поиск GOOGLE EARTH МТС Реклама слежка Mandriva BSD Zeus личные данные eset avast Avira G Data Software защита Defcon виртуализация dll LibreOffice Черный список BlackBerry индия Москва DVD социальные сети flash player paypal BitDefender email сертификат honda MasterCard Anonymous технологии IPv6 Ассанж Оптоволокно передача данных арест Fedora Samsung Иск Apache учетная запись iTunes исследование Cert Санкт-Петербург McDonald's SOPA PIPA Bioshock Infinite: Burial at Sea - ico Megaupload CES hotfile отчет приложение Инвестиции платформа DRM DDoS-атака роскомнадзор

Главная » Статьи » Общие Статьи

Интернет из нулевого кольца: программируем сеть в ядре Windows

Прочтя заголовок, ты, наверное, ожидаешь, что сейчас тебе во всех подробностях расскажут о работе с сетью в ядре Windows. Но задача это трудная по двум причинам: во-первых, из-за сложности темы; а во-вторых, из-за практически полного отсутствия осмысленных статей на эту тему на русском языке. Но, как говорят китайцы, «дорога в тысячу ли начинается с первого шага». Мы с тобой начнем ее прямо сейчас.

Строго говоря, в нулевом кольце для программера доступно два интерфейса для работы с сетью: TDI (Transport Data Interface) и NDIS (Network Device Interface Specification). Считается, что с TDI работать гораздо легче, чем с NDIS, что, впрочем, и понятно, ведь при работе с NDIS кодеру нужно будет самому реализовывать стек сетевых протоколов, общаться напрямую с сетевым адаптером, что нерационально. При работе же с TDI программер опирается на уже существующую в ядре реализацию TCP/IP-стека и задача его сильно упрощается.

Итак, TDI. Вообще-то, изначально он создавался для работы в usermode, однако затем разработчикам Windows что-то пришло в голову, и они сделали его доступным в режиме ядра. На данный момент TDI представляет собой набор документированных и не очень структур, функций, макросов, большинство из которых определено в DDK в заголовочных файлах <tdi.h> и <tdikrnl.h> (кстати, не забудь заинклюдить их при сборке драйвера). Рассматриваемый нами вариант прокатит на всей линейке Windows: от W2k до Vista. Со временем, судя по сообщениям Microsoft, TDI обречен на вымирание - со следующей ОС мелкомягкие намерены отказаться от его поддержки. Хотя кто их знает… говорят одно, делают другое, спецификацию пишут для чего-то вообще постороннего...

Как можно видеть на рисунке, структурно TDI занимает промежуточное место между реализацией NDIS и WinSock.


Сетевая инфраструктура в ядре

Программная модель TDI очень похожа на модель WinSock – работа происходит почти аналогично, только с другими функциями, макросами и структурами. Посредством TDI можно отправлять как TCP-, так и UDP-пакеты.

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

Алгоритм драйвера самого примитивного TDI–клиента будет выглядеть примерно так:

  1. создание дескриптора соединения;
  2. создание дескриптора локального адреса;
  3. привязка объекта «соединение» к «локальному адресу»;
  4. реализация функции соединения с удаленным хостом.

Это минимальный необходимый набор действий, осуществляющих «хандшейк» - «рукопожатие» с удаленным хостом.

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

Создаем объект «соединение»

Основное, что здесь нам потребуется, - это создать и получить хэндл устройства \\Device\\Tcp через ZwCreateFile, создать пустой объект FileObject и вызовом ObReferenceObjectByHandle связать их вместе. Предварительно для получения хэндла устройства \\Device\\Tcp нужно заполнить структуру FILE_FULL_EA_INFORMATION. И все! Смотрим нижеследующий код (объявление переменных и реализация общих для всех функций моментов намеренно опущено, потому что журнал не резиновый - смотри исходник на диске).

NTSTATUS CreateConnection(PHANDLE Handle, PFILE_OBJECT *FileObject)
{
Ea = (PFILE_FULL_EA_INFORMATION)&DataBlock;
Ea->EaNameLength = TDI_CONNECTION_CONTEXT_LENGTH;
Ea->EaValueLength = sizeof(CONNECTION_CONTEXT);
memcpy(Ea->EaName, TdiConnectionContext, Ea->EaNameLength + 1);
*(CONNECTION_CONTEXT*)(Ea->EaName +
(Ea->EaNameLength + 1)) = (CONNECTION_CONTEXT)conn_context;
Status = ZwCreateFile(Handle, FILE_READ_EA | FILE_WRITE_EA, &Attr,
&IoStatus, 0, FILE_ATTRIBUTE_NORMAL, 0,
FILE_OPEN, 0, Ea, sizeof(DataBlock));
return ObReferenceObjectByHandle(*Handle, GENERIC_READ |
GENERIC_WRITE, 0, KernelMode, (PVOID
*)FileObject, 0);
}

Создаем локальный адрес

Фактически здесь происходит то же самое, что и при создании соединения, но теперь мы дополнительно заполняем структуру TA_IP_ADDRESS. Ее описание ты легко найдешь в DDK или в Сети. Можно заполнять поля самостоятельно, как показано ниже (например, поле sin_port - порт, который будет открыт на локальной машине при установке соединения), или оставить системе возможность самой назначить номер порта. При самостоятельном заполнении sin_port можно использовать такой макрос:

HTONS(a) (((0xFF&a)<<8) + ((0xFF00&a)>>8)).

После выполнения этой функции в системе будет создан объект «локальный адрес»:

NTSTATUS CreateAddress(PHANDLE Handle, PFILE_OBJECT *FileObject)
{
Ea = (PFILE_FULL_EA_INFORMATION)&DataBlock;
memcpy(Ea->EaName, TdiTransportAddress, Ea->EaNameLength + 1);
Ea->EaNameLength = TDI_TRANSPORT_ADDRESS_LENGTH;
Ea->EaValueLength = sizeof (TA_IP_ADDRESS);
Sin = (PTA_IP_ADDRESS)(&Ea->EaName + Ea->EaNameLength + 1);
Sin->TAAddressCount = 1;
Sin->Address[0].AddressLength = TDI_ADDRESS_LENGTH_IP;
Sin->Address[0].AddressType = TDI_ADDRESS_TYPE_IP;
Sin->Address[0].Address[0].sin_port = HTONS(номер_порта_на_локальной_машине);
Sin->Address[0].Address[0].in_addr = 0;
RtlZeroMemory(Sin->Address[0].Address[0].sin_zero,
sizeof Sin->Address[0].Address[0].sin_zero);
Status = ZwCreateFile(Handle, FILE_READ_EA |
FILE_WRITE_EA, &Attr, &IoStatus, 0,
FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN, 0, Ea, sizeof (DataBlock));
return ObReferenceObjectByHandle(*Handle, GENERIC_READ
| GENERIC_WRITE, 0, KernelMode, (PVOID
*)FileObject, 0);
}

Узелок на память...

Теперь необходимо связать оба созданных файловых объекта вместе.

Первое - универсальная функция TdiBuildInternalDeviceControlIrp, в зависимости от переданных ей параметров (в чем убедимся далее) создающая IRP-пакет, который передается функции TdiBuildAssociateAddress. Все дальнейшее взаимодействие будет происходить именно через этот IRP-пакет, который затем вызовом IoCallDriver будет передан нижележащему драйверу в стеке.

Вообще, рекомендуется прикрутить ко всему этому эвент на тот случай, если обработка IRP-пакета попадет в очередь, поскольку Windows - очень занятая система и, для того чтобы попасть на прием к Его Величеству Ядру, приходится выстаивать в очередях :). О работе с IRP-пакетами можно почитать статьи Four-F на wasm.ru.

Связываем созданные объекты вместе:

NTSTATUS Bind(PFILE_OBJECT FileObject, HANDLE Address)
{
DeviceObject = IoGetRelatedDeviceObject(FileObject);
Irp = TdiBuildInternalDeviceControlIrp(TDI_ASSOCIATE_ADDRESS, DeviceObject,
FileObject, &Event, &IoStatus);
TdiBuildAssociateAddress(Irp, DeviceObject, FileObject, 0, 0, Address);
return IoCallDriver(DeviceObject, Irp);
}

Коннектимся...

Главное здесь - заполнить структуру TA_IP_ADDRESS, которая будет описывать тот удаленный хост, к которому нужно приконнектиться. При создании локального адреса мы это уже делали, только теперь поля sin_port и in_addr нужно заполнить вручную. При заполнении in_addr используй следующий макрос: INETADDR(a, b, c, d) (a + (b<<8) + (c<<16) + (d<<24)). Кроме того, нужно заполнить структуру TDI_CONNECTION_INFORMATION. Непосредственный коннект реализуется вызовом TdiBuildConnect и уже привычным IoCallDriver. В остальном все в нижеприведенном коде должно быть понятно.

Функция соединения с удаленным хостом:

NTSTATUS Connect(PFILE_OBJECT FileObject)
{
DeviceObject = IoGetRelatedDeviceObject(FileObject);
Irp = TdiBuildInternalDeviceControlIrp(TDI_CONNECT,
DeviceObject, FileObject, &Event, &IoStatus);
rem_adr.TAAddressCount = 1;
rem_adr.Address[0].AddressLength = TDI_ADDRESS_LENGTH_IP;
rem_adr.Address[0].AddressType = TDI_ADDRESS_TYPE_IP;
rem_adr.Address[0].Address[0].sin_port = HTONS(номер_порта);
rem_adr.Address[0].Address[0].in_addr = INETADDR(IP-адрес хоста);
RtlZeroMemory(rem_adr.Address[0].Address[0].sin_zero,
sizeof rem_adr.Address[0].Address[0].sin_zero);
remote_node.UserDataLength = 0;
remote_node.UserData = 0;
remote_node.OptionsLength = 0;
remote_node.Options = 0;
remote_node.RemoteAddressLength = sizeof(rem_adr);
remote_node.RemoteAddress = &rem_adr;
TdiBuildConnect(Irp, DeviceObject, FileObject,
0, 0, 0, &remote_node, 0);
return IoCallDriver(DeviceObject, Irp);
}

То, что мы рассмотрели, - это костяк TDI-клиента, который всего лишь устанавливает соединение с выбранным хостом. Но ведь необходимо еще и отправлять и получать данные! Для этого потребуется всего лишь предусмотреть отдельную реализацию функций TdiBuildSend TdiBildRecieve с переданными параметрами TDI_SEND и TDI_RECEIVE соответственно. Что и как они делают, смотри в DDK. Для совсем ленивых на диске лежит небольшой бонус, в котором можно найти вполне рабочий сорец драйвера с уже реализованными функциями посылки и получения данных (если все еще не ясно, пиши мне на мыло - объясню).

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

Первое. При реализации функции получения данных через вызов TDI_RECEIVE нужно предусмотреть возможность получения ВСЕХ данных, которые нам отправит сервер. Если этого не сделать, размер полученных данных будет ограничен лишь размером ПРЕДВАРИТЕЛЬНО выделенного буфера. Иначе говоря, если ты захочешь скачать 2 Мб, а размер буфера равен 0xff байт, то свои 0xff ты и получишь. Остальное будет обрезано, и процедура может просто подвиснуть. На мой взгляд, неплохим вариантом будет динамическое выделение буфера в памяти под размер передаваемых данных, который можно выдрать из поля Content-Length, которое, в свою очередь, тебе вернет правильный веб-сервер (и то это при условии, что ты работаешь именно с веб-сервером). Решение этой проблемы будет твоим домашним заданием.

Второе. Полученный буфер будет ВРЕМЕННО храниться в ЯДРЕ. Не пытайся искать скачанное в кэше Internet Explorer или где-то на диске – это бесперспективно до тех пор, пока ты туда его насильно не сохранишь. При этом не забывай освобождать память из-под буфера в ядре, иначе... сам знаешь, чем это может грозить.

Ну и третье. При реализации функции приема данных через вызов TDI_RECEIVE нужно помнить, что, в случае если она не дождется данных от сервера (мало ли что с удаленным хостом может случиться: заддосят негодяи или просто админ будет где-то резаться в линейку, забыв о серваке), функция может вернуть вечный STATUS_PENDING, то есть IRP-пакет будет стоять в очереди до тех пор, пока он не будет обработан. А как следствие, зависший драйвер. Поэтому в коде обязательно надо учесть подобное развитие ситуации. Особо продвинутым и тем, кто хочет досконально разобраться в работе TDI, рекомендую скачать tdfw-файрвол. Он open source, и его можно свободно найти в Сети.

Итак, мы рассмотрели простейший вариант TDI-клиента, который ничего особенного не делает. Целью статьи было показать, дорогой читатель, что работать с сетью в ядре Windows не так уж и сложно. А TDI на самом деле предоставляет для этого кучу возможностей: там и TDI_LISTEN, и TDI_ACCEPT, и много прочих вкусностей... MSDN и журнал «Хакер» помогут тебе! И помни: дорогу осилит идущий...

Злоключение

«Зачем все это нужно, ведь можно спокойно юзать библиотеки WinSock в user mode и при минимальных затратах решать поставленные задачи по работе с сетью?» - спросит читатель. Как однажды сказал мой хороший друг, «то, что ты написал, в C# можно уложить в 5 строк». Может быть. Но если обратить внимание, можно заметить, что разработчики сетевых решений безопасности (имеются в виду монстры типа Outpost Firewall) все настойчивее стремятся к контролю за действиями пользователя на уровне ядра. Реализовывать файрволы в user mode уже давно моветон. Работа с ядром, прямые манипуляции с объектами ядра - стандарт де-факто, и «Хакер» об этом писал уже неоднократно. А чем мы хуже? Тем более, почувствовав вкус ядра и пощупав его своими шаловливыми ручками, ты уже ни за что не захочешь возвращаться в user mode... Ring0 - суровая среда обитания, в которой выживают только самые настоящие брутальные падаваны, но... способных учеников ядро награждает щедрыми дарами!

INFO

Крайне желателен для посещения tarasc0.blogspot.com, чувак нереально много знает о работе с сетью в ring0. Достойная для прочтения статья по кодингу TDI в ядре «Kernel mode sockets library for the masses» лежит здесь: http://rootkit.com/newsread.php?newsid=416. Про wasm.ru, codeproject.com, ntkernel.com, MSDN, google.com/codesearch и koders.com я уж промолчу :).

Для отладки драйвера обязательно нужен будет отладчик ядерного уровня типа SoftICE или WinDBG, иначе на первых порах искать ошибки в коде будет сложновато. И обязательно раздобудь продвинутый анализатор сетевых пакетов для контроля за устанавливаемыми соединениями и анализа содержимого пакетов.

Осторожнее в работе с ядром! Грубые ошибки неминуемо ведут к BSOD'у, стрессу и смерти нервных клеток!

Категория: Общие Статьи | Добавил: aka_kludge (06.03.2009)
Просмотров: 1923 | Рейтинг: 5.0/1
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
    Главная      
...
На службе : дней

23:02
Обновить


Пользователи
aka_kludge
qwerty
LeadyTOR
aka_Atlantis
AdHErENt
mAss
Sissutr
hiss
DrBio
tHick

Поиск


Copyright tHR - TeAM 2025 г. admin: aka_kludge (ICQ:334449009) Moderator's: LeadyTOR, ... Яндекс.Метрика