• Твой профиль заполнен на 0%. Заполни за 1 минуту, чтобы тебя нашли единомышленники и работодатели. Заполнить →

Статья Rootkit обнаружение Windows: DKOM, SSDT hooking, minifilter и callback-манипуляции через WinDbg и Volatility

Kernel-mode руткиты Windows: обнаружение DKOM, SSDT hooking и callback-манипуляций через WinDbg и Volatility


Антивирус говорит «система чиста», а сетевой трафик тем временем утекает на подозрительный C2-сервер. Знакомая картина? Значит, вы наткнулись на kernel-mode руткит. Не на ту поделку, что прячет файлы через перехват FindFirstFile в user-mode, а на зверя, который работает на одном уровне привилегий с самой ОС и напрямую ковыряет структуру ядра.

По данным Positive Technologies, руткиты - меньше 1% от всех вредоносных программ. Но каждый такой случай - это серьёзный инцидент: APT-кампании, скрытый майнинг на десятках тысяч машин. Причина простая: руткит режима ядра - верхняя точка эскалации, после которой атакующий контролирует всё, что видит (и чего не видит) операционная система.

В MITRE ATT&CK руткиты описаны как техника T1014 (Rootkit). Но реальный руткит - это не одна техника, а комбо: DKOM для скрытия процессов, SSDT hooking для перехвата системных вызовов, minifilter-драйверы для фильтрации файлового I/O и манипуляции с kernel callbacks, чтобы ослепить средства защиты (T1562.001, Disable or Modify Tools). Дальше разберём каждую из этих техник на уровне структур ядра Windows и покажем, как их ловить с помощью WinDbg и Volatility - не по туториалу из первой ссылки в Google, а по реальной методологии анализа.

DKOM атака Windows: как работает скрытие процессов через ActiveProcessLinks​

DKOM (Direct Kernel Object Manipulation) - классика kernel-mode руткитов. Атакующий напрямую правит структуры данных ядра без штатных API. Самый ходовой сценарий - скрытие процесса через удаление из двусвязного списка ActiveProcessLinks.

Механика на уровне ядра​

Каждый процесс в Windows представлен структурой EPROCESS. Эти структуры связаны через поле ActiveProcessLinks - элемент типа LIST_ENTRY с указателями Flink (forward link, на следующий процесс) и Blink (backward link, на предыдущий). Главный списка - глобальная переменная ядра PsActiveProcessHead.

Когда диспетчер задач или Process Explorer запрашивают список процессов через NtQuerySystemInformation, ядро обходит именно этот список. Руткит делает классическое удаление узла из двусвязного списка:
Код:
// Концептуальный псевдокод DKOM-скрытия
// Для демонстрации механизма - не рабочий эксплойт

PEPROCESS targetProcess;  // EPROCESS скрываемого процесса
PLIST_ENTRY prevEntry = targetProcess->ActiveProcessLinks.Blink;
PLIST_ENTRY nextEntry = targetProcess->ActiveProcessLinks.Flink;

// Перелинковка: предыдущий указывает на следующий, минуя целевой
prevEntry->Flink = nextEntry;
nextEntry->Blink = prevEntry;

// Замыкаем ссылки скрытого процесса на самого себя
targetProcess->ActiveProcessLinks.Flink = &targetProcess->ActiveProcessLinks;
targetProcess->ActiveProcessLinks.Blink = &targetProcess->ActiveProcessLinks;
После этой операции процесс продолжает работать - планировщик ядра использует другие структуры для переключения потоков - но становится невидимкой для любого инструмента, работающего через PsActiveProcessHead. Именно так работали руткиты FU и его потомки.

Нюанс: DKOM не ограничивается процессами. Та же техника применяется к спискам драйверов (PsLoadedModuleList), потокам и другим объектам ядра. По MITRE ATT&CK скрытие объектов ядра через DKOM - это T1014 (Rootkit) и более общая T1564 (Hide Artifacts).

Обнаружение DKOM в WinDbg​

Первый шаг - получить список процессов двумя независимыми способами и сравнить. В WinDbg в kernel-mode сессии:
Код:
kd> !process 0 0
Эта команда обходит PsActiveProcessHead - тот самый список, из которого DKOM удаляет записи. Скрытый процесс здесь не всплывёт.

