Способ динамического поиска глобальных переменных ядра в операционных системах семейства Windows nt

1В.В. Черняковский СПОСОБ ДИНАМИЧЕСКОГО ПОИСКА

глобальных переменных ЯДРА в операционных системах

СЕМЕЙСТВА WINDOWS NT

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

Ключевые слова: операционная система, семейство Windows NT, глобальные переменные ядра, отладчик ядра, драйвер, руткит, программные средства защиты.

Введение

Со стремительным развитием информационных технологий средства современной вычислительной техники становятся неотъемлемой частью при организации производства и построении бизнес-процессов на предприятиях разной величины. Возрастающее количество используемых аппаратных средств и увеличение функциональных возможностей создаваемых программных средств усложняют процедуры контроля происходящих в информационных системах процессов, что приводит к снижению их защищенности, а также отказоустойчивости. Увеличение функциональных возможностей программных средств неизбежно ведет к появлению неде-кларируемых возможностей программного обеспечения и ошибок в логике их работы, информация о которых может быть применена для подготовки и проведения атак на информационные системы1.

В последнее время стремительно стали развиваться и распространяться новые виды вредоносных программ (malware), разрабатываемых с применением механизмов руткит-технологий, которые позволяют нарушителю безопасности скрывать факты своего при© Черняковский В.В., 2011

сутствия и активной деятельности в пораженной системе. Такие вредоносные программы носят название руткитов2 (rootkit), они представляют огромную угрозу для безопасности информационных систем, поскольку их обнаружение является проблемой для современных средств защиты, ориентированных на обнаружение и противодействие другого рода угрозам.

Для противодействия такого рода угрозам разрабатывается отдельный класс средств защиты, известных как системы предотвращения вторжений (Host Intrusion Prevention System - HIPS). Эти программные средства защиты основаны, как правило, на использовании штатных механизмов защиты, предоставляемых самой операционной системой (ОС). В свою очередь штатные механизмы защиты ОС представляют собой некоторые алгоритмы управления ключевыми компонентами ядра ОС (диспетчер системных сервисов, диспетчер процессов и потоков, диспетчер ввода / вывода и др.). Каждый из таких ключевых компонентов ОС представляет собой некоторый объект ядра, который описывается определенной структурой в памяти. Чтобы иметь возможность управления (создания, удаления, изменения) и контроля над этими объектами, ОС хранит их адреса в глобальных переменных ядра.

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

Целью данной работы является разработка нового способа нахождения адресов глобальных переменных ядра, применимого в ряде современных ОС семейства Windows NT.

Для достижения поставленной цели необходимо решение следующих задач:

- теоретически обосновать возможность создания нового способа поиска глобальных переменных ядра в ОС семейства Windows NT;

- экспериментально обосновать возможность практического использования разработанного способа.

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

- архитектура и внутреннее устройство ОС семейства Windows NT;

- руткит-технологии, основные механизмы и техники, способы противодействия;

- известные способы нахождения глобальных переменных ядра;

- разработка драйверов для современных ОС семейства Windows NT.

Теоретическое обоснование

Идея разработки нового способа динамического поиска глобальных переменных ядра основана на результатах исследований, опубликованных Эдгаром Барбосой3. В ходе исследования ядра ОС Windows XP (NT 5.1) им было обнаружено некоторое изменение в описании одной из ключевых структур ядра относительно ОС Windows 2000 (NT 5.0). Изменение заключается в том, что в структуре области управления процессором KPCR (Kernel Processor Control Region), которая описывает состояние каждого процессора в системе, поле Reserved2 изменило свое название на KdVersionBlock. Помимо этого, вместо нулевого значения это поле содержало в качестве своего значения некоторый адрес, т. е. являлось указателем на некую внутреннюю структуру ядра. В ходе проведения дальнейших исследований ядра ОС Windows XP было выяснено, что поле KdVersionBlock структуры KPCR содержит указатель на структуру nt!KdVersionBlock, которая, в свою очередь, содержит указатель на известную структуру nt!KdDebuggerDataBlock. Эти внутренние структуры ядра ОС используются отладчиком ядра для быстрого определения текущего состояния ОС и ее основных компонентов, а также для предоставления необходимой отладочной информации, например о причинах краха системы. Полные описания этих структур (под именами DBGKD_GET_VERSION и KDDEBUGGER_DATA соответственно) можно найти в файле wdbgexts.h, входящем в состав заголовочных файлов набора для разработки драйверов (Windows Driver Kit - WDK), доступном на официальном сайте компании Microsoft Corporation.

