Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Чаще всего при анализе поведения подозрительной программы наблюдают за пользовательским режимом, а код в ядре остается вне поля зрения аналитика. Отчасти это правильно, поскольку больше всего вредоносной деятельности ведется именно в пользовательском пространстве. Тем не менее вредоносный код в ядре может нанести больше ущерба, и его сложнее обнаружить. Однако это возможно.
С появлением KPP (Kernel Patch Protection), или сокращенно PatchGuard, в Windows стало сложнее модифицировать ядро без последствий. Если раньше, например, таблицу системных вызовов с целью фильтрации системных вызовов перехватывали даже такие легитимные программы, как антивирусы, то с появлением KPP это стало не так просто. Тем не менее для руткитов PatchGuard не представляет особой угрозы, поскольку техники его обхода можно найти в открытом доступе, они развиваются и актуальны по сей день. В этой статье будет рассматриваться множество техник модификации ядра, включая те, что обнаруживает PatchGuard, на примере нового плагина для DRAKVUF — rootkitmon.
Мы уже несколько лет разрабатываем песочницу для рискориентированной защиты, использующую фреймворк DRAKVUF. DRAKVUF — безагентная песочница, основанная на библиотеке LIBVMI и гипервизоре XEN. Все возможности DRAKVUF реализованы в плагинах. Каждый из двадцати с лишним плагинов выполняет определенную работу: для обнаружения доступа к файлам есть плагин filetracer, для трассировки системных вызовов — плагин syscalls. Rootkitmon — новый плагин, позволяющий отслеживать вредоносную активность в ядре средствами DRAKVUF.
Существует множество определений понятия «руткит», мы же будем опираться на следующее: «компьютерная программа, которая использует недокументированные и (или) запрещенные техники для манипуляции ядром операционной системы в своих целях».
В «джентельменский набор» плагина входят возможности детектировать следующие типы перехватов:
MSR LSTAR
;
DRIVER_OBJECT
, стек DEVICE_OBJECT
;
EPROCESS
;
Поскольку руткитов намного меньше, чем вредоносного ПО в пользовательском пространстве, и время выполнения образца всегда ограниченно, уменьшение нагрузки плагина на работающую систему было приоритетной задачей. Для этого во многих случаях было решено сверять целостность критических структур в начале анализа и в конце, а не выполнять непосредственный перехват на запись страниц памяти.
Рассмотрим самый распространенный тип перехватов и, возможно, самый легко обнаруживаемый — inline-перехваты. Inline-перехваты очень популярны, и даже Microsoft предоставляет возможность перехватить API-библиотеки, добавляя перед функциями двухбайтовый пролог вроде mov edi, edi для быстрого редактирования функциональности уже загруженных и работающих компонентов. Конечно, такие перехваты возможны только в пользовательском режиме, а в ядре караются синим экраном с кодом ошибки 0x109
, если PatchGuard не выключен.
Inline-перехваты обычно состоят из трех частей:
Рассмотрим простой пример вызова CreateFileW
из библиотеки kernel32.dll
. Пройдя все библиотеки, в итоге код окажется в ядерной функции nt!NtCreateFile
. Если бы руткит установил перехват на эту функцию, он бы мог выглядеть следующим образом.
Схема системного вызова
Поскольку код находится в страницах с правами только на чтение и выполнение, для записи в такие страницы необходимо либо выделить новую виртуальную страницу с правами на запись и спроецировать ее на физическую страницу, где находится код, либо отключить бит Write Protect в специальном регистре управления CR0
, что позволит выполнять запись в страницы в обход их прав для текущего ядра.
Структура регистра управления CR0
Описание значения флага Write Protect
Обнаружение таких перехватов сводится к подсчету контрольной суммы секций драйвера в момент начала анализа и пересчету, сверке контрольной суммы в конце. В отличие от PatchGuard, который защищает только небольшой список системных драйверов, мы можем проверять абсолютно все загруженные драйверы из списка PsLoadedModules
.
PsLoadedModules
— двусвязный список структур _KLDR_DATA_TABLE_ENTRY
, описывающих загруженный драйвер: его базовый адрес, размер, имя, характеристики и прочее.
Двусвязный список загруженных модулей
Описание структуры _KLDR_DATA_TABLE_ENTRY
Для перечисления загруженных модулей ядра с помощью DRAKVUF API мы реализовали метод drakvuf_enumerate_drivers
. В плагине rootkitmon список PsLoadedModules
проходится два раза: в начале инициализации плагина — для подсчета контрольных сумм секций драйверов — и в конце — для сравнения значений.
Перечисление и подсчет контрольных сумм секций драйвера
В случае расхождения в логе появится строка:
"DeviceHarddiskVolume2WindowsSystem32explorer.exe":KiDeliverApc SessionID:0 PID:1968 PPID:476 Reason:"Driver section modification" Driver:"ntoskrnl.exe"
Для отработки в конце анализа мы добавляем перехват часто вызываемой ядерной функции KiDeliverApc
. Поскольку мы заранее не знаем, какой поток вызовет эту функцию и в каком контексте процесса этот поток находился, имя процесса, его PID и PPID, которые DRAKVUF автоматически сохраняет в журнале, не имеют отношения к детекту, в то время как поле Reason и все поля, следующие за ним, имеют. В данном случае поле Reason означает обнаруженную вредоносную технику, а поле Driver — название модифицированного драйвера.
SSDT (System Service Descriptor Table) — массив указателей на обработчики системных вызовов в 32-битных системах или список смещений относительно базового адреса таблицы на 64-битных ОС. Как говорилось ранее, до появления PatchGuard эта таблица активно использовалась легитимными программами, а также руткитами для фильтрации и мониторинга системных вызовов. Перезапись одного указателя в такой таблице равносильна перехвату всех вызовов определенного обработчика, что намного проще inline-перехватов, рассмотренных до этого.
На данный момент в ОС Windows существует два таких массива под символами nt!KiServiceTable
и win32k!W32pServiceTable
для системных вызовов к модулю ntoskrnl.exe
и графической подсистеме win32k.sys
соответственно. Количество элементов в этих массивах сохранено в переменных nt!KiServiceLimit
и win32k!W32pServiceLimit
.
Значения структур nt!KiServiceTable и win32k!W32pServiceTable
Указатель на SSDT находится в специальной структуре KSYSTEM_SERVICE_TABLE
, или SST
, под именем ServiceTableBase
:
Описание структуры KSYSTEM_SERVICE_TABLE
typedef struct _KSYSTEM_SERVICE_TABLE{ PULONG ServiceTableBase; PULONG ServiceCounterTableBase; ULONG NumberOfService; ULONG ParamTableBase;} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;
В то же время массив этих структур лежит в таблицах nt!KeServiceDescriptorTable
и nt!KeServiceDescriptorTableShadow
. Если в первой хранится только SST для обработчиков системных вызовов NT, то во второй добавляется SST-таблица графической подсистемы win32k.
Значения таблиц nt!KeServiceDescriptorTable и nt!KeServiceDescriptorTableShadow
Можно заметить, что первая SST-структура в обеих таблицах одинакова. При системном вызове ОС Windows использует нижние 12 бит регистра eax
как индекс в одной из SSDT-таблиц, а биты 12–13 указывают, какую SDT-структуру выбрать: nt!KeServiceDescriptorTable
или nt!KeServiceDescriptorTableShadow
.
В первой части статьи уже шла речь о том, что на 64-битных системах SSDT содержит массив смещений, а виртуальный адрес обработчика системного вызова вычисляется по формуле
RoutineAddress = ServiceTableBase + (ServiceTableBase[index] >> 4)
Сдвиг на 4 необходим, поскольку первые 4 бита содержат количество параметров, передаваемых через стек.
Пример подсчета адреса обработчика системного вызова
Если взглянуть на пример с вызовом CreateFileW
, учитывая вышеизложенное, он будет выглядеть следующим образом.
Схема системного вызова
Мониторинг модификации SSDT-таблиц заключается в выставлении ловушек на запись в физические страницы памяти данных таблиц и реализован в плагине ssdtmon.
Перехват nt!KiServiceTable
уже был реализован в этом плагине, и мы лишь добавили поддержку таблицы win32k!W32pServiceTable
. Также в плагине вредоносной считается запись, совершенная на 8 байт ниже начала таблицы. Это связано с тем, что с помощью 16-битных XMM-инструкций можно переписать первые 12 байт таблицы, записывая на 4 байта ниже ее начала. Тем самым можно обойти тривиальную проверку границ. Стоит заметить, что в то время, как таблица nt!KiServiceTable
находится в секции данных модуля ntoskrnl.exe
и доступна из всего ядерного пространства, win32k!W32pServiceTable
принадлежит модулю win32k.sys
и для доступа к данному драйверу нужно находиться в контексте какого‑либо графического процесса, например explorer.exe
.
Источник: xakep.ru