Теперь сравним с другим источником - пулом памяти ядра. Каждый EPROCESS распределён из nonpaged pool с тегом Proc:
Код:
kd> !poolfind Proc 0
Если pool scan находит EPROCESS-блок, чей PID отсутствует в выводе !process 0 0 - это прямой индикатор DKOM. Имейте в виду: !poolfind может молотить десятки минут на больших дампах. На Windows 10 19H1+ (build 18362) и новее с segment heap pool scanning через !poolfind менее надёжен (можно предварительно проверить наличие тега через !poolused Proc) - в таких случаях лучше использовать psscan в Volatility. Также полезно проверить структуру конкретного подозрительного процесса:
Код:
kd> dt nt!_EPROCESS <address>
Смотрим поля ActiveProcessLinks.Flink и ActiveProcessLinks.Blink - если оба указывают на саму структуру (замкнуты на себя), процесс был unlinkован из списка. Это как отпечаток пальца на месте преступления.

Для проверки целостности цепочки можно пройти весь список вручную:
Код:
kd> dl nt!PsActiveProcessHead 200 2
Команда dl проходит по двусвязному списку. Если при обходе вперёд и назад получаем разное количество элементов - список повреждён, что тоже указывает на DKOM.

Обнаружение DKOM в Volatility​

Volatility решает проблему обнаружения DKOM через кросс-валидацию - сравнение разных источников информации о процессах. Ключевой плагин - psxview (только в Volatility 2). В Volatility 3 встроенного аналога нет - кросс-валидация делается вручную через сравнение выводов windows.pslist и windows.psscan.
Bash:
# Volatility 2
python vol.py -f memory.dmp --profile=Win10x64 psxview

# Volatility 3
python vol.py -f memory.dmp windows.pslist
python vol.py -f memory.dmp windows.psscan
psxview сравнивает результаты из нескольких источников:
ИсточникЧто проверяетОбходится DKOM
PsActiveProcessHeadДвусвязный список EPROCESSДа
ETHREAD scanningПотоки, ссылающиеся на процессНет
Pool tag scanning (psscan)Сигнатуры EPROCESS в памятиНет
PspCidTableТаблица объектов по PID/TIDЧастично
Csrss handlesХэндлы csrss.exe к процессамЧастично
Session listСписок процессов в сессииЧастично
Если процесс виден в psscan (сканирование пула), но отсутствует в pslist (обход ActiveProcessLinks) - перед вами DKOM. Классический паттерн: в столбце pslist значение False, а в psscan - True. Лично у меня это был самый частый способ обнаружения - просто diff двух выводов.
Bash:
# Прямое сравнение двух плагинов
python vol.py -f memory.dmp windows.pslist > pslist_output.txt
python vol.py -f memory.dmp windows.psscan > psscan_output.txt
# Любой PID из psscan, отсутствующий в pslist - кандидат на DKOM

SSDT hooking rootkit: перехват таблицы системных вызовов​

SSDT (System Service Descriptor Table) - таблица с адресами обработчиков системных вызовов ядра. Когда user-mode код вызывает NtCreateFile, процессор через syscall/sysenter переходит в kernel-mode, где диспетчер KiSystemService использует номер системного вызова как индекс в SSDT для вызова нужной функции. В MITRE ATT&CK SSDT hooking описывается техникой T1014 (Rootkit), тактика Defense Evasion. Примечание: тесты Atomic Red Team для T1014 есть только для Linux; на Windows валидация требует других подходов. Ранее существовала техника T1179 (Hooking), покрывавшая Persistence, Privilege Escalation и Credential Access, но её отозвали (deprecated) начиная с ATT&CK v8 (октябрь 2020); часть функциональности мигрировала в T1056.004 (Credential API Hooking) и другие суб-техники.

Механика SSDT hook​

Руткит подменяет адрес в таблице SSDT, заставляя ядро вызывать свой обработчик вместо оригинального. Через это можно перехватить что угодно: скрывать файлы через хук NtQueryDirectoryFile, процессы через хук NtQuerySystemInformation, сетевые соединения через хук NtDeviceIoControlFile.

На современных 64-битных Windows SSDT хранит не абсолютные адреса, а относительные смещения. Каждая запись - 4-байтовое значение, где верхние 28 бит - смещение от базы KeServiceDescriptorTable, а нижние 4 бита - количество аргументов, передаваемых через стек. Это усложняет hooking по сравнению с 32-битными системами, но не делает его невозможным.

На 64-битных Windows с активным PatchGuard (Kernel Patch Protection) прямая модификация SSDT вызывает BSOD с кодом CRITICAL_STRUCTURE_CORRUPTION. Однако техники обхода PatchGuard задокументированы, и некоторые руткиты - Turla, FiveSys - успешно их применяли. PatchGuard проверяет целостность периодически, а не в реальном времени, что создаёт окно для атаки. Так-что не стоит считать его непробиваемым щитом.

