Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР 129, СЕНТЯБРЬ 2009 г.

Тушим файрволы по-новому. Новые stealth-технологии на службе злобных программеров

Александр Эккерт (aleksandr-ehkkert@rambler.ru)

С завидной периодичностью мы публикуем различные кодерские методы обхода огненных стен. Оно и понятно – противостояние брони и снаряда продолжается, новые версии фаеров, проактивок и интегрированных систем безопасности выходят более чем часто. Не отстают от девелоперов и злобные кодеры. Они спускаются все ниже и ниже, в самые потаенные уголки ядра операционной системы.

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

Основы основ

Что такое минипорт? Это, упрощенно говоря, виртуальное представление интерфейса сетевого устройства - то, как видит сетевую карту ядро операционной системы. Минипорт является своеобразным посредником между чипсетом сетевого адаптера и ядром ОС. Драйверы минипорта напрямую (точнее, через HALHardware Abstraction Layer) взаимодействуют с сетевым адаптером на самом низком уровне, предоставляя некий абстрактный, общий для всех сетевых адаптеров, интерфейс к своему сетевому адаптеру другим драйверам и самой операционной системе.

В отличие от высокоуровневых драйверов, драйверы минипорта имеют две дополнительные функции, называемые ISR (Interrupt Service Routine – обработчик прерывания) и DpcForIsr (Deferred Procedure Call – процедура отложенных вызовов). ISR является высокоприоритетной процедурой, вызываемой при получении прерывания от устройства (например, при получении пакета из сети). В этой процедуре необходимо выполнить ряд самых необходимых действий, чтобы не задерживать надолго выполнение других процессов. В частности, – запретить устройству генерировать данное прерывание. DPC имеет более низкий приоритет при планировании потоков и вызывается непосредственно после ISR, если это требуется.

Обычно в DPC производится обмен данными с устройством (например, программирование контроллера DMA для переписывания вновь пришедшего кадра в оперативную память машины из буферной памяти устройства). Такой драйвер, как правило, входит в поставку самой сетевой карты.

При разработке драйверов сетевых карт разработчик должен четко следовать правилам, установленным той или иной версией NDIS, потому что, как ты помнишь, NDIS именно так и переводится – «Network Driver Interface Specification», то бишь – «спецификация интерфейса сетевого устройства». Впрочем, за более подробным описанием действий разработчиков я отсылаю тебя к MSDN. В настоящее время актуальной является как NDIS 5.1 (w2k/XP/2003), так и 6.0 (для Windows Vista).

Минипорт и все-все-все

В ядре минипорт сетевой карты представлен в виде структуры NDIS_MINIPORT_BLOCK. Она заполняется кучей всяческих данных при регистрации минипорта вызовом системной функции NdisMRegisterMiniport. Этот вызов происходит в драйвере сетевой карты при его загрузке.

