R0 CREW

Kaspersky Hooking Engine Analysis

Оригинал: quequero.org

В этой статье мы поговорим про несколько перехватов, которые используются антивирусами. В качестве подопытного кролика был выбран антивирус Касперского (PURE 3.0 Total Security). Он использует различные методы перехватов, как в пользовательском режиме, так и в режиме ядра.

Анализ проводился на Windows 7 Professional 32-bit.

На рисунке ниже приведено общее количество методов, которые мы проанализируем в этой статье:

Порядок рассмотрения будет следующим:

  • User-space Processes
  • Inline hooking
  • IAT and EAT
  • Virtual Address Descriptor
  • Hidden registry entry
  • IDT
  • SSDT
  • MINI-FILTER
  • IRP
  • TDI HOOKING
  • CALLBACKS
  • Conclusion

User-space Processes

Начнем с краткого знакомства с процессами Касперского, работающих в пользовательском режиме.

Основный пользовательским процессом является “avp.exe”, который запускается в двух экземплярах: один запускается с привилегиями NT AUTHORITY \ SYSTEM, а другой запускается с правами пользователя (avp.exe user) и используется для отображения пользовательского интерфейса. Другой процесс ProtectedObjectsSrv.exe работает в качестве сервиса шифрования.

Мы сосредоточимся на последнем. Он запускается в качестве фонового сервиса с именем CSObjectSrv (CryptoStorage control service).

InfoWatch CrytpStorage предназначен для централизованной защиты конфиденциальный данных, используя криптографические методы во время хранения и обработки данных. Продукт основан на комплексном подходе к защите данных. Функциональные возможности включают шифрование файлов и папок с помощью устойчивых алгоритмов шифрования, создание криптографических контейнеров, дисков и флэш-устройств с возможностью разграничения прав доступа к защищаемым объектам.

InfoWatch CryptoStorage защищает против несанкционированного доступа к содержимому ОЗУ, которое сбрасывается на жесткий диск во время гибернации, crash-дампов или во время, когда данные приходят от временных или swap-файлов. Больше информации по этой теме можно найти здесь.

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

Inline hooking

Чтобы найти перехваченные API-функции в режиме пользователя, воспользуемся плагином apihooks для Volatility.

Процессы, которые участвуют в Inline Hooking:

  • svchost.exe
  • avp.exe[pid1] NT Authority\SYSTEM
  • avp.exe[pid2]

Как уже говорилось, один avp.exe является сервисом защиты (avp.exe system), другой используется для взаимодействия с пользователем (avp.exe user). Сервис требует полного доступа к системе, поэтому он работает с правами System.

Давайте исследуем svchost.exe. Этот процесс подвергается Inline Hooking. По адресу 0x7453b5dd можно найти переход, которые ведет в модуль wfapigp.dll, который располагается по адресу 0x74586218.

Используя Volatility мы можем сдампить процесс с PID 1560. После чего, с помощью IDA дизассемблировать дамп и подтвердить наличие перехвата.

Снова, прокрутив отчет, сгенерированный Volatility, можно увидеть то, что процесс avp.exe использует различные модули:

Процесс avp.exe использует разные техники перехватов. Давайте попробуем исследовать следующий модуль:

  • ushata.dll

В данном случае происходит перехват функции ZwProtectVirtualMemory, которая расположена по адресу 0x7715f18 внутри ntdll.dll. Используя IDA можно подтвердить, то что переход на адрес 0x71722066 указывает на область загруженного модуля ushata.dll.

Здесь можно видеть как avp.exe загружает ushdata.dll при помощи стандартной функции LoadLibraryEx().

Примерный псевдокод того, что здесь происходит показан ниже:

hDLL = LoadLibraryExW(L"USHATA.DLL", null, 8);
lpGetNumber = (LPGETNUMBER)GetProcAddress((HMODULE)hDLL, “InitHooks”);

Давайте посмотрим, какие функции экспортирует модуль ushdata.dll:

Модуль ushdata.dll экспортирует функции InitHooks, SetClientVerdict и SetShuttingDownHint.