Обнаружение SSDT hooking через WinDbg​

Смотрим базу SSDT и проверяем, что все адреса указывают внутрь ntoskrnl.exe:
Код:
kd> dps nt!KiServiceTable L 0x200
Команда выведет таблицу с адресами и символами. Все записи должны резолвиться (DNS resolution) в функции nt!Nt*. Если какая-то запись указывает на адрес вне диапазона ntoskrnl - это хук, и тут уже пора напрягаться.

Проверяем диапазон ntoskrnl:
Код:
kd> lm m nt
Запоминаем Start и End адреса. Любой адрес в SSDT, выходящий за этот диапазон, - аномалия. На 64-битных системах с относительными смещениями:
Код:
kd> dd nt!KiServiceTable L 0x10
Каждое 4-байтовое значение - relative offset. Для преобразования в абсолютный адрес: базовый_адрес + (offset >> 4). Если результат выходит за пределы ntoskrnl - хук.

Для проверки конкретного системного вызова, например NtCreateFile (syscall номер зависит от версии Windows):
Код:
kd> u nt!NtCreateFile
Если первые инструкции содержат jmp на адрес вне ntoskrnl - это inline hook. Ещё один вариант перехвата, при котором сама SSDT не тронута, но код функции подменён. Хитрее, но и обнаруживается несложно.

Обнаружение SSDT hooks в Volatility​

Bash:
# Volatility 2
python vol.py -f memory.dmp --profile=Win10x64 ssdt

# Volatility 3
python vol.py -f memory.dmp windows.ssdt
Плагин ssdt выводит каждую запись SSDT с resolved символами. Ключевой индикатор: если функция резолвится не в ntoskrnl.exe или win32k.sys (для Shadow SSDT), а в неизвестный модуль или адрес вообще без модуля - это hooking.
Код:
# Пример подозрительного вывода (для демонстрации концепции)
Entry 0x0049: 0xfffff880012345678  unknown_driver.sys!HookedNtQueryDirectoryFile
Нормальные записи выглядят так:
Код:
Entry 0x0049: 0xfffff80001234567  ntoskrnl.exe!NtQueryDirectoryFile

Minifilter driver abuse: невидимая фильтрация файловых операций​

Minifilter-драйверы - легитимный фреймворк Windows (Filter Manager, fltmgr.sys) для перехвата файловых операций ввода-вывода. Антивирусы, DLP-системы, средства шифрования - все используют minifilter для сканирования файлов на лету. Но тот же механизм с удовольствием эксплуатируют руткиты.

Как работает minifilter​

Filter Manager организует minifilter-драйверы в стек по altitude - числовому значению, определяющему приоритет обработки. Антивирусные фильтры обычно имеют altitude 320000–329999, фильтры шифрования - 140000–149999. Каждый minifilter регистрирует callback-функции для конкретных операций (Pre-Operation и Post-Operation): создание файла, чтение, запись, запрос директории.

Вредоносный minifilter может:
  • Скрывать файлы руткита, фильтруя результаты IRP_MJ_DIRECTORY_CONTROL
  • Блокировать доступ антивируса к вредоносным файлам через IRP_MJ_CREATE
  • Подменять содержимое файлов при чтении через IRP_MJ_READ
  • Перехватывать и эксфильтровать записываемые данные
По MITRE ATT&CK это связано с T1547.006 (Kernel Modules and Extensions) - сохранение состояния системы с помощью драйвера, T1014 (Rootkit) - скрытие файлов через minifilter callbacks, и T1562.001 (Disable or Modify Tools) - блокировка доступа антивируса к вредоносным файлам.

Обнаружение вредоносных minifilter-драйверов​

В WinDbg проверяем загруженные minifilter-драйверы:
Код:
kd> !fltkd.filters
Расширение !fltkd даёт полный доступ к внутренним структурам Filter Manager. Команда выведет все зарегистрированные фильтры с их altitude и количеством зарегистрированных операций.
Код:
kd> !fltkd.frames
Показывает фреймы Filter Manager и все присоединённые фильтры. На что смотреть:
  • Фильтры с необычными именами или без цифровой подписи
  • Фильтры с altitude, не соответствующим их заявленному назначению (антивирус с altitude шифровальщика - подозрительно)
  • Фильтры, зарегистрировавшие callbacks для IRP_MJ_DIRECTORY_CONTROL или IRP_MJ_CREATE - именно они используются для скрытия файлов
