Вирмэейкерские типсы и триксы: игра в прятки на уровне ядраАлександр Эккерт (aleksandr-ehkkert@rambler.ru) Процессы в Windows (как и в любой другой ОС) – это наше все. От ядра до калькулятора, операционная система представляет собой лишь набор процессов. Когда ты дважды кликаешь на значок какой-нибудь программки, чтобы запустить ее, в недрах системы приводятся в действие огромные ресурсы, выделяется память, вызываются десятки Native API... И сегодня мы поговорим о том, как эффективно использовать эти ресурсы в решении нетривиальных задач при работе с процессами на уровне ядра Windows. Процессы в WindowsС некоторой натяжкой процессом в Windows можно назвать набор байт в оперативной памяти. Это если в целом. А в частности – процессом обычно называют экземпляр программы, загруженной в оперативную память и выполняемой Windows. Любой процесс состоит из:
Мало-мальски опытный кодер знает, что создание Win32 процесса осуществляется вызовом одной из таких функций, как CreateProcess, CreateProcessAsUser (для Win NT/2000) и CreateProcessWithLogonW (начиная с Win2000) и т.д. Но все это касается только юзермода. Если посмотреть на создание процесса на уровне ядра, то волосы начнут шевелиться от того количества ресурсов и системных вызовов, которые ОС задействует при создании нового процесса! За подробностями отсылаю тебя к книге М. Руссиновича и Д.Соломона «Внутреннее устройство Windows», хотя, по правде, с программистской точки зрения она мало что даст. Поэтому, если тебя заинтересует программный процесс создания и запуска процесса из ядра, пиши мне на мыло, обсудим. Что же может дать честному хакеру доступ к адресному пространству процесса? Да все что угодно! От изменения данных процесса и манипулирования его окружением, вплоть до запуска своего зловредного (или не очень) кода в чужом адресном пространстве. Получить доступ к адресному пространству чужого процесса можно, все эти способы хорошо документированы. Одно плохо - они довольно эффективно палятся проактивками и специальными утилитами. Что делать? Лезть в нулевое кольцо! Уж там-то нам никакой NOD или Касперский перечить не будет! Сокрытие процессаБыла раньше такая фича - заказчики малвари, следуя модным тенденциям, требовали от программиста «шоб в процессах не видна была». Это и сейчас довольно распространенный хакерский прием, нацеленный на сокрытие от бдительных глаз процесса трояна (руткита, вируса и пр.). Оговорюсь сразу, что способа, гарантирующего 100% невидимость процесса для различных утилит, антивирей и проактивных защит, не существует. Скрытый процесс можно выявить всегда. Как? Подробности можешь почитать на www.wasm.ru. Для скрытия процессов в User Mode обычно используется технология внедрения своего кода в чужие процессы и перехвата функции ZwQuerySystemInformation(SystemProcessesAndThreadsInformation , ...) из ntdll.dll. Оно сплайсится и из полученного списка убирается интересующий нас процесс. Каждый процесс в ОС Windows представлен структурой EPROCESS. Кроме атрибутов процесса, она ссылается на несколько других структур, связанных с выполняющимся процессом. Например, с каждым процессом связан один или несколько потоков, представленных в системе структурой ETHREAD. Структуры EPROCESS, в свою очередь, связаны в круговой двусвязный список – то есть, прошу прощения за тавтологию, в каждой такой структуре присутствует указатель на LIST_ENTRY, содержащий указатели на предыдущую и последующую структуры. Наша цель - найти текущий EPROCESS, пробежаться по всему списку, найти процесс, который надо скрыть, после чего слинковать предыдущий и последующий процессы. При этом обязательно надо помнить о том, что в Windows от билда к билду меняется «состав» недокументированных структур и, соответственно, смещения на нужные нам поля. Первое, что нужно сделать, это получить указатель на структуру EPROCESS. Это делается вызовом функции PsGetCurrentProcess():
Не будем сейчас разбирать, как именно она действует – если интересно, покопайся в отладчике. Вызовом ZwQuerySystemInformation(SystemProcessesAndThreadsInformation, ...) находим нужный нам процесс из общего списка (по PID'у процесса или по его имени). Затем немного поколдуем с двусвязными списками из структуры EPROCESS и... вуаля! запущенный процесс исчезает из менеджера задач. Вот, в принципе, и все. Драйвер, реализующий сокрытие процесса из менеджера задач, ищи на диске. Сокрытие загруженной в процесс dllЧасто малварь поставляется конечному юзеру не как отдельный exe-шник (поскольку его легко отловить), а как отдельная библиотека dll, подгруженная в адресное пространство какого-нибудь процесса. А что, совсем недурно: отдельного процесса нет, а искать подгруженную dll в адресном пространстве другого процесса – задача трудоемкая. Этот нехитрый хакерский прием заключается в том, чтобы записать в память чужого процесса свой код через вызов VirtualAllocEx/WriteProcessMemory и затем выполнить его посредством CreateRemoteThread. Впрочем, справедливости ради скажу, что трюк палится антивирями. Попробуем немного осложнить им жизнь - скроем нашу зловредную dll из списка загруженных в АП процесса. Как ты помнишь, в ядре Windows для работы с процессами используется целая куча структур. Одна из самых важных - это «Блок переменных окружения процесса» (Process Environment Block, PEB). Блок представляет собой недокументированную и критически важную для нормального функционирования процесса структуру, которая создается и заполняется на стадии создания процесса. К примеру, в ней содержатся такие важные поля, как PEB_LDR_DATA или PROCESS_PARAMETERS. А вот уже в PEB_LDR_DATA содержится список библиотек, загруженных в адресное пространство процесса. Он-то нам и нужен! Получив указатель на PEB, мы заимеем указатель на двусвязный список dll, загруженных в процесс: Peb -> LoaderData -> InLoadOrderModuleList. Теперь, чтобы спрятать dll от всяческих утилит, просто поступим так же, как и в случае сокрытия процесса из списка задач - слинкуем соседние указатели в InLoadOrderModuleList. Итак, добываем указатель на PEB текущего процесса в Usermode:
Также можно вызвать функцию ZwQueryInformationProcess с классом информации ProcessBasicInformation. В этом случае в буфер запишется структура PROCESS_BASIC_INFORMATION, одно из полей которой и есть указатель на PEB. Замечу, что адреса PEB для всех процессов аналогичны, более того, PEB всегда находится в виртуальном адресном пространстве процесса по адресу 0x7FFDF000. Вот еще одно нехитрое решение. Чтобы защитить процесс от попытки найти там скрытую dll, сделаем перехват функции NtReadVirtualMemory. Затем точно также найдем список загруженных библиотек, исключим нашу dll-ку и возвратим управление вызвавшему коду. Так, путем манипуляций с PEB'ом можно осуществить защиту внедренной dll от чтения. Оба примера, реализованных на С, ищи на диске. Техника сокрытия dll, которую мы только что рассмотрели, не является чем-то принципиально новым, и ты легко сможешь с ней разобраться. Но это только начало. Следующим этапом будет техника подмены самого PEB, когда путем таких же манипуляций в таргет-процесс осуществляется подгрузка фейковой dll вместо какой-нибудь законопослушной user32.dll. Но об этом в другой раз. Инжект кодаА теперь – о самом интересном. Нам необходимо исполнить свой не очень добропорядочный код в контексте доброго и хорошего процесса. То бишь, выполнить инжект кода – из ядра и не привлекая внимания бдительных проактивок. Для новичков, возможно, это будет непросто, но, если у тебя есть опыт программирования в ядре, ничего трудного тут нет. Механизм ОС Windows предусматривает APC - Asynchronous Procedure Call – что-то типа «сообщений», которые могут доставляться потоку для выполнения определенных функций, причем поток о них может ничего не знать. Их-то мы и задействуем. Доставляющий код определяет свою callback-функцию для APC, которая будет выполнена в контексте требуемого потока. Например, механизм APC используется в таком важном механизме потоков Windows, как приостановка и восстановление (suspend/resume) потока. Главное преимущество APC с точки зрения малвари - это то, что APC для антивирей и практивок - совершенно нормальная операция, выполняемая ОС, и поэтому соотнести вызов APC со зловредными действиями малвари ей будет крайне трудно. Более подробно (правда, на английском) о вызове APC можно почитать здесь: http://www.cmkrnl.com/arc-userapc.html. Итак, первое: выделяем кусок памяти в виде MDL, который затем будем использовать для записи нужного нам кода. Затем аттачимся к таргет-процессу, после чего лочим в юзермодной памяти выделенный нам участок памяти. Отключаемся от процесса и вызываем проинициализированный APC, который будет выполнен системой в контексте одного таргет-процесса. Здесь нужно предусмотреть поток TargetThread, где будет выполняться код. Обычно при решении этой проблемы находится существующий поток в процессе вызовом PKTHREAD thread=KeGetCurrentThread(). Использование APC для инжекта кода В дополнение к вышесказанному могу сказать, что, приаттачившись к какому-либо процессу, кроме инжекта кода, можно без особых проблем считывать нужные данные через вызов NtReadVitualMemory или же производить запись в адресное пространство процесса соответственности через NtWriteVirtualMemory. К примеру, что-нибудь вроде:
Не забудь проверить, имеет ли секция, куда хочешь произвести запись, аттрибут «writeable», иначе попытка записи в non-writeable участок памяти приведет к BSOD'у. Таковой по умолчанию является секция кода, но проблема решается легко - нужной секции просто присваивается аттрибут для записи. «Так почему именно ring0, ведь тот же самый инжект кода можно успешно сделать и в юзермоде?», - можешь спросить ты. Открою тайну - кодинг в нулевом кольце не так страшен, как может показаться на первый взгляд и, вмешиваясь в работу ядра своими руками, ты начнешь чувствовать себя «повелителем машин». Шутка. Если есть вопросы, пиши, обсудим! DVDНа диске ты найдешь драйвер, реализующий сокрытие процесса, а также несколько других фич, описанных в статье и предназначенных для изучения процессов. WWW Для лучшего усвоения материала советую статьи (на английском):
«What Goes On Inside Windows 2000: Solving the Mysteries of the Loader» (на MSDN), INFOДля отладки драйверов обязательно нужен отладчик ядерного уровня типа SoftICE или WinDBG. И не забудь скачать символы с www.microsoft.com/whdc/DevTools/Debugging/symbolpkg.mspx. WARNINGВ ядре Windows от билда к билду во многих системных структурах меняются смещения. Всегда держи под рукой прогу типа PdbDump, которая поможет тебе находить необходимые смещения. |