Понять к каким функциям применялся Inline Hooking можно из отчета Volatility:

  • C:\Program Files\Kaspersky Lab\Kaspersky PURE 3.0\avp.exe[pid1] and [pid2] C:\Windows\SYSTEM32\ntdll.dll (FUNCTIONS ntdll.dll!NtProtectVirtualMemory e ntdll.dll!ZwProtectVirtualMemory)
  • C:\Program Files\Kaspersky Lab\Kaspersky PURE 3.0\avp.exe[pid1] and [pid2] ntdll.dll!NtProtectVirtualMemory JMP 70B12066 C:\Program Files\Kaspersky Lab\Kaspersky PURE 3.0\ushata.dll
  • C:\Program Files\Kaspersky Lab\Kaspersky PURE 3.0\avp.exe[pid1] and [pid2] C:\Windows\system32\kernel32.dll
  • C:\Program Files\Kaspersky Lab\Kaspersky PURE 3.0\avp.exe[pid1] and [pid2] C:\Windows\system32\ole32.dll
  • C:\Program Files\Kaspersky Lab\Kaspersky PURE 3.0\avp.exe[pid1] and [pid2] USER32.dll!NotifyWinEvent + 6AE

Например, выше показано определение перехвата в недокументированной функции NtProtectVirtualMemory, которая позволяет устанавливать защиту на страницу.

IAT and EAT

Продолжим анализ перехватов в режиме пользователя. Volatility можно использовать для анализа IAT и EAT перехватов. В нашем случае Volatility обнаружил всего две перехваченные функции. Одну в IAT, а другую в EAT.

Исходя из предыдущего рисунка (EAT-перехват), можно сделать вывод, что в модуле kernel32.dll была перехвачена функция CreateThread. Этот момент изображен на рисунке ниже:

Virtual Address Descriptor

Сделаем небольшое отступление, чтобы понять о чем пойдет речь.

Process VAD (Virtual Address Descriptor) является структурой данных ядра, которая заботится о регистрации использования виртуальных адресов в процессе. Для каждого из процессов менеджер памяти поддерживает свой набор VAD-структур, который содержит информацию об адресном пространстве конкретного процесса. Восстановление VAD-дерева позволяет восстановить процесс со всеми его отображенными (mapped) файлами. Вот пример:

Поле Protection, выделенное красным цветом, извлекается из параметра flProtect, который передается в качестве входного параметра функции VirtuaAlloc.

Для получения информации о VAD-дереве конкретного процесса, так же можно использовать команду !vad отладчика WinDbg:

kd> !process 0 1 avp.exe
kd> !vad [address of VadRoot]
[B]Hidden registry entry[/B]

Реестр является базой данных, данные которой разделены на логические группы разделов и подразделов. Разделы реестра имеют ряд вспомогательных файлов, которые содержат резервные копии их данных. Все эти файлы находятся в %SystemRoot%\System32\Config. Они создаются / обновляются каждый раз, как пользователь входит в систему.

Вот таблица, которая показывает стандартные разделы и связанные с ними файлы:

Давайте посмотрим на скрытые значения связанные с драйвером klif.sys. Начнем с небольшой справки по команде !reg:

  • !reg hivelist

С помощью этой команды отобразим список всех разделов в системе (их адреса и названия файлов). После чего выбрав адрес SYSTEM используем его в следующей команде:

  • !reg openkeys “Hive Address of SYSTEM”

Которая отобразит все открытые ключи в разделе:

Выше мной была использована еще одна команда:

  • !reg cellIndex “HiveAddress of SYSTEM” “Index”

Она отображает виртуальный адрес для ячейки в разделе. Index указывает на индекс ячейки.

Используя эту команду:

  • !reg valuelist “HiveAddress of SYSTEM” KeyNodeAddress

мы сможем вывести список значений в казанном узле. После чего отобразим структуру выбранного ключа реестра:

  • !reg kvalue Address

Здесь Address указывает на адрес значения ключа. Finally, we can reuse the cell index with the new index of the cell and dc command (она отображает double word значения и ASCII символы)

Того же результата можно достичь с помощью Volatility. Ниже я кратко покажу как сделать это же с помощью команды hivelist.

И снова мы сталкиваемся со службой KLIF:

Теперь давайте перейдем к анализу перехватов в режиме ядра. Далее мы будем иметь дело с: IDT, SSDT и IRP-перехватами.

IDT

Системные вызовы используются для обхода барьера, который существует между пространством пользователя и пространством ядра. Для решения этой задачи используется таблица векторов прерывания IDT. Размер одной записи в этой таблице равен 8-байтам. Каждая такая запись описывает как именно должно обрабатываться прерывание (на x86-совместимых процессорах). На рисунке ниже можно видеть взаимосвязь между IDT и инструкцией INT 2e, которая обычно используется для того, чтобы инициировать системный вызов. Хотя на новых процессорах вместо неё используется инструкция SYSENTER.

Целью IDT-перехвата является перехват любой ранее зарегистрированной функции конкретного прерывания.

Давайте посмотрим, используется ли подобные перехваты Касперским. Для этого анализа воспользуемся WinDbg и его командой !idt.

Проведя эту же проверку с помощью Volatility и её команды idt, можно видеть, что результаты идентичны:

В выделенной строке, в столбце Value, можно видеть, что адрес совпадает с тем, что был получен и проанализирован в WinDbg. Там же в столбце Module можно заметить ntoskrnl.exe, который указывает на то, что перехваты отсутствуют.

SSDT

System Service Descriptor Table (SSDT) содержит указатели на функции режима ядра, предоставляемые ядром исполняемого модуля (ntoskrnl.exe). Существует еще одна SSDT таблица, называемая Shadow SSDT таблица. Она хранит указатели на функции предоставляемые модулем win32k.sys, который ответственен за обработку графики (GUI). Важно заметить: во-первых, что когда системный вызов дойдет до ntdll.dll, регистр EAX будет содержать шестнадцатеричное значение, соответствующее индексу в SSDT той функции, которая будет вызвана; и во-вторых, сразу после выполнения инструкции INT 2e управление перейдет в функцию KiSystemService:

Нам нужно проверит содержимое двух таблиц. Это можно сделать как с помощью WinDbg, так и Volatility:

На рисунке выше с левой стороны представлена память, принадлежащая klif.sys, который расположен по адресу 0x8c836000. С правой стороны можно видеть вывод команды:

  • kd> dps KiServiceTable l11C

Которая показывает наличие SSDT-перехватов из модуля klif.sys.

Мы можем так же исследовать KeServiceDescriptorTable и KeServiceDescriptorTableShadow. Модуль klif.sys кажется осуществляет перехваты в SSDT и Shadow SSDT, поэтому давайте более подробно рассмотрим его. Первой функцией, которую мы проанализируем является PsSetLoadImageNotifyRoutine. Она регистрирует предоставляемую драйвером функцию обратного вызова, которая в последствии вызывается всякий раз, как происходит загрузка или отображение образа (image) в память.

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

SSDT перехваты не выполняются на 64-битных системах, потому что там есть Kernel Patch Protection (KPP), так же известный как Patchguard, который защищает эту структуру.

Но в любом случае, в качестве обходного пути, можно использовать драйвер мини-фильтра (mini-filter).

MINI-FILTER

И действительно, то с чем мы имеем дело является драйвером мини-фильтра:

Драйвер мини-фильтра должен определить значение альтитуды (altitude) из диапазона, который соответствует одной из групп LoadOrderGroup. Альтитуда определяет позицию загрузки драйвера мини-фильтра, в стеке ввода / вывода, относительно других драйверов мини-фильтра. Драйвер с меньшей альтитудой, устанавливается, в стеке ввода / вывода, под драйвером с большей альтитудой.

Альтитуда гарантирует, что экземпляр драйвера мини-фильтра всегда будет загружаться в подходящем месте, относительно других экземпляров драйверов мини-фильтра. Так же она определяет порядок в котором менеджер фильтров вызывает драйвер мини-фильтра для обработки запросов ввода / вывода. Определением диапазона альтитуд занимается Microsoft.

На рисунке ниже показан упрощенный стек ввода / вывода с менеджером фильтров (Filter Manager) и тремя драйверами мини-фильтра.

В нашем случае мы имеем:

Больше информации по теме альтитуд можно получить здесь.

IRP

IRP это объект, который используется для обмена данными между всем слоями стека драйверов.

У каждого драйвера имеется набор основных функций (Major Functions) ответственных за получение и обработку IRP. Все эти основные функции хранятся внутри фиксированной таблицы указателей.

Наш исследуемый драйвер содержит следующие функции:

  • Driver Entry
  • AddDevice
  • Dispatch routine
  • Unload()

Структура Driver Object имеет следующий вид:

