Сергей Попов
15.04.2026, 12:30
https://forum.antichat.xyz/attachments/4951472/img_6ef858b08c.png
Антивирус говорит «система чиста», а сетевой трафик тем временем утекает на подозрительный C2-сервер. Знакомая картина? Значит, вы наткнулись на kernel-mode руткит. Не на ту поделку, что прячет файлы через перехват FindFirstFile в user-mode, а на зверя, который работает на одном уровне привилегий с самой ОС и напрямую ковыряет структуру ядра.
По данным Positive Technologies, руткиты - меньше 1% от всех вредоносных программ. Но каждый такой случай - это серьёзный инцидент: APT-кампании, скрытый майнинг на десятках тысяч машин. Причина простая: руткит режима ядра - верхняя точка эскалации, после которой атакующий контролирует всё, что видит (и чего не видит) операционная система.
В MITRE ATT&CK руткиты описаны как техника T1014 (Rootkit). Но реальный руткит - это не одна техника, а комбо (полную классификацию с матрицей обнаружения см. в обзоре техник руткитов (https://forum.antichat.xyz/threads/592911/)): 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
Смотрим поля
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
сравнивает результаты из нескольких источников:
ИсточникЧто проверяетОбходится DKOMPsActiveProcessHeadДвусвязный список 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
Покажет все зарегистрированные 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 creationPsSetCreateProcessNotifyRoutine(Ex)Уве омление о создании/завершении процессовThread creationPsSetCreateThreadNotifyRoutineУведом ление о создании потоковImage loadPsSetLoadImageNotifyRoutineУведомлен е о загрузке образов (DLL, EXE)RegistryCmRegisterCallbackExУведомлен ие об операциях с реестромObject accessObRegisterCallbacksФильтрация доступа к объектам (процессам, потокам)
EDR-решения регистрируют свои callback-функции через эти API. Руткит может:
Удалить callback из массива (по аналогии с DKOM - unlinking)
Подменить адрес callback-функции на заглушку, которая тупо ничего не делает
Пропатчить код самой 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 -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
kd> !process 7
;// Смотрим потоки скрытого процесса
kd> !process 4
Поле
ActiveProcessLinks
у скрытого процесса покажет замкнутые на себя указатели. Поле
ImageFileName
- имя исполняемого файла руткита.
Шаг 6: Восстановление полной картины
Для каждого обнаруженного артефакта фиксируем:
PID и имя скрытого процесса
Загруженные им модули (
!process 7
покажет VAD-дерево)
Сетевые соединения (
windows.netscan
в Volatility)
Файлы, открытые процессом (
windows.handles
)
PatchGuard и современные ограничения руткитов
На 64-битных Windows PatchGuard (Kernel Patch Protection) защищает критические структуры ядра: 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 (https://forum.antichat.xyz/threads/592233/) или анализ через отладчик ядра (WinDbg) - подходы, которые работают извне скомпрометированного ядра и не зависят от API, которые руткит может контролировать.
Rootkit обнаружение Windows - это всегда комбинация: кросс-валидация (сравнение нескольких источников данных о процессах), проверка целостности критических структур (SSDT, callback-массивы) и анализ загруженных драйверов (minifilter-стек, неподписанные модули). Ни один инструмент не ловит всё - поэтому WinDbg и Volatility работают в паре, компенсируя ограничения друг друга. Попробуйте прогнать свой дамп по workflow из шагов 1-6 - даже на «чистой» системе результаты бывают... познавательными.
Антивирус говорит «система чиста», а сетевой трафик тем временем утекает на подозрительный C2-сервер. Знакомая картина? Значит, вы наткнулись на kernel-mode руткит. Не на ту поделку, что прячет файлы через перехват FindFirstFile в user-mode, а на зверя, который работает на одном уровне привилегий с самой ОС и напрямую ковыряет структуру ядра.
По данным Positive Technologies, руткиты - меньше 1% от всех вредоносных программ. Но каждый такой случай - это серьёзный инцидент: APT-кампании, скрытый майнинг на десятках тысяч машин. Причина простая: руткит режима ядра - верхняя точка эскалации, после которой атакующий контролирует всё, что видит (и чего не видит) операционная система.
В MITRE ATT&CK руткиты описаны как техника T1014 (Rootkit). Но реальный руткит - это не одна техника, а комбо (полную классификацию с матрицей обнаружения см. в обзоре техник руткитов (https://forum.antichat.xyz/threads/592911/)): 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
Смотрим поля
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
сравнивает результаты из нескольких источников:
ИсточникЧто проверяетОбходится DKOMPsActiveProcessHeadДвусвязный список 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
Покажет все зарегистрированные 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 creationPsSetCreateProcessNotifyRoutine(Ex)Уве омление о создании/завершении процессовThread creationPsSetCreateThreadNotifyRoutineУведом ление о создании потоковImage loadPsSetLoadImageNotifyRoutineУведомлен е о загрузке образов (DLL, EXE)RegistryCmRegisterCallbackExУведомлен ие об операциях с реестромObject accessObRegisterCallbacksФильтрация доступа к объектам (процессам, потокам)
EDR-решения регистрируют свои callback-функции через эти API. Руткит может:
Удалить callback из массива (по аналогии с DKOM - unlinking)
Подменить адрес callback-функции на заглушку, которая тупо ничего не делает
Пропатчить код самой 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 -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
kd> !process 7
;// Смотрим потоки скрытого процесса
kd> !process 4
Поле
ActiveProcessLinks
у скрытого процесса покажет замкнутые на себя указатели. Поле
ImageFileName
- имя исполняемого файла руткита.
Шаг 6: Восстановление полной картины
Для каждого обнаруженного артефакта фиксируем:
PID и имя скрытого процесса
Загруженные им модули (
!process 7
покажет VAD-дерево)
Сетевые соединения (
windows.netscan
в Volatility)
Файлы, открытые процессом (
windows.handles
)
PatchGuard и современные ограничения руткитов
На 64-битных Windows PatchGuard (Kernel Patch Protection) защищает критические структуры ядра: 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 (https://forum.antichat.xyz/threads/592233/) или анализ через отладчик ядра (WinDbg) - подходы, которые работают извне скомпрометированного ядра и не зависят от API, которые руткит может контролировать.
Rootkit обнаружение Windows - это всегда комбинация: кросс-валидация (сравнение нескольких источников данных о процессах), проверка целостности критических структур (SSDT, callback-массивы) и анализ загруженных драйверов (minifilter-стек, неподписанные модули). Ни один инструмент не ловит всё - поэтому WinDbg и Volatility работают в паре, компенсируя ограничения друг друга. Попробуйте прогнать свой дамп по workflow из шагов 1-6 - даже на «чистой» системе результаты бывают... познавательными.