Данные структуры в совокупности содержат список большинства глобальных переменных ядра, используемых различными компонентами ОС. Результаты анализа содержимого данных структур позволили сделать вывод о том, что в них находится необходимое и достаточное количество информации, позволяющее динамически определять текущее состояние ОС и ее основных компонентов. На

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

Рис. 1. Взаимосвязь структур, содержащих отладочную информацию

Используя полученную взаимосвязь, можно составить алгоритм нахождения адресов большинства глобальных переменных ядра. Этот алгоритм и положен в основу предлагаемого способа динамического поиска глобальных переменных ядра, среди которых наибольший интерес представляют следующие:

- KernelBase, адрес загрузки образа ядра ОС в памяти;

- PsActiveProcessHead, указатель на голову списка активных процессов в системе;

- PsLoadedModuleList, указатель на голову списка загруженных исполняемых модулей ядра (драйверов) в системе;

- PspCidTable, указатель на таблицу дескрипторов (описателей) существующих в системе процессов и потоков.

Алгоритм поиска некоторой глобальной переменной ядра состоит из следующих этапов:

1) получить адрес структуры KPCR, описывающей состояние текущего процессора (он хранится в регистре fs в 32-разрядных версиях ОС и регистре gs в 64-разрядных4);

2) получить адрес структуры DBGKD_GET_VERSION (он хранится в поле KdVersionBlock структуры KPCR);

3) получить адрес структуры KDDEBUGGER_DATA (он хранится в поле DebuggerDataList структуры DBGKD_GET_ VERSION);

4) получить адрес интересующей глобальной переменной (значение соответствующего поля структуры KDDEBUGGER_ DATA).

Использование предлагаемого способа динамического поиска глобальных переменных ядра позволяет избежать многих принципиальных проблем, встающих перед разработчиками современных средств защиты для ОС семейства Windows NT.

Поддержка различных версий ОС. Адреса глобальных переменных ядра отличаются в различных версиях ядра ОС (как в ядрах новых версий ОС, так и в ядрах, предназначенных для различных типов процессоров), даже очередной пакет обновлений (Service Pack) в рамках одной версии ОС может внести достаточные изменения в бинарный образ ядра, чтобы изменить адреса глобальных переменных. Помимо этого, в версиях ОС начиная с Windows Vista (NT 6.0) введен новый механизм защиты - рандомизация адресного пространства5 (Address Space Layout Randomization - ASLR). Его суть заключается в том, что бинарные образы ядра, его модулей, динамических библиотек и приложений загружаются в память по вычисляемым случайным образом адресам, что нарушает работу не только вредоносных программ, но и программных средств защиты. Поэтому использование статически заданных адресов и смещений относительно других объектов ядра не является достаточно эффективным способом.

Производительность и стабильность. Многие средства защиты используют сигнатурный поиск для нахождения адресов глобальных переменных, что позволяет устранить проблему поддержки различных версий ОС. То есть происходит сканирование физической памяти на предмет совпадения содержимого ячеек памяти с некоторым заданным шаблоном (последовательностью байт). Такой способ заметно теряет свою эффективность на машинах с большим объемом физической памяти, тем более нет никаких гарантий, что найденное совпадение - это не случайный мусор в памяти, что может привести к краху всей системы. К тому же не каждая глобальная переменная ядра имеет известную сигнатуру, что является серьезным ограничением на использование сигнатурного поиска.

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

- платформенная независимость;

- быстродействие и надежность;

- простота реализации и использования.

Экспериментальное обоснование

На основе вышеописанного способа был разработан модуль ядра для динамического получения адресов некоторых глобальных переменных ядра ОС семейства Windows NT.

На листинге 1 представлены описания необходимых структур на языке программирования C, который используется для разработки драйверов для ОС семейства Windows NT.

// Описание структуры ядра nt!KdVersionBlock

typedef struct _DBGKD_GET_VERSION {

USHORT Maj orVersion;

USHORT MinorVersion;

UCHAR ProtocolVersion;

UCHAR KdSecondaryVersion;

USHORT Flags;

USHORT MachineType;

UCHAR MaxPacketType;

UCHAR MaxStateChange;

UCHAR MaxManipulate;

UCHAR Simulation;

USHORT Unused[1];

ULONG64 KernelBase;

ULONG64 PsLoadedModuleList;

ULONG64 DebuggerDataList;

} DBGKD_GET_VERSION, *PDBGKD_GET_VERSION;

// Описание структуры ядра nt!KdDebuggerDataHeader

typedef struct _DBGKD_DEBUG_DATA_HEADER {

LIST_ENTRY64 List;

ULONG OwnerTag;

ULONG Size;

} DBGKD_DEBUG_DATA_HEADER, *PDBGKD_DEBUG_DATA_HEADER;