По умолчанию менеджер ввода / вывода направляет DriverInit в функцию DriverEntry(). Массив MajorFunciton по сути является таблицей. Каждый драйвер заполняет эту таблицу указателями на функции, которые называют рабочими процедурами (Dispatch Routine). Вот некоторые из наиболее часто используемых процедур: IRP_MJ_CREATE, IRP_MJ_READ, IRP_MJ_WRITE, IRP_MJ_DEVICE_CONTROL. Основной структурой данных, которую используют рабочие процедуры, является IRP.

Для перехвата IRP-трафика, предназначенного для драйвера klif.sys, мы будем использовать Irp Tracker:

В красных рамках можно видеть два процесса (avp.exe и svchost.exe) которые вызывают функцию NtFsControlFile (которая посылает управляющий код прямо в драйвер klif).

TDI HOOKING

Модулем ядра, отвечающий за TDI HOOKING, является kltdi.sys, мы можем найти его внутри структуры LDR_DATA_TABLE_ENTRY, на которую указывает PsLoadedModuleList. Выполнив команду modules в Volatility получим:

  • kltdi.sys 0x8cb7a000 0x9000 \SystemRoot\system32\DRIVERS\kltdi.sys

В данный момент мы можем посмотреть, если ли что-то необычное для драйвера tdx.

Как можно видеть, это список устройств, которые принадлежат драйверу Driver\tdx, загруженному по адресу 0x8cb57000. В каждом устройстве tdx присутствует модуль kltdi.sys. Используя WinDbg мы можем проверить, что происходит по месту загрузки tdx.sys.

  • tdx.sys 0x8cb57000 0x17000 \SystemRoot\system32\DRIVERS\tdx.sys

Мы видим расположение только одной основной функции IRP_MJ_CREATE:

DriverName: tdx
DriverStart: 0x8cb57000
DriverSize: 0x17000
DriverStartIo: 0x0
0 IRP_MJ_CREATE                       0x8cb62faa tdx.sys

Давайте установим брейкпойнт на адрес 0x8cb62faa, это то место, где находится обработчик функции IRP_MJ_CREATE модуля tdx.sys. Затем запустим утилиту PING, после чего отладчик сразу остановится на нашем брэйкпойнте.

В стеке вызовов (call stack) присутствует модуль kldti.sys. Давайте сосредоточимся на его функции kltdi+0x4803:

Функция IoCallDriver посылает IRP драйверу, связанному с указанным объектом устройства (Device Object). Она принимает два входных параметра PDEVICE_OBJECT и PIRP.

NTSTATUS IoCallDriver(
    _In_     PDEVICE_OBJECT DeviceObject,
    _Inout_  PIRP Irp
);

Цитата из документации Microsoft:

CALLBACKS

Теперь давайте посмотрим на kernel callbacks. В очередной раз воспользуемся Volatility:

Thread creation (PsSetCreateThreadNotifyRoutine):

  • klif.sys
  • kl1.sys

Shutdown callbacks (IoRegisterShutdownNotification):

  • kl1.sys

KeRegisterBugCheckReasonCallback

  • kl1.sys

Существует несколько адресов для функций обратного вызова (callbacks), но мы хотим указать на присутствие модуля kl1.sys, так что давайте копнем глубже:

kl1.sys это драйвер, который запускается во время загрузки ОС, в его образе вы можете видеть присутствие (в процедуре DriverEntry) функцию IoRegisterBootDriverReinitialization.

Функция IoRegisterBootDriverReinitialization регистрирует callback, который будет вызван в момент, когда будут загружены все загрузочные (boot) драйверы. Как правило, эта процедура используется в фильтрующих драйверах, который присоединяются к устройствам, которые не поддерживают Plug-and-Play, и таким образом, не могут полагаться на функцию AddDevice(), вызываемую чтобы уведомить драйвер о том, что было создано новое устройство (за подробностями обращайтесь к этому примеру)

Теперь давайте так же посмотрим на рабочие процедуры драйвера (Driver Dispatch Routines):

Как вы можете видеть, все рабочие процедуры драйвера (Driver Dispatch Routines) указывают на один и тот же адрес kl1+0x32f0.

Заключение

Эта статья была написана для образовательных целей. Анализ поверхностный. Многие вещи были проанализированы очень быстро. Так же всё еще остается место для исследования сетевой части.

© Translated by Prosper-H from r0 Crew

Ссылки

Hunting rootkits with Windbg