Для проверки конкретного фильтра:
Код:
kd> !fltkd.filter <filter_address>
Покажет все зарегистрированные Pre/Post callback-функции. Адрес каждого callback должен резолвиться в известный модуль.

В Volatility minifilter-анализ доступен через плагин driverirp и анализ загруженных модулей:
Bash:
# driverirp -r fltmgr показывает IRP dispatch table самого fltmgr.sys,
# а НЕ callback-функции зарегистрированных minifilter-драйверов.
# Для анализа minifilter-стека используйте WinDbg с !fltkd.filters.
python vol.py -f memory.dmp --profile=Win10x64 driverirp -r "fltmgr"
Для обнаружения скрытых minifilter-драйверов через Volatility используйте driverscan и modscan - они находят драйверы, скрытые из штатного списка загруженных модулей (PsLoadedModuleList), но всё ещё присутствующие в памяти. Для полного анализа minifilter-стека (зарегистрированные callback-функции, altitude, перехватываемые операции) основной метод - WinDbg с расширением !fltkd.filters; в продвинутых случаях возможен ручной анализ структур FLT_FILTER в дампе памяти, но это уже для любителей острых ощущений.

Kernel callback manipulation: нейтрализация защитных механизмов​

Ядро Windows предоставляет набор notification callback-механизмов, которые средства защиты используют для мониторинга системных событий. Руткиты манипулируют этими callback-таблицами, чтобы «ослепить» антивирусы и EDR.

Целевые callback-механизмы​

CallbackФункция регистрацииНазначение
Process creation (Ex)Уведомление о создании/завершении процессов
Thread creationPsSetCreateThreadNotifyRoutineУведомление о создании потоков
Image loadPsSetLoadImageNotifyRoutineУведомление о загрузке образов (DLL, EXE)
RegistryCmRegisterCallbackExУведомление об операциях с реестром
Object accessObRegisterCallbacksФильтрация доступа к объектам (процессам, потокам)
EDR-решения регистрируют свои callback-функции через эти API. Руткит может:
  1. Удалить callback из массива (по аналогии с DKOM - unlinking)
  2. Подменить адрес callback-функции на заглушку, которая тупо ничего не делает
  3. Пропатчить код самой callback-функции (inline hook), заставляя её сразу возвращать управление
Это прямая реализация техники Disable or Modify Tools (T1562.001, Defense Evasion) - атакующий не уничтожает средство защиты, а тихо выключает ему глаза и уши.

Обнаружение callback-манипуляций в WinDbg​

Проверяем массив notification routines для процессов:
Код:
kd> dt nt!_EX_CALLBACK_ROUTINE_BLOCK
Для проверки зарегистрированных Process notification callbacks:
Код:
kd> x nt!PspCreateProcessNotifyRoutine
kd> dps nt!PspCreateProcessNotifyRoutine L 0x40
Каждый ненулевой элемент - указатель на EX_CALLBACK_ROUTINE_BLOCK. Значения должны резолвиться в известные модули защитных средств. Если слот обнулён или указывает на неизвестный модуль - callback был удалён или подменён. Вот тут-то и начинается самое интересное.

Аналогично для потоков и загрузки образов:
Код:
kd> dps nt!PspLoadImageNotifyRoutine L 0x40
kd> dps nt!PspCreateThreadNotifyRoutine L 0x40
Для ObRegisterCallbacks проверка сложнее - нужно пройти по списку ObTypeInitializer для объектов типа Process и Thread:
Код:
kd> !object \ObjectTypes\Process
kd> dt nt!_OBJECT_TYPE <address> -r1

Обнаружение через Volatility​

Bash:
# Volatility 2
python vol.py -f memory.dmp --profile=Win10x64 callbacks

# Volatility 3
python vol.py -f memory.dmp windows.callbacks
Плагин callbacks покажет все зарегистрированные kernel notification callbacks с указанием модуля-владельца. Красные флаги:
  • Callback, принадлежащий неизвестному или неподписанному модулю
  • Отсутствие callbacks от EDR, который точно установлен на системе (EDR есть, а его callbacks нет - значит, кто-то их вычистил)
  • Callback-функция в области памяти без привязки к модулю - это шеллкод

Практический workflow обнаружения руткитов Windows​

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

Шаг 1: Снятие дампа памяти​

Используем WinPmem или DumpIt для получения raw memory dump. Если подозревается руткит - не доверяйте инструментам, работающим из-под скомпрометированной системы. Идеально - DMA-устройство (PCILeech) или дамп через гипервизор. На практике часто приходится довольствоваться WinPmem с USB-носителя - не идеал, но лучше, чем ничего.
Bash:
# WinPmem (запуск с USB-носителя)
winpmem_mini_x64.exe output.raw

