Проявитель для троянописателей. Детектируем скрытые процессы в usermode и ring0Итак, друг мой, сегодня я особенно суров и к долгим интродукциям не склонен. Поэтому буду краток: речь пойдет о том, как, имея относительно прямые руки, голову на плечах и умение пользоваться диззассемблером длин инструкций, можно почти со 100% вероятностью выявлять и брать под контроль процессы, которые злые недоброжелатели пытаются скрытно запустить на твоем компьютере. ПесочницаНемного о том, с чем придется иметь дело. Почти все методы сокрытия процессов от таскменеджеров и различных просмотрщиков фактически основаны на перехвате NtQuerySystemInformation (как в ядре, так и в ring3), а также исключении структуры EPROCESS из двунаправленного списка в ядре. Дальше фантазия прогеров почему-то не идет. Это связано с одной конструктивной особенностью существования процессов в ОС Windows — можно, к примеру, подсуетиться и со списками процессов, и с таблицами хендлов, но единственное, что делать категорически нельзя — исключать процесс из списков планировщика Windows. В противном случае процесс не получит процессорного времени и его код просто не будет выполнен. Именно по этой причине создать полностью невидимый процесс в ОС Windows нельзя, а вирусы и трояны, существующие в виде отдельного процесса — вещь неумная, характеризующая своего создателя как неумелого программиста. В целом, жизнедеятельность процесса в Windows — чрезвычайно шумная штука, поскольку процесс оставляет следы практически везде, начиная от открытых хендлов и заканчивая переключением контекста потока. И соответственно, скрытый процесс можно попытаться обнаружить как в usermode, так и в ядре операционной системы. Способы для UsermodeПолучить честным путем список процессов в Windows очень легко; для этого существуют специальные функции, такие как CreateToolhelp32Snapshot и NtQuerySystemInformation — их вызов вернет нам список процессов в системе. Поэтому для начала можно просто сравнить полученные ими результаты. Однако способ крайне ненадежен, поскольку троянописатели в первую очередь перехватывают именно эти функции. Что же делать, если нужно обойтись только usermod’ными способами? Иногда достаточно вспомнить, что открытый процесс обладает списком открытых им хендлов, поэтому их перечисление и сравнение с «легальным» списком процессов поможет выявить разницу. Перечисление открытых хендлов процессов осуществляется вызовом функции NtQuerySystemInformation c параметром SystemHandleInformation. В качестве лирического отступления от темы, хотелось бы заметить, что системная функция NtQuerySystemInformation — функция настолько универсальная, что используется практически повсеместно в целях получения самой разнообразной системной информации — от списка процессов до скорости процессора. Этих вариантов — более семидесяти, все они содержатся в недокументированной структуре SYSTEM_INFORMATION_ CLASS. При ее использовании надо обязательно помнить о том, что ты никогда заранее не знаешь размер данных, которые тебе вернет NtQuerySystemInformation, поэтому зачастую ее вызов обламывается и она возвращает STATUS_INFO_LENGTH_MISMATCH. В этом случае просто увеличь размер выходного буфера в 2 раза. Правильное использование ZwQuerySystemInformation
Теперь предположим, что кулхацкер смог скрыть открытые процессом хендлы. Не беда! Находясь в usermode, можно перечислить список окон, открытых процессом. Да-да, об окнах, которые создаются всегда, почти никто не вспоминает, поэтому вызов функции GetWindowThreadProcessId тебе в руки! Едем дальше. Находясь в usermode, полезно вспомнить о таком очень интересном и загадочном системном процессе, как csrss.exe. Он является частью пользовательской Win32-подсистемы. СSRSS — сокращение от «client/server run-time subsystem» (клиент/серверная подсистема) — отвечает за консольные приложения, создание/удаление потоков и 16-битную виртуальную среду MS-DOS. Это официальное определение от Microsoft. От себя добавлю — при создании нового процесса в ядре Windows подсистема обязана уведомить csrss.exe о знаменательном событии системным вызовом CsrClientCallServer. Если этого не произойдет, вновь созданный процесс будет работать не совсем адекватно и в конце концов вылетит с ошибкой. Таким образом, сsrss.exe ведет своеобразный учет процессов и потоков, и эту особенность можно использовать при детекте скрытых процессов. Говоря в самых общих чертах — нужно будет открыть процесс csrss.exe вызовом OpenProcess, после чего пройтись дизассемблером длин инструкций по функциям CsrLockProcessByClientId и CsrLockThreadByClientId для получения адреса недокументированной структуры CSR_PROCESS, которая в свою очередь содержит список процессов, зарегистрированных в csrss.exe. Сразу скажу, — способ сложный и довольно громоздкий, но весьма эффективный. Пример реализации ищи на диске. Способы для продвинутыхИтак, лезем в ядро. Я думаю, понятно, для чего — на этом уровне нам никто никаких запретов поставить не сможет, поскольку все обладают равными правами. В ring0 выигрывает тот, у кого больше опыта и тот, кто лучше знает принципы функционирования ядра. Поговорим о списках планировщика Windows, которые содержат потоки, подлежащие исполнению. Главное неудобство метода в том, что адреса списков потоков планировщика меняются от билда к билду, что приходится учитывать в коде. Например, в Win2K их три: KiWaitInListHead, KiWaitOutListHead, KiDispatcherReadyListHead. Все они двусвязные. Первые два списка содержат потоки, ожидающие наступления какого-либо события, а третий содержит потоки, готовые к исполнению. Подобный способ обнаружения скрытых процессов используется в Klister’e от знаменитой потрошительницы Windows Джоаны Рутковской (http://invisiblethings.org/tools/klister-0.4.zip). Однако этот способ работает только под Win2K из-за жестко прописанных адресов списков потоков. Именно адреса списков и делают способ немного неудобным из-за определенной разности в устройстве ядра в Win2K и WinXP (планировщик в WinXP имеет только два списка потоков: KiWaitListHead и KiDispatcherReadyListHead), так как это надо будет учитывать в процессе программирования. Весьма надежным способом обнаружения скрытых процессов является перехват системных вызовов. В основе метода лежит аксиома, что все процессы (точнее, их программная логика) обращаются к ядру системы через интерфейс системных вызовов. Идея метода заключается в том, чтобы перехватить обращения к интерфейсу системных вызовов, а в обработчике получать указатель на EPROCESS текущего процесса. В win2k для системного вызова используется прерывание 2Eh, а в Win XP — sysenter. При этом из соображений совместимости прерывание 2Eh также может использоваться в Win XP. Все, что для этого нужно — осуществить перехват данного прерывания в таблице прерываний IDT. Код несложный, пример перехвата int2e и sysenter, реализованный на С, ищи на диске. Перехват прерывания 2EH
У метода имеется небольшой недостаток — текущий список процессов постоянно изменяется и, если указатель на EPROCESS (ищем мы именно его, не забывай), будет удален, то в лучшем случае драйвер вылетит в BSOD. Об этом надо помнить. Как это реализовать на практике? Использовать callback-функцию PsSetCreateProcessNotifyR outine, которая установит нотификатор, информирующий нас о создании или удалении процесса. Еще одним надежным способом выявления скрытых процессов может служить перехват SwapContext (перехват переключения потоков). Так называемая псевдомногозадачность в ОС Windows реализуется таким образом, что через определенные промежутки времени, равные 10-20 мс (называемые квантом времени), таймер ОС генерирует прерывание, которое в свою очередь вызывает планировщик. И если квант времени, связанный с текущим потоком, истек, то происходит переключение потоков. Переключение потоков выполняется неэкспортируемой функцией ядра KiSwapContext. Эта функция вызывается планировщиком при истечении кванта времени потока либо при ожидании потоком какого-либо события. В первом случае функция вызывается из KiDispatchInterrupt, а во втором — из неэкспортируемой функции, которая в свою очередь вызывается из KeWaitForSingleObject, KeDelayExecutionThread и KeWaitForMultipleObjects. Все, что нужно — это найти адрес KiSwapContext в ядре с использованием диззассемблера длин инструкций! Эту функцию можно легко найти в любой версии ядра по сигнатуре «0F 20 C5» (mov ebp,cr0). На момент вызова KiSwapContext регистр EDI указывает на структуру ETHREAD-потока, который останавливается, а регистр ESI — на структуру ETHREAD-потока, который получает управление. В данной ситуации сплайсинг функции — самое то. Для этого запишем в начало функции KiSwapContext инструкцию JMP на нашу hook-функцию. В ней мы обрабатываем входные параметры так, как нам нужно, и передаем управление оригинальной функции через Trampoline. Чтобы не восстанавливать начальные байты оригинальной функции (а то потом придется искать момент, как опять в начале записать JMP), начальные инструкции копируются в специально выделенную область памяти, к которым добавляется JMP на продолжение функции (Trampoline function). Код драйвера, реализующий перехват KiSwapContext, ищи на диске. ХендлыХендлы открытых файлов — удивительная вещь, о которой доморощенные троянописатели часто забывают. Получить список открытых хендлов можно как в usermod’ном режиме, так и непосредственно в ядре Windows. Для этого, как говорилось выше, используется уже известная нам системная функция ZwQuerySystemInformation с переданным ей параметром SystemHandleInformation. К примеру, этот нехитрый способ будет выявлять процессы, скрытые путем исключения структуры EPROCESS из двусвязанного списка. Для удобства перечисления хэндлов все таблицы хэндлов объединены в двусвязный список HandleTableList. Проблема заключается лишь в том, как его найти, ибо нет таких системных функций, которые могли бы заполучить этот самый вожделенный список. Начинается список с HandleTableListHead, поэтому для ее поиска необходимо знать, что HandleTableListHead — это глобальная переменная ядра, следовательно, она находится в одной из секций его PE-файла, а остальные элементы HandleTableList находятся в динамически выделяемой памяти и всегда будут за его пределами. Из этого следует, что нам нужно получить указатель на HandleTable любого процесса, и двигаться по связанному списку до тех пор, пока его элемент не окажется внутри PE-файла ядра. Этот элемент и будет HandleTableListHead. Сложно? Только на первый взгляд. Смотрим код: Поиск HandleTableListHead
Здесь в CurrentHandleTable и будет содержаться адрес искомой HandleTableListHead. Далее мы можем пройтись по таблицам хэндлов и построить по ним список запущенных процессов. ЗаключениеЯ попытался собрать и кратко описать относительно известные и не очень способы выявления скрытых процессов в Windows. Как видишь, найти скрытый процесс достаточно легко, и иногда бывает достаточно скомбинировать несколько вышеописанных способов. Удачного компилирования и да пребудет с тобой Сила! InfoОсобенность Windows XP в том, что перехват системных вызовов должен производиться как через sysenter, так и через int 2Eh, поэтому перехватывать нужно оба обработчика. |