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

Разработчики малвари постоянно жалуются, что их боты мрут, как мухи, аверы все сильнее закручивают гайки, и выживать в системе становится все сложнее и сложнее. Но не стоит отчаиваться – есть еще порох в пороховницах и ягоды в ягодицах. В подтверждение этому можно привести тот факт, что на широких просторах «тырнета» с определенной периодичностью отлавливаются все новые и новые зверушки типа Rustock’a или TDSS, которые формально уже должны были быть побеждены. А если поразмыслить о том, сколько еще тайных ботов будет выявлено, то можно с уверенностью утверждать, что в среднесрочной перспективе разработчики аверов и проактивных защит без работы точно не останутся.

 

Введение

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

Рассмотрим ситуацию: тебе всеми правдами и неправдами удалось установить драйвер в систему, и ты уже радостно потираешь свои руки, поскольку, как тебе говорили, проникновение в ядро делает тебя его повелителем. И плевать ты хотел на то, что какой-то авер что-то там контролирует.

Но не тут-то было! Проникновение в ядро, будь то установка своего драйвера или инфект уже установленного, при наличии в системе очень популярной антивирусной шушеры, не даст тебе ровным счетом ничего. Даже если ты наивно полагаешь, что исполнение твоего мегакода в ядре сделает тебя его полновластным Черным Властелином.

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

Почти каждый руткит, который можно найти в паблике, или который так или иначе попадал в мои руки, прямо или косвенно использовал системные сервисы – а куда без них? И ведь верно, уж такова архитектура ОС марки Windows, что и вирусы/руткиты, и аверы в ядре, кто для выживания, а кто – для охоты, используют один и тот же набор системных сервисов, куда входят те, которые можно найти в таблицах KeServiceDescriptorTable/KeServiceDescriptorTableShadow, экспорте ядра и важнейших драйверов.

 

KeServiceDescriptorTable – ключ ко всей системе

Ну, или почти ко всей системе. Первое, что сделает порядочный антивирус и файрвол, чтобы надавать по рукам мегакулхацкерам – установит свои перехватчики на KeServiceDescriptorTable – в ней содержатся адреса системных сервисов, таких как, например NtCreateFile, NtCreateProcess, NtCreateThread и т.д. Для чего это делается, я думаю, понятно – чтобы иметь возможность отслеживать вызовы потенциально опасных для стабильности ОС системных сервисов (работы с виртуальной памятью, реестром, привилегиями и тому подобными штуками). Как правило, такие функции перехватываются всеми аверами подряд, поэтому можно не сомневаться – если на машине стоит хоть что-нибудь антивирусное, будь уверен – в KeServiceDescriptorTable найдется пара-тройка, если не десяток перехватов системных сервисов. Их количество варьируется от одного (F-Secure, например, перехватывает только NtLoadDriver) до «до хрена и больше» (в случае с COMODO Internet Security или Outpost). Все это, естественно, не радует глаз начинающего системного прогера. К примеру, Kaspersky AV вообще грузит (грузил?) свою SSDT, благодаря чему все системные вызовы так или иначе проходят через его адресное пространство.

Не сильно от KeServiceDescriptorTable отличается теневая таблица KeServiceDescriptorTableShadow. Ее второй элемент содержит таблицу win32k.sys, драйвера, на котором в свою очередь держится вся графическая подсистема Windows. Win32k.sys содержит два типа сервисов: сервисы NtUser* и NtGdi*, первые отвечают за оконную подсистему, вторые за графику. Несмотря на неприглядное название и недокументированность, использование сервисов Win32k довольно популярно в андеграундной хакерской среде: посмотри на те сервисы, которые перехватывают аверы в KeServiceDescriptorTableShadow — NtUserFindWindowEx, NtUserQueryWindow, NtUserGetForegroundWindow – все они отвечают за работу с windows-окнами.

Для начала получим указатель на KeServiceDescriptorTable. Чтобы это сделать, просто добавь в свой код такую строку: extern PVOID KeServiceDescriptorTable. Получить адрес KeServiceDescriptorTableShadow немного сложнее, она не экспортируется, код для ее получения ты можешь найти на диске.

Итак, что же делать? Как убрать установленные хуки? Самый дешевый и сердитый способ – просто восстановить SSDT и выполнить свой код, а там – хоть трава не расти. Сделать это, несмотря на сложное название, довольно просто. Сначала мы через вызовы ZwOpenFile/ZwCreateSection/ZwMapViewOfSection найдем и промаппим в свою виртуальную память образ ядра ntoskernl.exe (ntkrnlpa.exe для многоядерных систем). Далее находим в полученной проекции ядра указатель на KeServiceDescriptorTable, после чего в цикле восстанавливаем адреса оригинальных обработчиков.

Находим адрес KiServiceTable

ULONG FindKiServiceTable( ULONG SdtPtr, ULONG Handle )
{
ULONG bFirst = 1, RvaPtr, i;
pointer = (char *)Handle;
pointer += 0x3c;
pointer = (char *)(*(ULONG *)pointer) + Handle + 0xA0;
reloc = (PIMAGE_BASE_RELOCATION)((char *)
(*(ULONG *)pointer) + Handle);
while ((bFirst) || (reloc->VirtualAddress))
{
bFirst = 0;
fixup = (PIMAGE_FIXUP_ENTRY)((ULONG)reloc + 8);
for (i = 0; i < ( reloc->SizeOfBlock - 8) >>
1; i++, fixup++)
if ( fixup->type == 3)
{
RvaPtr = reloc->VirtualAddress +
fixup->offset;
if (*(PULONG)( Handle + RvaPtr) –
0x400000 == SdtPtr)
{
if (*(PUSHORT)( Handle + RvaPtr - 2)
== 0x05c7)
return (*(PULONG)
( Handle + RvaPtr + 4) - 0x400000 + Handle);
}
}
*(PULONG)&reloc += reloc->SizeOfBlock;
}
return 0;
}

Минусы такого способа, я думаю, очевидны – практически все аверы и проактивки следят за состоянием своих хуков, установленных в SSDT и, обнаружив их отсутствие, тут же их восстановят. К тому же нельзя игнорировать тот факт, что все порядочные аверы перехватывают NtCreateSection и NtMapViewOfSection, без которых при проецировании ядра не обойтись. Что делать в этом случае, ты узнаешь ближе к концу статьи.

Код, реализующий восстановление SSDT и ShadowSSDT, ты можешь найти на диске.

 

Position number two

Как ни крути, выжить в условиях тотального контроля за системой очень и очень сложно.

При вызове системного сервиса ядро находит указатели на KeServiceDescriptorTable и KeServiceDescriptorTableShadow, которые хранятся в структуре KTHREAD. Мы дружно маппим ядро, находим в нем указатель на старую, неизмененную KeServiceDescriptorTable. Далее подменяем в «живой» KeServiceDescriptorTable указатели на системные сервисы своими, а в KTHREAD.ServiceDescriptorTable сохраняем указатель на найденную ранее неизмененную KeServiceDescriptorTable.

Вуаля! Этим незатейливым способом можно поставить в тупик не один продвинутый авер. Как это сделать в ядре? Чтобы получить указатель на структуру ETHREAD, частью которой является структура KTHREAD (не веришь – сам посмотри), можно вызвать функцию PsLookupThreadByThreadId (как вариант – PsLookupProcessThreadByCid), в которые нужно передать хендл потока, либо выполнить такой несложный код:

__asm push esi;
__asm mov esi, fs:[0x124].

После этого в регистре esi будет находиться искомый указатель на ETHREAD. Что делать дальше, я думаю, ты поймешь.

 

Position number три

Есть еще один вариант: оставить не слишком расторопный авер с носом (или что там у них между ног, я не помню :)). Вспомним тезисы, изложенные мной в одной из прошлых статей, посвященных организации виртуальной памяти в Windows. В частности, там шла речь о подмене PTE для нужных виртуальных адресов, в результате чего появляется интересная возможность изменения данных в АП целевого процесса без вызова контролируемых аверами системных функций типа NtWriteVirtualMemory. Вспомнил? Так вот, что мешает нам сделать то же самое здесь? Проецируем ядро уже известными шагами, находим KeServiceDescriptorTable и адрес интересующей нас функции и вызываем ее, но только не через виртуальный адрес, а с адреса, считанного с PTE. Экстремалам и мазохистам можно предложить и такой способ: считывать нужный нам адрес напрямую из физической памяти. Он легко находится вот таким простым способом:

ULONG_PTR GetPhysicalAddress(IN ULONG_PTR VirtualAddress)
{
return (VirtualAddress & 0x1FFFFFFF);
}

 

Еще одно ядро?

А почему нет? Можно пойти двумя путями. Первый – пропатчить ядро Windows на диске. Делается это элементарно – CreateFile/ReadFile/WriteFile. После любой модификации системных файлов необходимо пересчитать их контрольную сумму, иначе Windows просто откажется их загружать.

Не скрою, все это сложно и палевно, поскольку делать дело придется налету, в недрах операционной системы, где за каждым углом тебя поджидает злой авер. Поэтому править ядро – способ легкий, но ненадежный. Более подробно об этом способе можно почитать в статье К.Касперски «Хак ядра NT»), которая все еще не потеряла своей актуальности.

Экстремалам я могу предложить такой способ: собираем свое ядро из сорцов WRK (Windows Research Kernel), правим его при этом, как хотим, а затем – заменяем им ядро операционной системы и начинаем властвовать. Предвижу бурю недовольства и шквал вопросов, дескать, что это невозможно, что существует и SFC, и ядро весит несколько мегабайт… Да, не спорю, способ трудоемок, но вполне реализуем. И главное – может принести массу дивидендов. Каких? Если у тебя в руках будет свое полностью контролируемое ядро, то дивиденды ты будешь придумывать сам.

Да и потом, простые эксперименты с ядром Windows в стиле «а-ля Linux» будут очень полезны для начинающего системного кодера, пусть даже эти эксперименты не будут направлены на обеспечение безопасности системы. Если ты из числа этих самых экстремалов – сорцы WRK ты сможешь найти на диске. Для начала собери их у себя на машине, а затем тщательно протести на виртуалке, перед тем, как рисковать установкой этого ядра на рабочую машину.

 

Pro & cons

Ну что ж, пора подводить итоги. Выжить в ядре, даже будучи в условиях тотального контроля со стороны бдительных аверов, можно. Самое главное, чего нельзя делать – это недооценивать разработчиков антивирусов, файрволов и прочих защитных системы – они грамотные люди и зря свой хлеб не едят. Однако лазейки для выживания найти всегда можно, если хорошенько покопаться. И не стоит, наверное, серьезно относиться к вышеописанному :). Просто взгляни на смысл послания как на демонстрацию «proof of concept».
Удачного компилирования, и да пребудет с тобой Сила!

 

DVD

На диске ты найдешь WRK – Windows Research Kernel, альтернативные сорцы для сборки ядра Windows. Крайне полезный подгон для изучения внутренностей Windows :).

 

WWW

Как всегда, ценные жемчужины программистской мудрости можно отыскать на сайтах wasm.ru и http://rsdn.ru/forum/asm.

Оставить мнение

Check Also

Ручная распаковка. Вскрываем кастомный пакер на примере вымогателя GlobeImposter 2.0

При реверсе вирусов зачастую обнаруживается, что малварь накрыта какой-нибудь «навесной» з…