Криминалистический анализ памяти: Исследуем процессы в Windows 7Привет, мой дорогой читатель! Сегодня я познакомлю тебя с продвинутым способом детекта скрытых модулей процессов. Ты уже знаешь, что большинство руткитов скрывают свои модули широко известным способом — через удаление их из PEB. Но вот способ, с помощью которого такие скрытые модули определяются, и которым пользуются антируткиты, недокументирован и мало где описан. Сейчас я внесу свой светлый луч в это темное царство. Но для понимания этой статьи от тебя понадобятся знания в области внутреннего устройства ядра. Суть проблемыВсем известно, что Windows очень гибко управляет распределением физической памяти. Она выдается процессу только тогда, когда он к ней реально обратится (исключение составляет лишь #PF). В момент такого обращения диспетчер памяти (или VMM) должен различить ситуацию нарушения доступа (обращения к участку памяти, который не был зарезервирован, или под него не выделена физическая память) от ситуации, когда память передана, но физически еще не выделена. VMM полагается на структуры дескрипторов виртуальных адресов (Virtual Address Descriptor), которые организованы в дерево на основе номеров страниц. Общая схема работы VAD есть у Руссиновича в главе про диспетчер виртуальной памяти. Детальную же информацию по структурам нам может дать только windbg. Суть VAD’ов в том, что они помогают обнаруживать библиотеки, загруженные в память процессов, которые скрывают руткиты (например, давно известным способом удаления из PEB). Когда я впервые заинтересовался структурой VAD для обнаружения скрытых dll, мне на глаза попалась замечательная статья журнала Digital Investigation — «The VAD tree: A process-eye view of physical memory» (dfrws.org/2007/proceedings/p62-dolan-gavitt.pdf). Материал дает подробную информацию об устройстве VAD, но, к сожалению, вопрос лишен практической стороны. Обобщая эту статью и добавляя практическую часть, я наглядно объясню, как с помощью анализа VAD антируткиты показывают список загруженных в процессы DLL. Что такое VAD, и с чем его едятУсловно, VAD — это структура, которая описывает регион адресного пространства. Например, при вызове функции VirtualAlloc с параметром MEM_RESERVE резервируется регион требуемого размера и создается VAD, описывающий этот регион. При передаче физической памяти параметра MEM_COMMIT система пометит в этой структуре количество переданных страниц. Другой пример: система загружает DLL в адресное пространство процесса, соответственно, это приводит к резервированию региона памяти, и тогда создается VAD, который описывает данный регион. Принцип один, разница лишь в том, какая структура VAD будет использоваться для того или иного случая резервирования. Внутри себя ядро использует функцию MiCheckVirtualAddress, которая первым аргументом принимает виртуальный адрес, а возвращает указатель на соответствующий этому адресу PTE. При этом в третий аргумент записывается указатель на соответствующий адрес VAD. Ее структура выглядит так: VAD БЫВАЕТ РАЗНЫМВидов VAD бывает несколько: _MMVAD_SHORT, _MMVAD и _MMVAD_LONG. Причем каждый последующий фактически расширяет предыдущий. Шапка у всех одна и выглядит так (см. также _MMADDRESS_NODE):
MMVAD_SHORT не подойдет для наших целей, так как с его помощью описываются обычные приватные страницы, выделенные, например, с помощью VitualAlloc. А вот _MMVAD — как раз то, что нужно.
Через область управления разделом мы можем выйти на FILE_OBJECT, который специально создан для проекции файла.
При получении указателя на FileObject мы зануляем первые три бита FilePointer маской 0xFFFFFFF8. Указатель на корневой узел хранится в EPROCESS в поле VadRoot, смещение которого меняется от версии к версии, поэтому неплохо было бы захардкодить его под разные версии. Как видно из структуры _VAD, первые два бита адреса Parent, используются для служебных целей, поэтому для получения валидного адреса их также нужно занулять. Поле применяется для получения указателя на VAD верхнего уровня. Формат VadRoot в EPROCESS различен в разных версиях NT. В Windows 7 он представляет собой структуру MM_AVL_TABLE (очевидно, разработчики добавили информацию о балансировке дерева).
Некоторая сложность заключается в том, что BalancedRoot. u1.Parent не является истинной вершиной дерева, для этого нужно анализировать RightChild, но его можно использовать для начала обхода дерева, потому что он указывает сам на себя. При прохождении по дереву VAD нужно отделять MMVAD_SHORT от MMVAD и MMVAD_LONG. Это можно делать по тэгу блока пула, которому принадлежит структура. Собственно тэг хранится по смещению 4 от самого блока (и по обратному смещению -4 от начала структуры). Диспетчер памяти присваивает тэг «VadS» для _MMVAD_SHORT, «Vad» для _MMVAD, «Vadl» для _MMVAD_LONG. Кроме того, разумеется, нужно проводить валидацию промежуточных параметров типа ControlArea, Subsection, FilePointer. ПрактикуемсяПроведем исследование вадов. Выберем из списка процессов (!process 0 0) некий процесс:
VAD описывает регион 0x703b0000-0x703e1000 включительно, а также имеет указатель в подраздел 0x84820110 (секция раздела, в случае с EXE-файлами — подраздел на каждую секцию).
Несложным расчетом определяем, что объект: 0x88cbc79a & 0xFFFFFFF8 = 0x88CBC798.
В итоге мы получили информацию о том, что VAD описывает спроецированную winmm.dll и тем самым вышли на конкретный файл! Обрати внимание, что в Windows 2000/XP не нужно выравнивать указатель при получении адреса на VadRoot, потому что там он имеет вид PVOID VadRoot. Дерево VAD доступно только в «живых» или запущенных процессах. Ядро зануляет указатель на вершину дерева с завершением процесса. Это ограничивает полезность данного метода для изучения некоторых процессов. На этом пока все. Изучай структуры, экспериментируй, и ты выявишь абсолютно любой руткит, даже базируясь на моем примере. Если есть вопросы — пиши на почту, и я всегда помогу. |