NDIS_MINIPORT_BLOCK - одна из самых важных структур при работе с сетью на низком уровне (на уровне сетевой карты). Описание ее полей ты можешь найти в хидере «ndis.h», но надежнее будет сдампить ее из файла символов ndis.pdb утилитой типа pdbdump (http://pdbdump.sourceforge.net), потому что ее структура может меняться от билда к билду ОС и сильно зависит от версии NDIS. В этой статье подразумевается использование NDIS версии 5.1.

Тем не менее, решая, например, задачи фильтрации, мелкомягкие товарищи строго рекомендуют ограничиваться «законными» и документированными способами фильтрования сетевого трафика, поскольку эта структура критически важна для жизнедеятельности ОС Windows и лишний раз ее трогать не стоит. Но запретный плод сладок. Поэтому скажу с уверенностью, что все самое вкусное для хакера содержится именно в NDIS_MINIPORT_BLOCK. К примеру - функция PacketIndicateHandler, которая уведомляет драйвер протокола, что массив полученных пакетов доступен для дальнейшей обработки, и передает ей указатель на данный массив.

«Так почему бы не получить указатель на этот самый NDIS_MINIPORT_BLOCK и дальше работать с ним?», - спросишь ты. Вся загвоздка в том, что для этого нужно совершить очень много телодвижений в ядре Windows. Они больше напоминают танец слона в посудной лавке, что, естественно, не пройдет незамеченным для файрвола. Скажем, известный легальный способ получения такого указателя в обобщенном виде сводится к вызову NDIS-функции NdisRegisterProtocol, – она всегда перехватывается файрволами, проактивными защитами и антивирусами всех мастей. Что же нам делать?

В поисках утраченного KINTERRUPT'а

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

Как ты знаешь, все прерывания в ОС Windows представлены в ядре в виде таблицы дескрипторов (векторов) прерываний IDT(Interrupt Descriptor Table). При генерировании прерывания ядро просматривает IDT и по номеру прерывания передает управление по адресу, соответствующему номеру прерывания. Просмотреть IDT можно, к примеру, через отладчик WinDBG при помощи команды «!idt –a» - она выведет на экран дамп IDT.

Программным способом загрузка IDT осуществляется вызовом ассемблерной команды sidt и может выглядеть так:

Дампим IDT

typedef struct _IDT{
WORD wLimit;
DWORD dwBase;
}IDT, *PIDT;

VOID GetIDT(OUT PIDT pIdt){
__asm
{
MOV EAX, [pIdt]
SIDT [EAX]
}
}

Думаю, код понятен без слов: мы получили IDT во всей красе. Но что дальше? Если приглядеться внимательнее, то можно увидеть, что на самом деле при вызове прерывания система не вызывает функцию прерывания «железки» напрямую - прежде она должна позаботиться о многих других вещах. Система так и делает путем начального вызова функции KiInterruptTemplate.

Архитектура ОС Windows подразумевает, что при вызове системной функции KiInterruptTemplate система должна сохранить контекст потока и только затем вызвать функцию обработки прерывания KiDispatchInterrupt, которой в качестве параметра будет передан сохраненный указатель на «объект прерывания», представленный очень интересной структурой KINTERRUPT.

И только затем передается управление ISR той «железки», которая сгенерировала прерывание. Если диззасемблировать функцию KiInterruptTemplate, то именно это мы и увидим.

Плясать будем от структуры KINTERRUPT, тем более, она встречается как в NDIS 5.1, так и в NDIS 6.0 и для завладения ядром нам нужно получить указатель на нее. Как этого добиться?

Проще всего - по полученному указателю на функцию KiInterruptTemplate (часто это и есть выдранный адрес из IDT), диззассемблировать его на предмет поиска инструкции «mov edi, PKINTERRUPT». Если найдем, то далее можно сделать следующее: запомнить адрес KINTERRUPT и переходить ко второму способу (описанному ниже) либо заменить реальный KINTERRUPT на свой собственный, подменив ту самую функцию DpcForIsr - функцию отложенной обработки прерывания. Так мы получим доступ к данным, пришедшим по сети. Здесь рассматривать способ подмены DpcForIsr мы не будем из-за его громоздкости. Более подробно о нем можно прочитать в статье «Stealth Hooking: another way to subvert the Windows kernel» (http://phrack.org).

На случай, если мы не нашли KINTERRUPT минипорта сетевого адаптера, самое время вспомнить, что помимо всего прочего, в ядре существует переменная InterruptListEntry. Она представляет собой круговой список LIST_ENTRY, содержащий в себе все указатели на зарегистрированные в системе структуры KINTERRUPT. Достаточно найти первый попавшийся, а далее - ULONG KINTERRUPTLink = (ULONG)&(((PKINTERRUPT)(AddressHandler))->InterruptListEntry), где AddressHandler есть адрес любой структуры KINTERRUPT в системе.
Ну и как вариант напоследок - можно в ядре перехватить и дизассемблировать функцию обработки прерываний KiDispatchInterrupt; ей в качестве одного из параметров передается указатель на KINTERRUPT. Это отнюдь не легкий способ, поскольку требует отличного знания работы системы прерываний в ядре Win.

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

Удаление гланд через…

Итак, мы овладели KINTERRUPT. Заметь, не сделали при этом ничего такого, что могло бы привлечь внимание бдительных проактивок. Что дальше?

Если Мухаммед не идет к горе, значит, гора идет к Мухаммеду. Так поступим и мы, чтобы заполучить вожделенный NDIS_MINIPORT_BLOCK.

В ядре Windows значительное количество классов элементов представлены в виде устройств (да, девайсов), причем это касается не только физических устройств, но и устройств виртуальных. Тип «устройство», к примеру, имеют такие эфемерные вещи, как протоколы сети - TCP, IP, UDP - имеют вид «DeviceTcp». Просмотреть список девайсов, зарегистрированных в системе, можно с помощью замечательной утилиты «Windows Object Explorer» от Four-F.

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

Реализуем следующий алгоритм: сначала находим все зарегистрированные девайсы в системе, открыв директорию «Device». В цикле получаем указатели на девайсы вызовом ObOpenObjectByName; по полученному указателю вызовом ObReferenceObjectByHandle и IoGetDeviceObjectPointer получаем указатель на DEVICE_OBJECT, а затем оставляем те, которые имеют тип, равный 0х17, то есть FILE_DEVICE_PHYSICAL_NETCARD. Таким образом, мы получим все устройства, относящиеся к сетевому интерфейсу системы. Оговорюсь сразу, девайсов с типом FILE_DEVICE_PHYSICAL_NETCARD в системе, как правило, несколько и, чтобы найти нужный, придется их отфильтровывать. Например, по имени драйвера, которое хранится в поле DRIVER_OBJECT.DriverName по смещению DEVICE_OBJECT + 0x8. А вот уже дальше, скомбинировав полученные ранее знания, можно найти нужный нам минипорт. Все дело в том, что правила дизайна WDM (Windows Driver Model) требуют от разработчика драйверов создания некой структуры под названием «Device Extension», где программист должен хранить переменные, поля и структуры, описывающие ту или иную «железку», под которую пишется драйвер.

Если присмотреться, то в структуре DEVICE_OBJECT по смещению 0x28 имеется поле типа void*, которое, собственно, и указывает на созданную разработчиком структуру DeviceExtension. Только не следует ее путать с «официально прикрепленной» структурой DEVOBJ_EXTENSION, которая идет далее, по смещению 0хb0. Что это нам даст? А теперь внимание: как непреложное правило, разработчики в указанной структуре сохраняют полученный в ходе инициализации минипорта указатель на структуру NDIS_MINIPORT_INTERRUPT (который возвращается вызовом функции NdisMRegisterInterrupt). Если в свою очередь посмотреть на эту структуру, то мы увидим, что по смещению 0x34 находится искомый указатель на NDIS_MINIPORT_BLOCK. Причем тут полученный ранее адрес KINPTERRUPT?

Если еще раз глянуть на NDIS_MINIPORT_INTERRUPT, то легко увидеть, что первое поле в этой структуре и есть указатель на KINTERRUPT. Выходит, найдя разыменованный адрес KINTERRUPT, можно заполучить указатель на NDIS_MINIPORT_INTERRUPT… а дальше – все просто: добавляем 0x34 и получаем указатель на NDIS_MINIPORT_BLOCK.

Подведя черту вышеизложенному бреду воспаленного сознания, мы получим финальный алгоритм: ищем указатель на DEVICE_OBJECT с типом FILE_DEVICE_PHYSICAL_NETCARD, находим указатель на DeviceExtension и, начиная с данного адреса, шаримся по памяти в поисках разыменованного PKINTERRUPT, полученного одним из вышеприведенных способов. Если находим - радостно идем пить пиво. Если нет – что же, может быть, сегодня не день Бэкхема.

Заключение

Не скрою, способ сложнореализуем, ненадежен и требует определенной доработки. К тому же, он годится только для драйверов минипорта версии NDIS 5.1. Если ты используешь Vista, там уже будет вызов NdisMRegisterInterruptEx, который действует немного по-другому. Но об этом мы поговорим позже. В связи с довольно большим объемом кода, приводить его здесь непрактично, поэтому драйвер, реализующий указанный прием, ищи на диске.

Что же мы получили в результате? Мы получили большой бонус - драйвер, способный перехватывать и контролировать сетевые операции в ядре и корректировать их по мере необходимости. При этом мы не затронули те критически важные для операционной системы вещи, которые обычно контролируются проактивными защитами. Получилось не только очень элегантно, но вполне работоспособно. Я не ставил целью предоставить тебе готовое решение, а лишь хотел показать, что даже в самых суровых условиях можно найти для своей программы способ выживания в системе. Все, что для этого нужно - задать себе вопрос: «А что, если...?». Удачи!

CD

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

WWW

Чтобы лучше ориентироваться в ядрокопании на сетевом уровне и не только, советую сетевой журнал Phrack за номером 0x41. А для лучшего понимания работы системного механизма DPC, рекомендую статью «Advanced DPCs» М.Руссиновича (http://technet.microsoft.com).

INFO

Обязательно советую установить VisualDDK, которая здорово помогает при разработке драйверов (http://sourceforge.net/projects/visualddk). Ее ты также сможешь найти на диске.

Если есть вопросы - пиши, обсудим.

WARNING

Рассматриваемый в статье код справедлив для W2k/XP/2003. В Windows Vista эти приемы работать не будут.

Содержание
ttfb: 12.987852096558 ms