| RSS



Меню

Bookmark and Share


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





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

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

Облако тэгов
ОС видио Tor Обратная сторона антенна 4.6 PHP Эксплоит Windows Server 2008 qip Virtual chroot kernel proc sysctl tune FreeBSD bridge Boot Disk Bluetooth GEO game directx Emulator Python Shell DDoS червь Conficker вирус троян Лаборатория Касперского пиратство apple iPhone ИТ-отрасль Щеголев Microsoft экономический кризис Twitter социальная сеть анонимность Лицензия Open Source ASP.NET MVC уязвимость MySQL база данных файлообмен закон франция пират Skype мобильный Deutsche Telekom Хакер киберпреступник Trend Micro кибератака Германия робот утечка данных персональные данные ноутбук интернет Китай цензура ядро Linux Торвальдс Windows Vista Acer Linux патент браузер Firefox Internet Explorer Opera Net Applications Safari Intel Linux Foundation Moblin Oracle патч банкомат кардер HSM IBM X-Force Cofee сша кибервойна Эстония Dell ИТ-специалист хакерские атаки Pirate Bay контроль кибербезопасность язык программирования The Pirate Bay Пиратская партия утечка информации приговор Mozilla Chrome безопасность Госдума СМИ Windows 8 Баллмер взлом Пентагон ботнет Украина Facebook Cisco cloud Windows XP нетбук торрент музыка биометрический nokia ФБР IP-адрес CIPAV Comcast sms RSA java Google CAPTCHA Symantec спам конфиденциальная информация инсайдер Perimetrix антивирус тест Anti-Malware Windows 7 операционная система Windows провайдер авторское право RapidShare UNIX свиной грипп шантаж дети EFF BluWiki копирайт экстремизм Panda Security cloud computing McAfee Cybercrime Response Unit Bottle Domains HTTPS ICANN студент шпионское ПО Норвегия школьник New York Times XSS YouTube Warner Music кибершпионаж КНДР Ubuntu свободное ПО AMD ATI касперский Россия РФ сервер хостинг фальшивый антивирус Comodo CA Wi-Fi D-Link суд пароль блог фишинг Одноклассники медведев контрафакт мошенник штраф Sony GPS по Gumblar JAVASCRIPT хакеры вредоносное ПО Yahoo ФАС компьютер Софт MPAA кибероружие PandaLabs Red Hat Минкомсвязи сбой ASUSTeK Computer мошенничество Доктор Веб ВКонтакте Cyber-Arc исходный код PCI DSS МВД фильтр порнография BREIN свобода слова Казахстан GEMA Autodesk сисадмин Gmail кредитная карта кибермошенник LiveJournal шифрование криптография Deep Purple банк нанотехнологии Wikipedia zero-day ColdFusion выборы кража данных DNS BIND Android BASIC атака Black Hat Mac OS X Click Forensics Clampi домен фсб Прокуратура Уголовное дело icq Barrelfish киберпреступность Sophos AT&T ошибка Electa Gamma Knife OpenBSD DARPA военные Сайт Visual Studio 2010 .NET Framework 4 Chrome OS электронная почта турция конференция спамер FTC полиция российская ОС Koobface Великобритания БЕЛОРУССИЯ грузия BSA Bittorrent облачные вычисления Azure Европа Dr.Web Билл Гейтс спецслужбы Cryzip Живой Журнал Royal Bank of Scotland смартфон Canonical Pwn2Own F-Secure Symbian Hotmail фильм

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

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

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

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

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

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

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

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

Алгоритм драйвера самого примитивного TDI–клиента будет выглядеть примерно так:
создание дескриптора соединения;
создание дескриптора локального адреса;
привязка объекта «соединение» к «локальному адресу»;
реализация функции соединения с удаленным хостом.

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

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

Основное, что здесь нам потребуется, - это создать и получить хэндл устройства \\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, иначе на первых порах искать ошибки в коде будет сложновато. И обязательно раздобудь продвинутый анализатор сетевых пакетов для контроля за устанавливаемыми соединениями и анализа содержимого пакетов.

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

02:57
Обновить


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

Поиск


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