// Описание структуры ядра nt!KdDebuggerDataBlock

typedef struct _KDDEBUGGER_DATA

DBGKD DEBUG DATA HEADER Header;

// Список переменных, общих для всех ОС платформы Windows NT

ULONG64 KernelBase;

ULONG64 BreakpointWithStatus;

ULONG64 SavedContext;

USHORT ThCallbackStack;

USHORT NextCallback;

USHORT FramePointer;

USHORT PaeEnabled:1;

ULONG64 KiCallUserMode;

ULONG64 KeUserCallbackDispatcher;

ULONG64 PsLoadedModuleList;

ULONG64 PsActiveProcessHead;

ULONG64 PspCidTable;

ULONG64 ExpSystemResourcesList;

ULONG64 ExpPagedPoolDescriptor;

ULONG64 ExpNumberOfPagedPools;

ULONG64 KeTimeIncrement;

ULONG64 KeBugCheckCallbackListHead;

ULONG64 KiBugcheckData;

ULONG64 IopErrorLogListHead;

ULONG64 ObpRootDirectoryObj ect;

ULONG64 ObpTypeObj ectType;

ULONG64 MmSystemCacheStart;

ULONG64 MmSystemCacheEnd;

ULONG64 MmSystemCacheWs;

ULONG64 MmPfnDatabase;

ULONG64 MmSystemPtesStart;

ULONG64 MmSystemPtesEnd;

ULONG64 MmSubsectionBase;

ULONG64 MmNumberOfPagingFiles ;

ULONG64 MmLowestPhysicalPage;

ULONG64 MmHighestPhysicalPage ;

ULONG64 MmNumberOfPhysicalPages ;

ULONG64 MmMaximumNonPagedPoolInBytes

ULONG64 MmNonPagedSystemStart;

ULONG64 MmNonPagedPoolStart ;

ULONG64 MmNonPagedPoolEnd;

ULONG64 MmPagedPoolStart;

ULONG64 MmPagedPoolEnd;

ULONG64 MmPagedPoolInformation;

ULONG64 MmPageSize;

ULONG64 MmSizeOfPagedPoolInBytes;

ULONG64 MmTotalCommitLimit;

ULONG64 MmTotalCommittedPages;

ULONG64 MmSharedCommit;

ULONG64 MmDriverCommit;

ULONG64 MmProcessCommit;

ULONG64 MmPagedPoolCommit;

ULONG64 MmExtendedCommit;

ULONG64 MmZeroedPageListHead;

ULONG64 MmFreePageListHead;

ULONG64 MmStandbyPageListHead;

ULONG64 MmModifiedPageListHead;

ULONG64 MmModifiedNoWritePageListHead

ULONG64 MmAvailablePages;

ULONG64 MmResidentAvailablePages;

ULONG64 PoolTrackTable;

ULONG64 NonPagedPoolDescriptor;

ULONG64 MmHighestUserAddress;

ULONG64 MmSystemRangeStart;

ULONG64 MmUserProbeAddress;

ULONG64 KdPrintCircularBuffer;

ULONG64 KdPrintCircularBufferEnd;

ULONG64 KdPrintWritePointer;

ULONG64 KdPrintRolloverCount;

ULONG64 MmLoadedUserImageList;

ULONG64 NtBuildLab;

ULONG64 KiNormalSystemCall;

ULONG64 Ki ProcessorBlock;

ULONG64 MmUnloadedDrivers;

ULONG64 MmLastUnloadedDriver;

ULONG64 MmTriageActionTaken;

ULONG64 MmSpecialPoolTag;

ULONG64 KernelVerifier;

ULONG64 MmVerifierData;

ULONG64 MmAllocatedNonPagedPool;

ULONG64 MmP e a kC ommi tment;

ULONG64 MmTotalCommitLimitMaximum;

ULONG64 CmNtCSDVersion;

ULONG64 MmPhysicalMemoryBlock;

ULONG64 MmSessionBase;

ULONG64 MmSessionSize;

ULONG64 MmSystemParentTablePage;

// Список переменных, добавленных в ОС Windows Server 2003

ULONG64 MmVirtualTranslationBase;

USHORT OffsetKThreadNextProcessor;

USHORT OffsetKThreadTeb;

USHORT OffsetKThreadKernelStack;

USHORT OffsetKThreadInitialStack;

USHORT OffsetKThreadApcProcess;

USHORT OffsetKThreadState;

USHORT OffsetKThreadBStore;

USHORT OffsetKThreadBStoreLimit;

USHORT SizeEProcess;

USHORT OffsetEprocessPeb-

USHORT Of f setEprocessParentCID;

USHORT OffsetEprocessDirectoryTableBase

USHORT SizePrcb;

USHORT OffsetPrcbDpcRoutine;

USHORT OffsetPrcbCurrentThread;

USHORT OffsetPrcbMhz;

USHORT OffsetPrcbCpuType;

USHORT OffsetPrcbVendorString;

USHORT OffsetPrcbProcStateContext;

USHORT OffsetPrcbNumber;

USHORT SizeEThread;

ULONG64 KdPrintCircularBufferPtr;

ULONG64 KdPrintBufferSize;

ULONG64 KeLoaderBlock;

USHORT SizePcr;

USHORT OffsetPcrSelfPcr;

USHORT OffsetPcrCurrentPrcb;

USHORT OffsetPcrContainedPrcb;

USHORT OffsetPcrInitialBStore;

USHORT OffsetPcrBStoreLimit;

USHORT OffsetPcrInitialStack;

USHORT OffsetPcrStackLimit;

USHORT OffsetPrcbPcrPage;

USHORT OffsetPrcbProcStateSpecialReg;

USHORT GdtR0Code;

USHORT

USHORT

USHORT

USHORT

USHORT

USHORT

USHORT

USHORT

USHORT

ULONG64

ULONG64

GdtR0Data;

GdtR0Pcr;

GdtR3Code;

GdtR3Data;

GdtR3Teb;

GdtLdt;

GdtTss;

Gdt64R3CmCode; Gdt64R3CmTeb;

IopNumTriageDumpDataBlocks; IopTriageDumpDataBlocks;

// Список переменных, добавленных в ОС Windows Vista

// Список переменных, добавленных в ОС Windows 7

} KDDEBUGGER_DATA, *PKDDEBUGGER_DATA;

Листинг 1. Описания необходимых внутренних структур ядра

Необходимо отметить тот факт, что поля, содержащие адреса глобальных переменных, выровнены на 64 бита. Это говорит о том, что данные структуры ядра можно использовать как в 64-разрядных (х64) версиях ОС, так и в 32-разрядных (х86) версиях ОС (просто отбрасывая 32 верхних разряда адреса).

Описав необходимые структуры ядра, можно приступать к реализации алгоритма поиска. Напишем функцию, получающую адреса необходимых глобальных переменных ядра, обращаясь к полям описанных структур ядра. Далее приведен листинг 2 с примером реализации подобной функции.

ULONG64 ULONG64 ULONG64

VfCrashDataBlock; MmBadPagesDetected;

MmZeroedPageSingleBitErrorsDetect

ed;

ULONG64 USHORT

EtwpDebuggerData; OffsetPrcbContext;

VOID KeGetGlobalKernelVariables() {

KPCR *kpcr;

DBGKD_GET_VERSION *kdvb;

D BGKD_D E BUG_DATA_HEAD E R *kddbgh;

KDDEBUGGER_DATA *kddbgdb;

// Получаем адрес структуры KPCR текущего процессора

_asm

{

mov eax, dword ptr fs:0x1c;

mov kpcr, eax; };

NT_KPCR = kpcr;

// Получаем адрес структуры DBGKD_GET_VERSION

kdvb = kpcr->KdVersionBlock;

if (kdvb) {

NT_KD_VERSION_BLOCK = kdvb;

// Получаем адрес структуры DBGKD_DEBUG_DATA_HEADER

kddbgh = kdvb->DebuggerDataList;

// Получаем адрес структуры KD_DEBUGGER_DATA

kddbgdb = kddbgh->List.Flink;

if (kddbgdb) {

// Получаем адреса необходимых переменных ядра

NT_KD_DEBUGGER_DATA_BLOCK = kddbgdb; NT_KERNEL_BASE = kddbgdb->KernelBase; NT_PS P_CID_TABLE = kddbgdb->PspCidTable; NT_PS_ACTIVE_PROCESS_HEAD = kddbgdb->PsActiveProcessHead; NT_PS_LOADED_MODULE_LIST= kddbgdb->PsLoadedModuleList; // ...Список других необходимых переменных

};

};

};

Листинг 2. Функция поиска адресов глобальных переменных ядра После выполнения такой функции в объявленных переменных вида NT_XXX будут содержаться адреса необходимых глобальных переменных ядра ОС.

Однако у приведенного выше варианта функции есть один небольшой недостаток: на многопроцессорных системах результат выполнения такой функции может быть некорректным, т. е. полученные адреса могут оказаться нулевыми, что может привести к краху системы при попытке обращения к ним. Это связано с тем, что действительный адрес структуры nt!KdVersionBlock находится только в той структуре KPCR, что описывает первый процессор в системе (под номером 0). Для решения данной проблемы требуется выполнение кода функции поиска именно на первом процессоре в системе. Это можно осуществить несколькими способами, например, используя механизм отложенного вызова процедур (Deferred Procedure Call - DPC). Данный механизм позволяет запускать выполнение кода на конкретном процессоре с конкретными параметрами, добавляя указанную процедуру в очередь на выполнение указанному процессору.

В данной работе будет использован чуть более простой способ, а точнее установка битовой маски Affinity для имеющихся в системе процессоров. Эта битовая маска представляет собой последовательность бит, размерностью совпадающую с разрядностью ОС. Каждый бит определяет соответствующий номер процессора. С помощью данной битовой маски устанавливается набор процессоров, которые будут задействованы для выполнения кода заданного потока. Воспользуемся этим способом для того, чтобы код функции поиска адресов глобальных переменных ядра выполнялся только на первом процессоре, структура KPCR которого содержит действительный адрес в поле KdVersionBlock. На листинге 3 представлен соответствующий код.

// Установка битовой маски

KeSetSystemAffinityThread(l);

// Вызов функции поиска глобальных переменных ядра

KeGetGlobalKernelVariables();

// Восстановление оригинальной битовой маски

KeRevertToUserAffinityThread();

Листинг 3. Гарантированное выполнение функции поиска на первом процессоре

Разработанный модуль ядра для динамического поиска адресов глобальных переменных ядра был протестирован на следующих 32-разрядных версиях ОС семейства Windows NT:

- Microsoft Windows XP (NT 5.1);

- Microsoft Windows Server 2003 (NT 5.2);

- Microsoft Windows Vista (NT 6.0);

- Microsoft Windows 7 (NT 6.1).

Для каждой из представленных ОС были проведены серии тестов, среди которых:

- запуск в ОС с различным набором установленных пакетов обновлений;

- запуск в ОС, установленных на машинах с различным аппаратным обеспечением (в том числе на одноядерных и многоядерных процессорах);

- запуск в ОС, установленных на виртуальных машинах (в том числе на одноядерных и многоядерных процессорах).

Разработанный модуль ядра может быть использован для динамического поиска адресов глобальных переменных ядра современных ОС семейства Windows NT как на однопроцессорных (UniProcessor - UP), так и на многопроцессорных (Multi-Processor -MP) системах 32-разрядной архитектуры, например на серверах и рабочих станциях распределенных информационных систем. Представленный исходный код требует минимальной корректировки для возможности использования его в системах 64-разрядной архитектуры. Данный модуль может быть в дальнейшем использован различными программными средствами защиты, например антивирусными средствами или системами класса HIPS, для обнаружения и активного противодействия вредоносным программам.

Заключение

В ходе данной работы были решены следующие поставленные задачи:

- представлено теоретическое обоснование возможности создания нового способа нахождения глобальных переменных ядра;

- представлено экспериментальное обоснование возможности практического использования разработанного способа.

На основании полученных результатов были сделаны следующие выводы:

- все современные ОС семейства Windows NT содержат внутренние структуры, предназначенные для использования отладчиком ядра;

- эти структуры предоставляют актуальную и корректную информацию о текущем состоянии системы в любой момент времени;

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

Разработанный в данной работе способ динамического поиска глобальных переменных ядра позволяет упростить и ускорить разработку средств защиты информации, а также решить ряд проблем, возникающих при разработке современных программных средств защиты для распределенных информационных систем, построенных на базе ОС семейства Windows NT.

Примечания

См.: Грушо А.А., Тимонина Е.Е. Распределенные атаки на распределенные системы // Информационный бюллетень JET INFO. М., 2006.

См.: Хоглунд Г., Батлер Дж. Руткиты: внедрение в ядро Windows. СПб.: Питер, 2007.

См.: Barbosa E. Finding some non-exported kernel variables in Windows XP [Electronic data]. [2004]. URL: http://www.reverse-engineering.info/System Information/GetVarXP.pdf

См.: Руссинович М, Соломон Д. Внутреннее устройство Microsoft Windows: Windows Server 2003, Windows XP и Windows 2000: Пер. с англ. 4-е изд. СПб.: Питер, 2008.

См.: Russinovich M, Solomon D, Ionescu A. Windows Internals: Covering Windows Server 2008 and Windows Vista, 5-th edition. Microsoft Press, 2009.

2

3

4

5