Шаг 2: Кросс-валидация списков процессов​

Bash:
python vol.py -f output.raw windows.pslist > step2_pslist.txt
python vol.py -f output.raw windows.psscan > step2_psscan.txt
Сравниваем PID из обоих файлов. Любое расхождение - первый индикатор DKOM.

Шаг 3: Проверка SSDT и драйверов​

Bash:
python vol.py -f output.raw windows.ssdt > step3_ssdt.txt
python vol.py -f output.raw windows.modules > step3_modules.txt
python vol.py -f output.raw windows.driverscan > step3_drivers.txt
В выводе ssdt все записи должны указывать на ntoskrnl.exe или win32k.sys. В driverscan ищем драйверы, отсутствующие в modules (аналог DKOM, только для модулей).

Шаг 4: Анализ callbacks​

Bash:
python vol.py -f output.raw windows.callbacks > step4_callbacks.txt
Проверяем наличие callbacks от установленных защитных средств. Если EDR установлен, но его callbacks отсутствуют - руткит их вычистил.

Шаг 5: Углублённый анализ в WinDbg​

Если на шагах 2-4 обнаружены аномалии, открываем дамп в WinDbg для детальной процедуры:
Код:
kd> .sympath srv*c:\symbols*https://msdl.microsoft.com/download/symbols
kd> .reload /f

;// Проверяем подозрительный процесс из psscan
kd> dt nt!_EPROCESS <address_from_psscan>
kd> !process <address_from_psscan> 7

;// Смотрим потоки скрытого процесса
kd> !process <address_from_psscan> 4
Поле ActiveProcessLinks у скрытого процесса покажет замкнутые на себя указатели. Поле ImageFileName - имя исполняемого файла руткита.

Шаг 6: Восстановление полной картины​

Для каждого обнаруженного артефакта фиксируем:
  • PID и имя скрытого процесса
  • Загруженные им модули (!process <addr> 7 покажет VAD-дерево)
  • Сетевые соединения (windows.netscan в Volatility)
  • Файлы, открытые процессом (windows.handles)

PatchGuard и современные ограничения руткитов​

На 64-битных Windows PatchGuard ( ) защищает критические структуры ядра: SSDT, GDT, IDT, системные образы, структуры объектов процессов. Периодические проверки целостности вызывают BSOD при обнаружении модификаций.

Классический SSDT hooking на современных 64-битных Windows - техника с высоким риском словить BSOD. Поэтому современные руткиты смещают фокус:
  • Minifilter-драйверы - легитимный механизм, PatchGuard не проверяет
  • Callback-манипуляции - менее заметны, чем прямые модификации SSDT
  • DKOM с осторожным восстановлением после проверок PatchGuard
  • Обход через уязвимости в подписанных драйверах (BYOVD - Bring Your Own Vulnerable Driver)
Руткит FiveSys использовал украденную цифровую подпись Microsoft WHQL, что позволяло ему загружаться как легитимный драйвер и обходить проверки целостности. Это наглядно показывает, что даже PatchGuard и DSE (Driver Signature Enforcement) - не абсолютная защита. На заборе тоже написано «защищено», а на деле..

Что упускают стандартные средства защиты​

Большинство антивирусов работают в user-mode или используют ограниченный набор kernel callbacks. Kernel-mode руткит, работающий на том же уровне привилегий, может:
  • Скрыть свой процесс через DKOM - антивирус не увидит его в списке процессов
  • Удалить callbacks антивируса - тот перестанет получать уведомления о событиях
  • Зарегистрировать minifilter с более высоким altitude - обработать запрос к файлу раньше, чем до него доберётся антивирус
Именно поэтому для обнаружения kernel-mode руткитов нужен офлайн-анализ памяти через Volatility или анализ через отладчик ядра (WinDbg) - подходы, которые работают извне скомпрометированного ядра и не зависят от API, которые руткит может контролировать.

Rootkit обнаружение Windows - это всегда комбинация: кросс-валидация (сравнение нескольких источников данных о процессах), проверка целостности критических структур (SSDT, callback-массивы) и анализ загруженных драйверов (minifilter-стек, неподписанные модули). Ни один инструмент не ловит всё - поэтому WinDbg и Volatility работают в паре, компенсируя ограничения друг друга. Попробуйте прогнать свой дамп по workflow из шагов 1-6 - даже на «чистой» системе результаты бывают... познавательными.
 
Последнее редактирование модератором:
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

Похожие темы

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab