Антивирус говорит «система чиста», а сетевой трафик тем временем утекает на подозрительный 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
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
nt!Nt*. Если какая-то запись указывает на адрес вне диапазона ntoskrnl - это хук, и тут уже пора напрягаться.Проверяем диапазон ntoskrnl:
Код:
kd> lm m nt
Код:
kd> dd nt!KiServiceTable L 0x10
базовый_адрес + (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 - Перехватывать и эксфильтровать записываемые данные
Обнаружение вредоносных minifilter-драйверов
В WinDbg проверяем загруженные minifilter-драйверы:
Код:
kd> !fltkd.filters
!fltkd даёт полный доступ к внутренним структурам Filter Manager. Команда выведет все зарегистрированные фильтры с их altitude и количеством зарегистрированных операций.
Код:
kd> !fltkd.frames
- Фильтры с необычными именами или без цифровой подписи
- Фильтры с altitude, не соответствующим их заявленному назначению (антивирус с altitude шифровальщика - подозрительно)
- Фильтры, зарегистрировавшие callbacks для
IRP_MJ_DIRECTORY_CONTROLилиIRP_MJ_CREATE- именно они используются для скрытия файлов
Код:
kd> !fltkd.filter <filter_address>
В 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"
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 creation | PsSetCreateThreadNotifyRoutine | Уведомление о создании потоков |
| Image load | PsSetLoadImageNotifyRoutine | Уведомление о загрузке образов (DLL, EXE) |
| Registry | CmRegisterCallbackEx | Уведомление об операциях с реестром |
| Object access | ObRegisterCallbacks | Фильтрация доступа к объектам (процессам, потокам) |
- Удалить callback из массива (по аналогии с DKOM - unlinking)
- Подменить адрес callback-функции на заглушку, которая тупо ничего не делает
- Пропатчить код самой callback-функции (inline hook), заставляя её сразу возвращать управление
Обнаружение callback-манипуляций в WinDbg
Проверяем массив notification routines для процессов:
Код:
kd> dt nt!_EX_CALLBACK_ROUTINE_BLOCK
Код:
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
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
Шаг 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
Шаг 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)
Что упускают стандартные средства защиты
Большинство антивирусов работают в user-mode или используют ограниченный набор kernel callbacks. Kernel-mode руткит, работающий на том же уровне привилегий, может:- Скрыть свой процесс через DKOM - антивирус не увидит его в списке процессов
- Удалить callbacks антивируса - тот перестанет получать уведомления о событиях
- Зарегистрировать minifilter с более высоким altitude - обработать запрос к файлу раньше, чем до него доберётся антивирус
Rootkit обнаружение Windows - это всегда комбинация: кросс-валидация (сравнение нескольких источников данных о процессах), проверка целостности критических структур (SSDT, callback-массивы) и анализ загруженных драйверов (minifilter-стек, неподписанные модули). Ни один инструмент не ловит всё - поэтому WinDbg и Volatility работают в паре, компенсируя ограничения друг друга. Попробуйте прогнать свой дамп по workflow из шагов 1-6 - даже на «чистой» системе результаты бывают... познавательными.
Последнее редактирование модератором: