Источник: Косых П. Диспетчер объектов Windows NT изнутри // http://www.codeguru.com. - 2001.
http://www.codeguru.com/Cpp/W-P/system/devicedriverdevelopment/article.php/c8035/
ДИСПЕТЧЕР ОБЪЕКТОВ WINDOWS NT ИЗНУТРИ
=====================================
Заточенное само в себе
Ласкаемое невинностью, убежище для твоего Я
Рождено однноким под частоколом саркастических небес
Одна любовь, одна жизнь, одна печаль...
(с) by Anathema
ОГЛАВЛЕНИЕ
==========
ДИСПЕТЧЕР ОБЪЕКТОВ WINDOWS NT ИЗНУТРИ
00."Объектная" Windows NT
01.Диспетчер объектов Windows NT
02.Типы объектов
03.Объекты Directory и SymbolicLink
04.Создание и структура объекта
05.Структура объекта-типа
06.Дальнейшая жизнь объекта - простейший случай
07.База данных хэндлов
08.Безопасность объектов
09.Если у объекта есть имя
0a.Смерть объекта
0b.Другие функции диспетчера
0c.Заключение
ПРИЛОЖЕНИЕ
13.Получение информации об объекте из user режима
14.Некоторые системные сервисы, связанные с диспетчером объектов
00."Объектная" Windows NT
=========================
Если вы знакомы с архитектурой Windows NT, то конечно знаете, что
ресурсы в этой ОС представлены в виде объектов. Это дает много
приемуществ, самое главное из которых - централизированное управление
ресурсами. Практически все подсистемы Windows NT, так или иначе, работают
с ресурсами, т.е. обращаются к диспетчеру объектов. Диспетчер объектов -
подсистема Windows NT, которая занимается поддержкой объектов. Не смотря
на то, что работа с объектами пронизывает всю ОС, истинная "объектность"
NT скрыта для прикладного программиста за подсистемой Win32, и даже
частично - за интерфейсом системных сервисов.
Обычно, когда говорят об архитектуре NT, говорят о менеджерах или
диспетчерах. (Диспетчер объектов, менеджер памяти. и т.д.) Все они - части
ядра Windows NT (ntoskrnl.exe). Ядро NT в основном написано на C, и каждый
диспетчер или менеджер представляет собой группу функций соответствующего
модуля. Нет какого-либо определенного интерфейса. Менеджеры
взаимодействуют между собой (внутри ядра) посредством вызовов функций.
Часть функций экспортируется из ядра, и доступны для использования из
режима ядра.
Диспетчер объектов представлен группой функций ObXXXX. На самом деле,
не все из этих функций экспортируются из ядра. Вообще, диспетчер объектов
в основном используется самим ядром. Например, работа многих системных
сервисов (NtCreateProcess, NtCreateEvent...) связана с тем или иным типом
объекта. Но сервис скрывает от нас работу с диспетчером объекта. Даже
разработчику драйвера вряд ли понадобится (а если и понадобится, то,
скорее всего, не удастся) интенсивно работать непосредственно с
диспетчером объектов, тем не менее важно представлять себе как реально
работает ядро, тем более, что диспетчер объектов тесно связан с
диспетчером безопасности. Понимание работы диспетчера объектов раскрывает
многие, первоначально не очевидные, потенциальные возможности NT...
01.Диспетчер объектов Windows NT
================================
Диспетчер объектов представлен множеством функции ObXXX в ядре NT.
Некоторые функций не экспортируются и используются другими подсистемами
только внутри ядра. Доступны для использования вне ядра следующие, в
основном недокументированные, функции.
ObAssignSecurity, ObCheckCreateObjectAccess , ObCheckObjectAccess,
ObCreateObject, ObDereferenceObject, ObFindHandleForObject,
ObGetObjectPointerCount, ObGetObjectSecurity, ObInsertObject,
ObMakeTemporaryObject, ObOpenObjectByName, ObOpenObjectByPointer,
ObQueryNameString, ObQueryObjectAuditingByHandle,
ObReferenceObjectByHandle, ObReferenceObjectByName,
ObReferenceObjectByPointer, ObReleaseObjectSecurity,
ObSetSecurityDescriptorInfo, ObfDereferenceObject, ObfReferenceObject
Для описания диспетчера объектов сначала посмотрим, что такое объект.
02.Типы объектов
================
Диспетчер объектов сам опирается в своей работе на некоторые объекты.
Один из них это объект-тип. Объект-тип нужен для того, что бы вынести
часть общих свойств объектов. Объекты-типы создаются с помощью функций
ObCreateObjectType(...) во время инициализации подсистем ядра. (Т.е. При
инициализации системы.) Сама функция ObCreateObjectType(...) нас мало
интересует, так как она не экспортируется ядром. Однако, информация в
объекте - типе важна. Но об этом немного позже.
Грубо говоря, любой объект можно разделить на две части: одна из
которых содержит информацию необходимую самому диспетчеру (назовем эту
часть заголовком). Вторая заполняется и определяется нуждами подсистемы,
создавшей объект. (Это - тело объекта). Не смотря на то, что диспетчер
объектов оперирует заголовками и не интересуется, собственно, содержимым
объекта, имеется несколько объектов, которые используются самим
диспетчером объектов. Объект-тип, как и следует из названия, определяет
тип объекта. У каждого объекта есть ссылка на его объект-тип, к телу
которого менеджер объектов обращается очень часто. Одно из полей
объекта-типа - это имя типа. В NT существуют следующие типы
(объекты-типы c соотв. именами).
Type, Directory, SymbolicLink, Event, EventPair, Mutant, Semaphore,
Windows, Desktop, Timer, File, IoCompletion, Adapter, Controller, Device,
Driver, Key, Port, Section, Process, Thread, Token, Profile
Не стоит пугаться объекта-типа с типом 'Type'. Ведь объект-тип это, в
общем, такой же объект для менеджера, как и все остальные, и , как и все
объекты, он имеет свой тип 'Type'. (Свой типовой объект 'Type'.) Если
немного подумать это не кажется слишком странным. В дальнейшем, под словом
'тип' будет в основном пониматься объект-тип с соответствующим именем.
03.Объекты Directory и SymbolicLink
===================================
Все перечисленные типы создаются различными подсистемами. Нас сейчас
интересуют типы Directory и SymbolicLink, потому что объекты этих типов
использует в своей работе сам менеджер объектов. (Эти типы он создает при
своей инициализации).
Объекты NT могут иметь имена и могут организовываться в древовидные
структуры. Объект 'Directory' служит как раз для организации такой
структуры. Его назначение очень простое - хранить в себе ссылки на другие
объекты, например, другие объекты 'Directory'. Структура тела объекта
'Directory' очень проста.
typedef _DIR_ITEM{
PDIR_ITEM Next;
PVOID Object;
}DIR_ITEM,*PDIR_ITEM;
typedef struct _DIRECTORY{
PDIR_ITEM HashEntries[37];
PDIR_ITEM LastHashAccess; //94h
DWORD LastHashResult; //98h
}DIRECTORY,*PDIRECTORY;
Директории предназначены для хранения именованных объектов. Директория
представляет собой 37-входовый хэш. Элементы списков хранят кроме
указателя на следующий элемент, указатель на тело объекта, принадлежащего
этой директории. Сама хэш функция выглядит так:
DWORD Hash(const char* str);
{
char *ptr=str;
int str_len=strlen(str);
char Sym;
int hash=0;
while(str_len--)
{
Sym=*ptr++;
ToUpperCase(&Sym);
Sym-=' ';
hash=hash*3+(hash>>1)+Sym;// умножение знаковое
}
return hash%37;
}
Конечно, приведенный код отражает лишь логику, а не действительную
реализацию. Этот пример написан на основе анализа внутренней функции
ObpLookupDirectoryEntry(...). Два последних поля отражают последний доступ
к хэшу. LastHashAccess это указатель на последний вход, к которому был
доступ. LastHashResult содержит единицу, если доступ при поиске завершился
удачно. NT пытается оптимизировать поиск в директории, и если в результате
поиска в одном из входов найденый объект находится не в начале списка, то
этот элемент списка перемещается в начало (предполагается, что следующий
доступ наиболее вероятно будет также происходить к этому объекту).
Напомню, что директория это объект и, как любой объект, она может
иметь имя. Если учесть еще и то, что объекты имеют ссылку на директорию,
которой (если) они принадлежат, становится понятной возможность построения
иерархических деревьев.
В Windows NT содержит корневой каталог ObpRootDirectoryObject, от
которого берет начало дерево именованных объектов Windows NT. Также
имеется директория ObpTypeDirectoryObject, которая содержит все объекты
типы (это дерево используется что бы предотвратить объекты-типы с
одинаковыми именами.) Указатели на объекты-типы содержатся также в таблице
ядра ObpObjectTypes. Плюс для каждого созданного типа подсистема хранит
отдельный указатель.
Объекты типа SymbolicLink предназначены для хранения строки. Они
используются как ссылки при поиске по дереву объектов менеджером. Например,
перманентный (постоянный) объект \??\C: (это означает, что в
ObpRootDirectoryObject имеется ссылка на объект директорию '??', которая в
свою очередь содержит объект 'C:') является объектом SymbolicLink и скорее
всего содержит строку '\Device\Harddisk0\Partition1'. Формат тела такого
объекта:
typedef struct _SYMBOLIC_LINK{
LARGE_INTEGER CreationTime; // время создания от 1601 года
UNICODE_STRING Link;
}SYMBOLIC_LINK,*PSYMBOLIC_LINK;
Теперь имеется общее представление о типах и объектах, используемых
менеджером, и можно заняться рассмотрением более конкретной информации.
04.Создание и структура объекта
===============================
Объект создается с помошью функции ObCreateObject. Эта функция
экспортируется из ntoskrnl.exe и вот ее прототип:
NTSTATUS NTOSKRNL
ObCreateObject
(
KPROCESSOR_MODE bMode, // kernel / user
POBJECT_TYPE Type, // Типовой объект
POBJECT_ATTRIBUTES Attributes, // Аттрибуты
BOOLEAN bObjectMode, // Тип объекта kernel/user
DWORD Reserved, // не используется функцией
DWORD BodySize, // размер тела объекта
DWORD PagedPoolQuota OPTIONAL, // если 0
DWORD NonPagedPoolQuota OPTIONAL,// то наследуется
PVOID* pObjectBody // возвращаемый указатель на тело.
);
Вряд ли предполагалось, что эта функция будет использоваться извне.
Например, указатель Type на конкретный тип нельзя получить извне, не имея
созданным хотя бы одного объекта такого же типа - т.е. нельзя получить
легально. Параметр Reserved просто игнорируется. bObjectMode определяет,
будет ли доступен объект из пользовательского режима. PagedPollQuota и
NonPagedPollQuota - квоты связанные с этим объектом на откачиваемый и
неоткачиваемый пулы. Квоты списываются с процесса всякий раз, когда он
открывает описатель на него. У процесса есть лимиты на использование квот,
которые он не может превысить. Структура POBJECT_ATTRIBUTES
документирована (описана в ntdef.h), но я привожу ее, так как часто буду
ссылаться на ее поля.
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES;
typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;
Важно сейчас обратить внимание на поля RootDirectory и ObjectName. Что
такое RootDirectory, теперь, думаю, уже можно догадаться - директория в
которой будет представлен объект (если это надо). ObjectName - название
говорит само за себя. Кстати, можно не указывать RootDirectory, но описать
полный путь к объекту в ObjectName, тогда объект будет представлен (я
специально избегаю слова создан) в дереве с корнем ObpRootDirectoryObject.
Про безопасность речь пойдет отдельно, поэтому поля Security... пока
оставим. Осталось поле Attributes. Значения бит описаны в ntdef.h. В
дальнейшем будут ссылки на эти атрибуты.
#define OBJ_INHERIT 0x00000002L
#define OBJ_PERMANENT 0x00000010L
#define OBJ_EXCLUSIVE 0x00000020L
#define OBJ_CASE_INSENSITIVE 0x00000040L
#define OBJ_OPENIF 0x00000080L
#define OBJ_OPENLINK 0x00000100L
В результате работы, функция возвращает указатель на тело объекта.
Пришло время описать структуру объекта.
+---------------+<------------------------------> -??h
| |
| ........ | --структура переменной длины (до 30h)
| |
+---------------+<--голова объекта--------------> 00h
| |
| Header | --стандартный "заголовок" 18h байт
| |
+---------------+<--тело объекта----------------> 18h
| |
| |
| Body | - тело объекта желаемого размера
| |
+---------------+<------------------------------> 18h+BodySize
Диспетчер обычно оперирует указателями на заголовок объекта. Нам он
возвращает указатель на тело. Для экономии памяти имеется еще одна область
над заголовком, которая имеет переменную длину и зависит от параметров и
типа создаваемого объекта. Эта "шапка" может содержать следующие
структуры.
typedef struct _POLLED_QUOTA // добавляется если квоты не равны default
{ // содержит расход квот
DWORD PagedPoolQuota;
DWORD NonPagedPoolQuota;
DWORD QuotaInformationSize; // суммируется с PagedPollQuota
PPROCESS_OBJECT pProcess; //Процесс владелец
}POLLED_QUOTA;
typedef struct _HANDLE_DB // информация об открытых описателях
{
union {
PPROCESS_OBJECT pProcess;// если только один процесс открыл
// указатель
PVOID HandlesDBInfo; //-------------+--------
}HanInfo; // |dd Num;2
// +-----------
// |dd pProcess1
// |dd Counter2
// +----------
// |dd pProcess2
// |dd Counter2
// +-----------
// |....
DWORD Counter; // Всего хэндлов.
}HANDLE_DB;
typedef struct _OBJECT_NAME
{
PDIRECTORY Directory; // директория, которой принадлежит объект
UNICODE_STRING ObjectName; Имя объекта
DWORD Reserved; //выравнивание
}OBJECT_NAME;
typedef struct _CREATOR_INFO
{
LIST_ENTRY Creator; // замкнутый список (включая тип)
DWORD UniqueProcessId;// ID процесса "отца" объекта
DWORD Reserved; // выравнивание
}CREATOR_INFO;
Те или иные структуры могут либо присутствовать, либо нет, но порядок
следования структур не нарушается. В комментариях написано, для чего
используются эти структуры. Сразу за последней из них (если имеется хотя
бы одна) начинается стандартный заголовок объекта.
typedef struct _OBJECT_HEADER
{
DWORD RefCounter; // число ссылок на объект 00
DWORD HandleCounter; // Число хэндлов 04
POBJECT_TYPE ObjectType; // объект-тип 08
DWORD SubHeaderInfo; // описано ниже 0c
UNION // 10
{
POBJECT_INFO ObjectInfo;
PQUOTA_BLOCK pQuotaBlock;
} a;
PSECURITY_DESCRIPTOR SecurityDescriptor; // 14 Optional
} OBJECT_HEADER;
Пусть пока не смущает объединение, о нем позже. Остальные поля вполне
очевидны. Диспетчер объектов ведет учет открытых описателей и ссылок на
объект, чтобы знать когда удалить имя объекта и тело объекта (если он не
перманентный, т.е. не задан бит OBJ_PERMANENT). Имеется ссылка на тип
объекта и информация о безопасности. Что бы знать, какие из структур
присутствуют над заголовком присутствует поле SubHeaderInfo.
Значения бит SubHeaderInfo следующие:
3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+---------------+-------------+---------------+-----------------+
|U|H|S|P|E|I|M|Q|Quota offset |HandleDB offset| Name Offset |
++-+-+-+-+-+-+-++-----+-------+------+--------+-------+---------+
| | | | | | | | | | |
| | | | | | | | | | Положительное смещение
| | | | | | | | | | к OBJECT_NAME от начала
| | | | | | | | | | заголовка (вверх).
| | | | | | | | | |
| | | | | | | | | Положительное смещение
| | | | | | | | | к HANDLE_DB от начала
| | | | | | | | | заголовка(вверх).
| | | | | | | | |
| | | | | | | | Положительное смещение
| | | | | | | | к POLLED_QUOTA от начала
| | | | | | | | заголовка(вверх).
| | | | | | | +- QuotaBlock/ObjectInfo (Quota charged)
| | | | | | +-- Режим Объекта User/Kernel
| | | | | +---- присутствует ли CREATOR_INFO
| | | | +------ соответствует биту OBJ_EXCLUSIVE
| | | +-------- соответствует биту OBJ_PERMANENT.
| | | Для создания такого объекта необходимо
| | | соответсвующее право.
| | +---------- присутствует SecurityDescriptor
| +------------ HandleInfo не инициализирована.
+-------------- Unused/Reserved
*В SubHeaderInfo нет байта-смещения к CREATOR_INFO, но так как эта структура
(если она есть) всегда последняя, указатель на нее можно получить, если
вычесть из указателя на заголовок значение sizeof(CREATOR_INFO).
Когда я впервые начал дизассемблировать функции диспетчера объектов и
увидел название ObCreateObject(...), я решил что эта функция делает все
необходимое, что бы объект начал свою "жизнь". Однако общий принцип
оттягивать_все_до_конца (хороший принцип) работает и здесь. На самом деле,
эта функция заполняет минимум полей структур, которые описаны выше. Скорее
функция просто размещает объект в памяти, при этом "попутно" заполняя
некоторые поля.
Подробнее. Биты SubHeaderInfo : Q=1 всегда, S=0 всегда ,H=1 если
присутствует HANDLE_DB. Directory в OBJECT_NAME = NULL. LIST_ENTRY в
CREATOR_INFO сам на себя. pProcess в POLLED_QUOTA = NULL. HANDLE_DB
зануляется. HandleCounter=0; RefCounter=1; Остальные поля заполняются в
соответствии с входной информацией и информации из объекта-типа. Кроме
того, выделяется и заполняется структура OBJECT_INFO, соответствующий
указатель помещается в UNION {..}a заголовка. (Да, конечно вся память
выделяется из NonPaged пула.)
typedef struct _OBJECT_INFO{
DWORD Attributes; //00h OBJECT_ATTRIBUTES.Attributes
HANDLE RootDirectory; //04h
DWORD Reserved; //08h - Unknown or Res.
KPROCESSOR_MODE bMode; //0ch
BYTE Reserved[3]; //0dh - Alignment
DWORD PagedPoolQuota; //10h
DWORD NonPagedPoolQuota; //14h
DWORD QotaInformationSize;//18h - размер SID группы
//+ размер DACL (округленные)
PSECURITY_DESCRIPTOR SelfRelSecDescriptor;
//1ch - указатель на Self Relativ.
//дескриптор безопасности Из Non Paed Pool
PSECURITY_QUALITY_OF_SERVICE pSecQual; //20h
SECURITY_QUALITY_OF_SERVICE SecQuality; //24h
//30h
} OBJECT_INFO,*POBJECT_INFO;
Кстати говоря, для структур OBJECT_INFO поддерживается Lookaside list
(см. DDK) ObpCreateInfoLookasideList. Как видим, информация в OBJECT_INFO
дублирует входную информацию (поэтому я и назвал ее так) и "припасена" для
дальнейшей эволюции объекта. На самом деле - поле Reserved иногда
используется (в качестве параметров в методы объектов) но мне не удалось
увидеть значение этого поля не равное 0. QuotaInformationSize -
используется при снятии квоты с процесса наряду с квотами на Paged и
NonPaged пулы. Ну вот пришло время и для объекта-типа.
05.Структура объекта-типа
=========================
Я не буду ходить вокруг да около как обычно, а просто опишу структуру
тела объекта типа.
typedef struct _OBJECT_TYPE
{
ERESOURCE TypeAsResource; //0x0 может выступать в роли ресурса
//34h
PLIST_ENTRY FirstCreatorInfo;//38h Я заметил поддержку такой структуры
PLIST_ENTRY LastCreatorInfo; //3ch только для объекта тип
UNICODE_STRING TypeName; //40h Имя типа
DWORD Unknown2[2]; //48h
DWORD RefCount; //50h счетчик объектов этого типа
DWORD HanCount; //54h счетчик хандлов этого типа
DWORD PeakRef; //58h пиковое число объектов
DWORD PeakHandles; //5ch пиковое число хандлов
DWORD Unknown3; //60h
DWORD AllowedAttributeMask;//64h возможные аттрибутов 0 - разрешено все
GENERIC_MAPPING GenericMapping;//68 отображение родовых прав на специальные
DWORD AllowedAccessMask; //78h (ACCESS_SYSTEM_SECURITY установлено всегда)
BOOLEAN bInNameSpace; //7ch Объекты этого типа в директории объектов
// возможно, что ошибаюсь, но похоже.
BOOLEAN bHandleDB; //7dh поддерживать ли информацию о хандлах
в объекте (HANDLE_DB)
BOOLEAN bCreatorInfo; //7eh ----//---- CreatorInfo + список по 38h
BOOLEAN Unknown5; //7fh
DWORD Unknown6; //80h если !=0 то создавать в NpSuccess
DWORD PagedPoollQuota; //84h default
DWORD NonPagedPollQuota; //88h квоты
PVOID DumpProcedure; //8ch прототип неизвестен (?)
PVOID OpenProcedure; //90h прототип известен
PVOID CloseProcedure; //94h прототип известен
PVOID DeleteProcedure; //98h прототип известен
PVOID ParseProcedure; //9ch прототип известен
PVOID SecurityProcedure; //a0h прототип известен
// возможно 4 случая вызова :
//0-set sec_info, 1-query descriptor, 2-delete, 3-assign
PVOID QueryNameProcedure; //a4h прототип известен
PVOID Tag; //a8h судя по имеющейся высокоуровневой
// информации тут должен быть метод OkayToCloseProcedure;
// на практике, для всех объектов я обнаружил по этому адресу
// четырехсимвольный тэг типа, например Dire (Directory)
} OBJECT_TYPE,*POBJECT_TYPE;
Я не привожу прототипы известных методов потому, что они сейчас мало
полезны. Итак, тип содержит в себе имя типа, процедуры для работы с
объектами, а также общую информацию для объектов, принадлежащих этому
типу. Поля структуры прокомментированы, тем не менее опишу некоторые из
них подробнее.
Сразу бросается в глаза то, что по смещению 0 расположена структура
ERESOURCE (ее можно найти в NTDDK.H). Другими словами, этот объект может
выступать в роли ресурса. Так и происходит. Менеджер объектов захватывает
ресурс объект-тип с помошью функции ExAcquireResourceExclusiveLite (см.
DDK) при работе с ним. PagedPollQuota и NonPagedPollQuota задают параметры
по умолчанию для квот. Если мы вызываем функцию ObCreateObject с нулевыми
параметрами квот (или они совпадают с default квотами) и если не
установлено OBJ_EXCLUSIVE и если QuotaInformationSize меньше 0x800, то при
создании объекта не будет создана структура POLLED_QUOTA. Поля
AllowedAttributeMask и AloowedAccessMask задают маски возможных бит
атрибутов и желаемого доступа. Наконец, имеются указатели на методы,
которые вызываются в определенные моменты. Например, при добавлении хэндла
в базу данных хэндлов процесса, происходит вызов функции OpenProcedure. Это
не значит, что эти поля необходимы, как можно подумать (и как я думал,
читая книгу Хелен Касер о WIndows NT). Но они могут присутствовать, тем
самым расширяя функциональность какого-либо объекта. В дальнейшем я опишу
условия вызовов некоторых из методов.
06.Дальнейшая жизнь объекта - простейший случай
===============================================
Ну что же, ObCreateObject создаст объект, но оказывается толку от
такого объекта мало. Нет хандла на него. Нельзя открыть его по имени.
(Функция ObCreateObject не добавила объект в дерево объектов.) Давайте
вернемся назад и посмотрим на экспортируемые функции диспетчера объектов.
Интригуют следующие три функции: ObOpenObjectByName,
ObOpenObjectByPointer, ObInsertObject. Первая функция отпадает. Но вот
вторая и третья!?... ObOpenObjectByPointer - по идее то, что нужно. Зато
название ObInsertObject вообще мало о чем говорит. Начнем с нее.
NTSTATUS NTOSKRNL
ObInsertObject(
PVOID pObject, //Тело
PACCESS_STATE pAccessState OPTIONAL,
ACCESS_MASK Access,
DWORD RefCounterDelta OPTIONAL, //0- default (т.е. 1)
PVOID OUT *ObjectExist OPTIONAL, //Если уже существует
PHANDLE OUT Handle //хэндл
);
Логика функции вполне очевидна. Основную информацию она получает из
заполненной ранее структуры OBJECT_INFO, указатель на которую она сразу же
получает из заголовка объекта (указатель на заголовок можно получить
просто отняв 18h от указателя на тело). Функция работает по двум
вариантам. Первый вариант: у объекта нет имени и не установлен флаг
bInNameSpace в типе. Второй: установлен флаг или есть имя. Рассмотрим обе
ветви.
Если объект без имени, то происходит вызов ObpCreateUnnamedHandle(..).
Дальше, я буду ссылаться на внутренние функции не описывая их прототипы,
хотя они и известны. Вряд ли эта информация представляет практический
интерес. Эта функция сама опирается на множество внутренних функций,
которые в совокупности проделывают такую работу.
С текущего процесса списываются квоты. При этом заполняется поле
a.pQuotaBlock и очищается бит Q в SubHeaderInfo. Теперь объект "заряжен".
квотой. a.pQuotaBloсk указывает на связанную с каждым процессом структуру
QUOTA_BLOCK. Хотя мы сейчас не рассматриваем структуру объекта-процесс,
думаю, стоит привести эту структуру.
typedef _QUOTA_BLOCK{
DWORD KSPIN_LOCK QuotaLock;
DWORD RefCounter; // для скольких процессов этот блок
DWORD PeakNonPagedPoolUsage;
DWORD PeakPagedPoolUsage;
DWORD NonPagedpoolUsage;
DWORD PagedPoolUsage;
DWORD NonPagedPoolLimit;
DWORD PagedPoolLimit;
DWORD PeakPagefileUsage;
DWORD PagefileUsage;
DWORD PageFileLimit;
}QUOTA_BLOCK,*PQUOTA_BLOCK;
Я думаю имена говорят сами за себя. Ладно, идем дальше. Увеличивается
число хэндлов и обновляется информация в HANDLE_DB, если она есть.
HANDLE_DB представляет собой структуру, хранящую ссылки на процессы,
открывшие объект. Для каждого из них поддерживается свой счетчик, наряду с
глобальным счетчиком. Объединение HanInfo в этой структуре в очередной раз
показывает склонность к принципу "ленивости". Если объект открыт только из
одного процесса, то объединение представляет собой указатель на этот
процесс. И все - никакой таблицы. Если же таблица присутствует - то она
поддерживается в актуальном состоянии и при необходимости увеличивается в
размере на 32 байта или 4 элемента. (ObpIncrementHandleDataBase).
Неименованный объект может быть эксклюзивным (бит OBJ_EXCLUSIVE в
атрибутах). При этом происходит контроль открытия объекта только из одного
процесса на основе соответствующего поля в POLLED_QUOTА. Если помните, эта
структура всегда присутствует в эксклюзивных объектах. Таким образом
процесс владеет объектом эксклюзивно - при этом отпадает проблема контроля
доступа. Логично, что хэндлы эксклюзивного объекта не могут наследоваться
(OBJ_INHERIT), что и проверяется диспетчером. Если необходима информация
CREATOR_INFO, то поддерживается замкнутый список CREATOR_INFO на которые
указывают соответствующие поля в объекте-типе. Сам объект-тип также входит
в эту цепочку. На самом деле, я обнаружил, что бит bCreatorInfo установлен
только в типовом объекте 'Type'. Это означает, что поддерживается список
только для объектов-типов. Для объектов, которым объекты-типы являются
типовыми, такие списки не поддерживаются. Вся, только что описанная
логика, в основном содержится в функции ObpIncrementUnnamedHandleCount(..)
Во время этой операции также происходит вызов OpenProcedure (если она
есть).
Когда описанные действия произведены, увеличивается счетчик RefCounter
в заголовке объекта на величину RefCounterDelta+1. Наконец, с помошью
ExCreateHandle создается хэндл. После успешного создания хэндла удаляется
соответствующая структура OBJECT_INFO, которая сослужила свою службу и уже
не нужна...
07.База данных хэндлов
======================
У процесса (который тоже объект, но об этом в другой раз) имется база
данных хэндлов связанных с объектом. Зная хэндл объекта очень легко
получать доступ к объекту, а также появляется возможность наследования и
дублирования. Что бы представлять себе реальное положение вещей рассмотрим
базу данных хэндлов (описателей) подробнее.
Не останавливаясь сейчас на формате объектов 'thread' и 'process',
покажу, как можно получить указатель на базу данных описателей
(естественно, в режиме ядра).
mov eax,large fs:124h ; Got current thread body pointer
mov eax,[eax+44h] ; Got current process body pointer
mov ecx,[eax+104h] ; Got handle_db header pointer !
Базу данных логически можно разделить на две части - заголовок базы и
тело (собственно, сама база данных). Заголовок выглядит следующим образом.
typedef struct _HANDLE_DB_HEADER{
WORD Unknown0[2]; //0x0 - эти поля всегда были 0
DWORD Unknown1; //0x4 - -----..-----
SPIN_LOCK SpinLock; //0x8
PVOID Head; //0xc
PVOID Tail; //0x10
DWORD HandleCount; //0x14
PPROCESS pProcess; //0x18
DWORD ProcessUniqueId;//0x1c
WORD GrowDelta; //0x20
LIST_ENTRY HandleTableList; //0x24 Система поддерживает
//список баз данных хэндлов. Вход в список - HandleTableListHead
KEVENT EventObj; //0x2c
KSEMAPHORE SemaphoreObj; //0x3c
}HANDLE_DB_HEADER,*PHANDLE_DB_HEADER;
Саму базу данных можно изобразить так:
+-------------+
+----------|+----------- |-+---------------------------- -------------+
+->NullEntry>+|Han0|Han1|..+>| LIST_ENTRY<|>LIST_ENTRY>|.. |>LIST_ENTRY<|-+
| +-----------+------------ +-------------------------- ---------------+ |
+--------------------------------------------------------------------------+
[Head] [ Handles ] [ Free spaces for handles ] [Tail]
Head в заголовке БД указывает на начало базы. Tail - на конец. При
инициализации базы данных - вся область памяти базы заполняется
циклическим списком (ничего кроме списка, т.е. каждый элемент представлен
LIST_ENTRY). Причем список построен последовательно слева-направо. Когда в
базу данных добавляется хэндл - удаляется очередной элемент списка сразу
после нулевого элемента списка. В итоге имеем систему с головным
указателем на список свободных мест и растущий массив хэндлов сразу после
него. Когда свободных мест нет (головной элемент списка указывает сам на
себя) происходит расширение базы данных на GrowDelta мест. Конечно, при
работе с базой данных хэндлов происходит соответствующее изменение
"заряда" квот. Все очень просто...
***************************************************************************
Внимание! Вся вышеприведенная информация актуальна в основном для ОС
Windows NT 4.0. В ОС Windows NT 2K структура базы данных дескрипторов
изменилась. Теперь таблица является трехуровневой.
Process Top Level
+-------+ pointers
| | Middle level
|-------| 0 pointers Subhandle
|Handle |---->+------+ 0 table
| Table | | |--->+------+ 0
|-------| |------| | |---->+------+
| | |------| | |
| | |------|
| | | |
| | 255+------+ | |
+-------+ 255+------+ | |
255+------+
Теперь нет необходимости пересоздавать таблицу дескрипторов. Достаточно
создать еще один блок указателей и добавить подтаблицу дескрипторов
(subhandle).
При этом структура OBJECT_HANDLE (см. ниже) не изменилась. Только 31 бит
в указателе используется как индикатор занятости или замка, и в обычном
состоянии сброшен. (Так как все указатели принадлежат пространству ядра,
31 бит не несет информационной нагрузки для указателя - все адреса
принадлежащие пространству ядра больше 0x80000000.) Приведу псевдокод
некоторых внутренних функций, иллюстрирующий работу с базой данных
дескрипторов в NT5.0.
ExMapHandleToPointer(Table,Handle)
{
TableEntry=ExpLookupHandleTableEntry(Table,Handle);
if(!TableEntry)return 0;
return ExLockHandleTableEntry(Table,TableEntry);
}
ExpLookupHandleTableEntry(Table,Handle)
{
level1=(Handle>>18) & 0xff;
level2=(Handle>>10) & 0xff;
level3=(Handle>>2) & 0xff;
if(Handle&0xfc000000)return 0;
pPtr=Table->0x8;
pPtr=pPtr+level1*4;
if(pPtr)
{
pPtr=pPtr+level2*4;
if(pPtr)
{
return pPtr+level3*8;
}
}
return 0;
}
ExLockHandleTableEntry(Table,TableEntry)
// Table not used
{
ObjectPointer = TableEntry->0x00;
if (!ObjectPointer) return 0;
if (ObjectPointer > 0)
{
NewObjectPointer = ObjectPointer | 0x80000000;
//xmpxchg NewObjectPointer,ObjectPointer in ObjectPointer
}
else {
// wait logic
}
return 1;
}
**************************************************************************
Для полноты картины приведу прототипы основных внутренних функций,
работающих с БД хэндлов.
Основная функция для создания базы данных:
NTSTATUS ExCreateHandleTable(
PPROCESS Process,
WORD Size, //Optional
WORD GrownDelta //Optional
);
Эта функция вызывается используется при расширении базы данных.
NTSTATUS ExpAllocateHandleTableEntries(
PHANDLE_DB_HEADER HandleDB,
PLIST_ENTRY Head,
DWORD OldSize, // в элементах
DWORD NewSize
);
Более высокоуровневая функция:
NTSTATUS ObInitProcess(
PPROCESS pFatherProcess, //если!=0 - база данных дублир.
//Иначе - создается
//Поддерж. аудит
PPROCESS pProcess
)
Что же представляет из себя хэндл объекта как структура? А вот что:
typedef struct _OBJECT_HANDLE{
PVOID ObjectHeaderPointer,
ACCESS_MASK GrantedAccess
}OBJECT_HANDLE,*POBJECT_HANDLE;
Как видим - хэндл не что иное, как просто указатель на голову объекта,
плюс маска доступа. Правда, ObjectHeaderPointer хранит еще некоторую
информацию. Дело в том, что ареса на головы всех объектов всегда
выравнены на 8-ки байт. Таким образом, для доступа к объекту используются
только биты [31:3] ObjectHeaderPointer. Младшие 3 бита представляют собой
нечто, что называется HandleAttributes в структуре
OBJECT_HANDLE_INFORMATION. (см. функцию ObReferenceObjectByHandle в
DDK). Значения бит: 0x01 - protect from close, 0x02- inherit, 0x04 -
generate on close. Итак, хэндл позволяет эффективно адресовать объект.
Пользователю возвращается значение хэндла, которое равно номеру
структуры OBJECT_HANDLE в базе данных сдвинутое влево на два разряда. В
ntdef.h рядом с определением OBJ_HANDLE_TAGBITS по этому поводу
написано следующее:
//
// Low order two bits of a handle are ignored by the system and available
// for use by application code as tag bits. The remaining bits are opaque
// and used to store a serial number and table index.
//
Ну что же, все понятно.
Если вы еще не забыли, мы только что рассмотрели простейший случай
работы ObInsertObject, в случае неименованного объекта. Что происходит
если объект должен быть помещен в дерево имен? Если говорить просто - то
идет работа с деревом имен - в конце концов объект помещается в какой -
либо каталог и становится возможно открыть его по имени. Если этот каталог
находится в корневом дереве системы - этот объект видим для всех. Если это
каталог какого-либо процесса - он видим только ему. (Обычно все-таки
объекты создаются в системном дереве - так как большинство системных
сервисов работает именно с ним.) Для того, что бы представить себе полную
картину этого процесса, сначала придется разобраться с кое-какими
вопросами...
08.Безопасность объектов
========================
До сих пор я избегал этой темы, потому что это было возможно. Но что
бы продолжать дальше придется напомнить некоторые вещи. К счастью,
большинство структур о которых сейчас пойдет речь описаны в том или ином
.h файле и практически документированы. Но мне кажется логичным объеденить
всю необходимую информацию, потому что дальше я буду часто ссылаться на
эти структуры. Иногда я буду приводить и недокументированные вещи.
Для идентификации пользователя или группы, система безопасности
использует структуру SID. (Security IDentifier). При создании учетной
записи генерируется случайный SID, при этом гарантируется случайность SID.
typedef struct _SID_IDENTIFIER_AUTHORITY {
BYTE Value[6];
} SID_IDENTIFIER_AUTHORITY, *PSID_IDENTIFIER_AUTHORITY;
typedef struct _SID {
BYTE Revision;
BYTE SubAuthorityCount;
SID_IDENTIFIER_AUTHORITY IdentifierAuthority;
DWORD SubAuthority[ANYSIZE_ARRAY];
} SID, *PISID;
Текущее значение Revision - 1. Максимальное значение SubAuthorityCount
- 15. Последнее из полей SubAuthority носит название RID. (Reliativ. Id.)
В текстовом виде NT представляет SID в виде S-1-A-SA1-SA2-...-RID. Где S-1
означает SID rev.1, A - IdentifierAuthority, SA? - SubAuthority. RID -
последний из SubAuthority. В NT имееются предопределенные идентификаторы
SID, а также предопределенные значения RID. Например, RID равное 500
означает встроенную учетную запись administrator. Подробнее эту информацию
можно посмотреть в WINNT.H.
Пользователю предоставляется некоторый набор привилегий
SeXXXPrivilege, каждая из которых представляеься в системе 8 байтовым
числом LUID (Local Unique Id.) Первоначально каждая привилегия
идентефицируется текстовой строкой (текстовое представление привилегий
можно посмотреть в WINNT.H).
Эта информация не висит в воздухе. При входе пользователя в систему
LSA (Local Security Administrator) создает маркер доступа (TOKEN),
содержащую информацию важную с точки зрения безопасности. Известная ине на
данный момент часть структуры:
typedef struct _ACCESS_TOKEN{
char SourceName[8]; //0 Источник token'a
LUID SourceIdentifier; //8
LUID TokenId; //10
LUID AuthenticationId;//18
LARGE_INTEGER ExpirationTime; //20
LUID ModifiedId; //28 меняется при изминении token'a
DWORD NumberOfUserAndGroupSids;//30
DWORD NumberOfPrivileges //34
DWORD Unknown; //38
DWORD DynamicCharged; //3c
DWORD DynamicAvailable; //40
DWORD NumberOfOwnerSid //44
PVOID SidDB; //48
PSID PrimaryGroup; //4c
PLUID_AND_ATTRIBUTES TokenPrivileges; //50
DWORD Unknown1; //54
PACL DefaultDacl; //58
TOKEN_TYPE TokenType; //5c Первичный или
// олицетворение
SECURITY_IMPERSONATION_LEVEL ImpLevel; //60 Олицетворение
DWORD UnknownFlag; //64
DWORD Tail[ANYSIZE_ARRAY]; //????
}ACCESS_TOKEN,*PACCESS_TOKEN;
База данных SidDB представляет собой примерно следующее:
+-TokenUser----+
|00 PSID |
|04 Attributes |
+-TokenGroups--+
|.... |
| |
| |
+--------------+
<--NumberOfUserAndGroupSids
+-OwnerSid-----+
| |<--NumberOfOwnerSid
+--------------+
|....
Приведу пару недокументированных функций, связанных с получением
объекта 'Token'.
PACCESS_TOKEN NTOSKRNL PsReferenceImpersonationToken(
KTHREAD * Thread, //IN
PBOOLEAN* CopyOnOpen, //OUT
PBOOLEAN* EffectiveOnly, //OUT
SECURITY_IMPERSONATION_LEVEL* ImpersonationLevel //OUT
);
PACCESS_TOKEN NTOSKRNL PsReferencePrimaryToken(KPROCESS* Process);
Я не буду подробно останавливаться на структуре маркера доступа
(сейчас не стоит задача описывать диспетчер безопасности) , важно лишь
какого рода информация в нем хранится. Так же не буду объяснять механизм
имперсонизации (олицетворения). Указатель на TOKEN помещен по смещению
108h в теле объекта 'Process'.
Когда объект cоздается с помошью ObCreateObject(..), в качестве
параметра передается указатель на структуру OBJECT_ATTRIBUTES в которой
имеются поля SecurityDescriptor и SecurityQualityOfService - указатели на
документированные структуры.
SecurityQualityOfService содержит информацию о степени
имперсонализации клиента с сервером, не будем на этом остнавливаться.
SecurityDescriptor описывает безопасность объекта - то что сейчас
нужно.
typedef struct _SECURITY_DESCRIPTOR {
BYTE Revision;
BYTE Sbz1;
SECURITY_DESCRIPTOR_CONTROL Control;
PSID Owner;
PSID Group;
PACL Sacl;
PACL Dacl;
} SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;
Control задает различные флаги, один из которых указывает на то, что
дескриптор Self Relative (т.е. все указатели в нем заданы относительно
головы структуры). Подробнее см. WINNT.H. Назначение полей Owner и Group
понятно. Dacl - список в котором перечислены пользователи и группы,
которым разрешен или запрещен доступ к объекту. Sacl связан с аудитом.
Структуры, относящиеся к ACL, хорошо описаны в WINNT.H. Итак, с объектом
связана информация (указатель на дескриптор безопасности имеется в
заголовке объекта), описывающая доступность объекта для разных
пользователей.
При получении доступа к объекту (например, ObInsertObject) задается
запрашиваемая маска доступа ACCESS_MASK. Обычно, при ее задании пользуются
соответствующими определениями типа STANDARD_RIGHTS_READ и т.д. Сейчас
важно посмотреть на эту информацию с точки зрения структуры битов.
3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+---------------+---------------+-------------------------------+
|G|G|G|G|Res'd|A| StandardRights| SpecificRights |
|R|W|E|A| |S| | |
++-+-+-+-------++------+--------+--------------+----------------+
| | | | | | |
| | | | | | SpecificRights;
| | | | | StandardRights;
| | | | AccessSystemAcl
| | | +GenericAll
| | +--GenericExecute
| +----GenericWrite
+------GenericRead
Биты Generic присутствуют для удобства программиста (они стандартны и
просты). На самом деле, в структуре типа объекта находится поле
GenericMapping, которое содержит информацию о преобразвании бит Generic в
SpecificRights (специальные права - смысл бит зависит от типа объекта).
StandardRights, как следует из названия, имеют одинаковый смысл для всех
объектов. Хэндл объекта содержит поле GrantedAccess, таким образом, при
работе с хэндлом нет необходимости постоянного анализа дескриптора
безопасности, достаточно информации из описателя. Кроме того, объект-тип
содержит AllowedAccessMask, и таким образом для конкретных типов объектов
может контролироваться возможный доступ.
Внутри, NT оперирует такой структурой, как ACCESS_STATE.
typedef struct _ACCESS_STATE {
LUID OperationID;
BOOLEAN SecurityEvaluated;
BOOLEAN GenerateAudit;
BOOLEAN GenerateOnClose;
BOOLEAN PrivilegesAllocated;
ULONG Flags;
ACCESS_MASK RemainingDesiredAccess;
ACCESS_MASK PreviouslyGrantedAccess;
ACCESS_MASK OriginalDesiredAccess;
SECURITY_SUBJECT_CONTEXT SubjectSecurityContext;
PSECURITY_DESCRIPTOR SecurityDescriptor;
PVOID AuxData;
union {
INITIAL_PRIVILEGE_SET InitialPrivilegeSet;
PRIVILEGE_SET PrivilegeSet;
} Privileges;
BOOLEAN AuditPrivileges;
UNICODE_STRING ObjectName;
UNICODE_STRING ObjectTypeName;
} ACCESS_STATE, *PACCESS_STATE;
typedef struct _SECURITY_SUBJECT_CONTEXT {
PACCESS_TOKEN ClientToken;
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
PACCESS_TOKEN PrimaryToken;
PVOID ProcessAuditId;
} SECURITY_SUBJECT_CONTEXT, *PSECURITY_SUBJECT_CONTEXT;
Как видно, эта структура объединяет в себе всю необходимую информацию
для контроля доступа к объекту, когда происходит доступ. Приведу несколько
связанных с ней недокументированных функций.
// Заполняет структуру ACCESS_STATE
// Остается незаполненной информация о превилегиях и объекте.
NTSTATUS NTOSKRNL SeCreateAccessState(
PACCESS_STATE AccessState,
PVOID AuxData,
ACCESS_MASK Access,
PGENERIC_MAPPING GenericMapping
);
NTSTATUS NTOSKRNL SeDeleteAccessState(PACCESS_STATE AccessState);
// заполняет SUBJECT_CONTEXT, используется в SeCreateAccessState
void NTOSKRNL SeCaptureSubjectContext(
PSUBJECT_CONTEXT SubjectContext
);
В DDK также описаны функции ObGetObjectSecurity и
ObReleaseObjectSecurity. Первая из них интересна тем, что из нее
происходит вызов метода SecurityProcedure (см. формат структуры
объекта-типа) с кодом 1. Этот метод ответственен за возвращение дескриптора
безопасности если он присутствует. Иначе происходит обращение к заголовку
объекта для получения дескриптора.
09.Если у объекта есть имя.
==========================
Рассмотрим вторую ветвь функции ObInsertObject, которая выполняется в
случае именованного объекта. Если объект не имеет имени или является
эксклюзивным - ему не нужен дескриптор безопасности. Иначе дело обстоит в
случае, когда доступ к объекту становится возможен по имени.
В самом начале ветви происходит создание структуры ACCESS_STATE и
происходит поиск такого-же имени в дереве с корнем в
OBJECT_INFO.RootDirectory (эта структура пока существует). Поиск
производится с помошью внутренней функции ObpLookupObjectName. Логика ее
работы очевидна - происходит анализ соответствующих объектов Directory и
SymbolicLink, образующих в связке дерево. Вообще то ObpLookupObjectName
используется не только для поиска, но и для создания объекта в дереве.
При разборе пути происходит также проверка на доступность объекта в дереве
с точки зрения безопасности (функция ObpCheckTraverseAccess). Разбор
происходит до тех пор, пока не закончится анализируемый путь или в пути не
встретится объект, неизвестный менеджеру объектов. (Это называется другим
пространством имен) В этом случае вызывается метод ParseProcedure (если он
есть), однин из параметров которого, указатель на оставшийся неразобранный
путь. Дальнейший анализ возлагается на этот метод. Поиск с помошью вызовов
метода Parse возможен по всем пространствам имен, но вставка объекта в
дерево возможна только в 'родном' корневом дереве. Метод ParseProcedure
предназначен только для поиска и игнорируется при создании представления
объекта в дереве (если он не указывает на процедуру по умолчанию -
ObpParseSymbolicLink. Но символьные ссылки являются родными для диспетчера
объектов, и обрабатываются всегда.) Если произошла коллизия имен (объект
уже существует), то дальнейшее поведение ObInsertObject зависит от флага
OBJ_OPENIF. Этот флаг переводится как open if exist. Другими словами,
будет открыт существующий объект. При нормальном же течении событий в
конечном счете для созданного объекта вызывается недокументированная
функция ObAssignSecurity. (Обратная функция ObGetObjectSecurity
документирована). Эта функция - заглушка для документированной
SeAssignSecurity(...)
BOOLEAN NTOSKRNL ObAssignSecurity{
PACCESS_STATE AccessState,
PSECURITY_DESCRIPTOR OPTIONAL OldSecurityDescriptor,
PVOID Object,
POBJECT_TYPE Type);
Параметр SecurityDecriptor используется только для передачи как
параметр в метод SecurityProcedure, который вызывается из этой функции c
кодом 3. Вся нужная диспетчеру безопасности информаци собрана
в структуре ACCESS_STATE. После работы этой функции,
заполняется поле SecurityDescriptor в загловке объекта (этого
не происходило для неименованного объекта) Затем происходит
вызов внутренней функции ObpCreateHandle. Не смотря на то, что
она не экспортируется, приведу прототипы функций ObpCreateHandle и
ObpCreateUnnamedHandle для того, что бы их можно было сравнить.
NTSTATUS ObpCreateHandle(
DWORD SecurityMode, // обычно==1, при этом проверяется доступ
PVOID Object,
POBJECT_TYPE Type,
PACCESS_STATE AccessState,
DWORD RefCounterDelta,
DWORD Attributes,
DWORD OPTIONAL UnknErrMode,
KPROCESSOR_MODE bMode,
PVOID Object,
PHANDLE Handle
);
NTSTATUS ObpCreateUnnamedHandle(
PVOID Object,
ACCESS_MASK Access,
DWORD RefCounterDelta,
DWORD Attributes,
KPROCESSOR_MODE bMode,
PVOID Object,
PHANDLE Handle
);
Обе функции делают практически одинаковую работу и, в конечном счете,
вызывают ExCreateHandle(..). Но, как наглядно видно из прототипов, первая
функция работает с диспетчером безопасности. Процесс создания хэндла уже
был описан и нам известен. Наконец, освобождается структура OBJECT_INFO. Я
уже говорил, что для этих структур поддерживается LookasideList, теперь
понятно зачем. В системе очень часто создаются и удаляются объекты, работа
с созданием OBJECT_INFO идет также интенсивно (эти структуры присутствуют
у каждого объекта в начале его существования.) LookasideList специально
направлен для оптимизации таких частых операций выделения/освобождения
памяти. (см. DDK).
0a.Смерть объекта
=================
Диспетчер сам удаляет объект, если он больше не нужен и он не
перманентный (бит OBJ_PERMANENT). При этом умирание происходит в два
этапа. В заголовке имеется два поля - поля RefCounter и HandleCounter. При
закрытии описателя (ZwClose) происходит последовательность вызовов
ExDeleteHandle и ObpDecrementHandleCount. Первая просто удаляет описатель
из базы данных хэндлов (свободное место возвращается в списох пустых
мест). ObpDecrementHandleCount, соответственно, поддерживает согласованной
информацию о числе открытых хэндлов, а также базу данных информации о
хэндлах (HANDLE_DB). Кроме того, из этой функции вызывается метод
CloseProcedure. Если обнаруживается, что закрыт последний описатель и
объект не перманентный - происходит вызов функции ObpDeleteNameCheck,
которая удаляет информацию об объекте из дерева имен. При этом вызывается
метод SecurityProcedure с кодом delete. Но объект продолжает существовать
- он просто перестает быть видимым.
Кроме того в заголовке, имеется поле количества ссылок на объект,
которое уменьшается с помошью функции ObDereferenceObject. (При
увеличении/уменшении числа хэндлов количество ссылок также увеличивается
или уменьшается, но можно изменить число ссылок на объект, не меняя числа
хэндлов.) Если достигнуто нулевое значение ссылок, в общем случае
происходит добавление объекта в очередь на удаление (в зависимости
от обстоятельств, объект может быть удален сразу). При этом поля RefCount
и HandleCount играют роль LIST_ENTRY. Я не делал объединения в формате
заголовка объекта для этих полей, что бы не усложнять все сразу. При
непосредственном удалении объекта происходит вызов методов
SecurityProcedure с кодом 2 и метода DeleteProcedure. Все функции ядра,
которые сейчас упомянались, кроме ObDereferenceObject, являются
внутренними и их прототипы я не привожу, хотя они и известны.
0b.Другие функции диспетчера
============================
На самом деле, рассмотрение функции ObInsertObject раскрывает основную
часть логики диспетчера объектов. Рассмотрим кратко оставшиеся
экспортируемые функции диспетчера.
Из списка функций, приведенного в начале, документированы в DDK
следующие функции:
ObReferenceObject, ObDereferenceObject, ObGetObjectSecurity,
ObReleaseObjectSecurity, ObReferenceObjectByHandle,
ObReferenceObjectByPointer.
В свете уже известных знаний, логика работы диспетчера в этих функциях
прозрачна. Это работа с полями заголовка объекта, базой данных хэндлов и
синхронизацией от внутренних объектов. Остались нерассмотренными:
ObCheckCreateObjectAccess , ObCheckObjectAccess,
ObFindHandleForObject, ObGetObjectPointerCount,
ObMakeTemporaryObject, ObOpenObjectByName, ObOpenObjectByPointer,
ObQueryNameString, ObQueryObjectAuditingByHandle,
ObReferenceObjectByName, ObSetSecurityDescriptorInfo
Уже известная информация позволяет описать все эти функции.
// возвращает полное имя объекта
// в качестве параметра - буффер для хранения UNICODE_STRING
// и имени.
// Обычно вызывается сначала без параметра Result, что бы получить
// необходимую длину для буфера.
// Если определен метод QueryName - вызывается он
NTSTATUS NTOSKRNL ObQueryNameString(
PVOID Object,
PUNICODE_STRING Result OPTIONAL,
DWORD Len,
PDWORD RetLen OPTIONAL
);
// вызывает ObpLookupObjectName
NTSTATUS NTOSKRNL ObReferenceObjectByName(
PUNICODE_NAME Name,
DWORD Attributes, // чаще всего используется OBJ_CASE...
PACCESS_STATE AccessState OPTIONAL,
ACCESS_MASK Access,
POBJECT_TYPE Type,
KPROCESSOR_MODE bMode,
DWORD Unknown OPTIONAL, // скорее даже зарезервировано,
// используется только как параметр к SecurityProcedure
// всегда было 0
PVOID BodyPointer
);
// основная логика ObpLookupObjectName/ ObpCreateHandle
NTSTATUS ObOpenObjectByName(
POBJECT_ATTRIBUTES Attributes,
POBJECT_TYPE Type,
KPROCESSOR_MODE bMode,
PACCESS_STATE AccessState OPTIONAL,
ACCESS_MASK Access,
DWORD Unknown OPTIONAL, // см. unknown в
// ObReferenceObjectByName
PHANDLE Handle OUT
);
// основная логика ObReferenceObjectByPointer / ObpCreateHandle
NTSTATUS NTOSKRNL ObOpenObjectByPointer(
PVOID Object,
DWORD Attributes,
PACCESS_STATE AccessState OPTIONAL,
ACCESS_MASK Access,
POBJECT_TYPE Type,
KPROCESSOR_MODE bMode,
PHANDLE Handle OUT
);
// код функции :
// mov eax,[esp+pObject]
// mov eax,[eax-18h]
// retn 4
// Все!
DWORD NTOSKRNL ObGetObjectPointerCount(PVOID Object);
// Интересная функция. Она ВООБЩЕ не используется самим ядром и в то же
// время не документирована.
// для полей Object, Type и HandleAttributes возможны значения 0,
// тогда поиску будут удовлетворять любые соответствующие значения.
// Это позволяет гибко переберать информацию о хэндлах
BOOLEAN NTOSKRNL ObFindHandleForObject(
PPROCESS Process,
PVOID Object,
POBJECT_TYPE Type,
DWORD HandleAttributes, //младшие 3 бита. См. 'таблица
// хэндлов'
PHANDLE Handle OUT // возвращенный хэндл
);
// эта функция очищает соответствующий флаг в заголовке объекта
// тем самым разрешая удалить его, если на него нет ссылок и вызывает
// внутреннею функцию ObpDeleteNameCheck
NTSTATUS NTOSKRNL ObMakeTemporaryObject(
PVOID Object
);
// Основная логика - ObGetObjectSecurity/SeAccessCheck
BOOLEAN NTOSKRNL ObCheckObjectAccess(
PVOID Object,
PACCESS_STATE AccessState,
BOOLEAN bTypeLocked,
KPROCESSOR_MODE Mode,
OUT PNTSTATUS AccessStatus
);
// Используется в ObpLookupObjectName при создании представления объекта в
// дереве.
BOOLEAN NTOSKRNL ObCheckCreateObjectAccess(
PDIRECTORY Directory,
ACCESS_MASK Access,
PACCESS_STATE AccessState,
PUNICODE NameFromDir,
DWORD UnknLockedFlags,
KPROCESSOR_MODE Mode,
OUT PNTSTATUS AccessStatus
);
// последовательность ExMapHandleToPointer/извлечь бит 0x4 (см. 'база
// данных хэндлов')
BOOLEAN NTOSKRNL ObQueryObjectAuditingByHandle(
HANDLE Handle,
PDWORD ReturnValue);
// Эта функция вызывается из метода SpDefaultObjectMetod для кода 0 - set
// security descriptor. В работе эта функция опирается на
// SeSetSecurityDesriptorInfo. Сейчас мы не рассмариваем подробно
// диспетчер безопасности, кроме того, назначение некоторых параметров еще
// не выяснено, так что прототип этой функции я не привожу.
// ObSetSecurityDescriptorInfo(...);
Не останавливаться на будущем -
Я понял, что его не будет
И ты знаешь, когда я уйду -
Ты услышишь мой плач в ветре...
(с) by Anathema
0c.Заключение.
==============
На самом деле, теперь совершенно очевидно, что множество
экспортируемых функций диспетчера объектов не является функционально
полным. Мне не ясно, зачем тогда надо было вообще экспортировать
большинство из рассмотренных выше функций. Вообще, если ставить вопрос о
применимости, можно высказать такие соображения.
Kernel драйверы не работают с хэндлами. Вообще, в общем случае
не известно в контексте какого процесса выполняется код драйвера.
(системного, процесса делающего запрос, 'случайного' процесса). Реально
используются функции, которые позволяют ссылаться на объект по имени или
указателю.
Для остальных функций трудно придумать реальное применение, если
только мы не пишем системные сервисы (кстати, это вполне возможно). В
любом случае, знание работы диспетчера полезно для того, что бы
представлять реальные возможности ОС Windows NT. Как мы убедились, система
работы с объектами очень гибкая - ведь исполнительная система NT
писалась с учетом использования ее для эмуляций различных сред ОС.
Конечно, возможно, что текст содержит неточности. Любые комментарии и
замечания свободно пишите на peter_k@vivos.ru (Peter Kosyh)
Best regards, Gloomy
ПРИЛОЖЕНИЕ
13.Получение информации об объекте из user режима
=================================================
В Windows NT есть множество системных вызовов, которые имеют в своем
названии Query или QueryInformation. Все они позволяют получить любопытную
информацию и в большинстве своем недокументированы. Ниже приведен прототип
вызова NtQueryObject, который легко было получить обладая знаниями о
формате объектов.
typedef enum _OBJECTINFOCLASS {
BaseObjectInfo,
NameObjectInfo,
TypeObjectInfo,
AllTypesObjectInfo,
HandleObjectInfo
} OBJECTINFOCLASS;
NTSYSAPI NTSTATUS NTAPI NtQueryObject(
HANDLE ObjHandle,
OBJECTINFOCLASS ObjectInfoClass,
OUT PVOID ObjectInfo, // буфер под информацию
DWORD ObjectInfoLen, // длина буфера
OUT PDWORD RetInfoLen // доина записанной информации
);
typedef struct _BASE_OBJECT_INFO{
DWORD HandleAttributes,
ACCESS_MASK GrantedAccess,
DWORD HandleCount,
DWORD RefCount,
DWORD PagedPollQuota,
DWORD NonPagedPollQuota,
DWORD ReservedAndAlwaysNull[3],
DWORD NameObjectInfoLength,
DWORD TypeObjectInfoLength,
DWORD SecurityDescriptorLengh,
LARGE_INTEGER SymLinkCreationTime //время от 1601 года
};
typedef struct _NAME_OBJECT_INFO{
UNICODE_STRING Name;
// тут должно быть место под имя. Параметр ObjectInfo может быть 0
// что бы узнать нужный размер под буфер.
}NAME_OBJECT_INFO;
typedef struct _TYPE_OBJECT_INFO{
UNICODE_STRING Name;
DWORD InstanceCount;
DWORD HandleCount;
DWORD PeakObjects;
DWORD PeakHandles;
DWORD AllowedAttributesMask;
GENERIC_MAPPING GenericMapping;
DWORD AllowedAccessMask;
BOOLEAN bInNameSpace;
BOOLEAN bHandleDBInfo;
BOOLEAN Align[2];
DWORD Unknown6; // см. поле unknown6 в объекте-типе
DWORD DefaultPagedPollQuota;
DWORD DefaultNonPagedPollQuota;
}TYPE_OBJECT_INFO;
typedef struct _ALL_TYPES_OBJECT_INFO{
DWORD NumOfTypes;
TYPE_OBJECT_INFO [ANY_SIZE_ARRAY];
}ALL_TYPES_OBJECT_INFO;
typedef struct _HANDLE_OBJECT_INFO{
BOOLEAN Inherit;
BOOLEAN ProtectFromClose;
}HANDLE_OBJECT_INFO;
14.Некоторые системные сервисы, связанные с диспетчером объектов
================================================================
В книге "Недокументированные возможности Windows NT" А.В. Коберниченко
приведены прототипы некоторых интересных системных сервисов. Вообще, автор
поставил себе цель описать прототипы недокументированных системных
вызовов отталкиваясь от использования их в kernel32.dll. Наверное, для
таким образом поставленной цели, это кратчайший путь. (Кстати, в этой книге
была попытка описать функцию NtQueryObject, но в данном случае такой
подход оказался неприемлим и функция в книге описана очень неполно.) Я
проверил его выводы для некоторых функций дизассемблированием и убедился в
правильности определений. Ниже приводятся некоторые вызовы так, как их
описал автор.
//Функции для работы с любым объектом
NTSYSAPI NTSTATUS NTAPI
NtClose(IN HANDLE Handle);
NTSYSAPI NTSTATUS NTAPI NtMakeTemporaryObject(
IN HANDLE Handle
);
#define DUPLICATE_CLOSE_SOURCE 0x00000001
#define DUPLICATE_SAME_ACCESS 0x00000002
NTSYSAPI NTSTATUS NTAPI
NtDuplicateObject(
IN HANDLE SourceProcessHandle,
IN HANDLE SourceHandle,
IN HANDLE TargetProcessHandle,
OUT PHANDLE TargetHandle OPTIONAL,
IN ACCESS_MASK DesiredAccess,
IN ULONG Attributes,//OBJ_xxx
IN ULONG Options
);
//Объект каталог
#define DIRECTORY_QUERY (0x0001)
#define DIRECTORY_TRAVERSE (0x0002)
#define DIRECTORY_CREATE_OBJECT (0x0004)
#define DIRECTORY_CREATE_SUBDIRECTORY (0x0008)
#define DIRECTORY_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | 0xF)
NTSYSAPI NTSTATUS NTAPI
NtCreateDirectoryObject(
OUT PHANDLE DirectoryHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
NTSYSAPI NTSTATUS NTAPI
NtOpenDirectoryObject(
OUT PHANDLE DirectoryHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
typedef struct _OBJECT_NAMETYPE_INFO {
UNICODE_STRING ObjectName;
UNICODE_STRING ObjectType;
} OBJECT_NAMETYPE_INFO, *POBJECT_NAMETYPE_INFO;
typedef enum _DIRECTORYINFOCLASS {
ObjectArray,
ObjectByOne
} DIRECTORYINFOCLASS, *PDIRECTORYINFOCLASS;
NTSYSAPI NTSTATUS NTAPI
NtQueryDirectoryObject(
IN HANDLE DirectoryObjectHandle,
OUT PVOID ObjectInfoBuffer,
IN ULONG ObjectInfoBufferLength,
IN DIRECTORYINFOCLASS DirectoryInformationClass,
IN BOOLEAN First,
IN OUT PULONG ObjectIndex,
OUT PULONG LengthReturned
);
//Объект символическая ссылка
#define SYMBOLIC_LINK_QUERY (0x0001)
#define SYMBOLIC_LINK_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | 0x1)
NTSYSAPI NTSTATUS NTAPI
NtCreateSymbolicLinkObject(
OUT PHANDLE ObjectHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PUNICODE_STRING SubstituteString
);
NTSYSAPI NTSTATUS NTAPI
NtOpenSymbolicLinkObject(
OUT PHANDLE ObjectHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
typedef struct _OBJECT_NAME_INFORMATION {
UNICODE_STRING Name;
} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;
NTSYSAPI NTSTATUS NTAPI
NtQuerySymbolicLinkObject(
IN HANDLE ObjectHandle,
OUT POBJECT_NAME_INFORMATION SubstituteString,
OUT PULONG SubstituteStringLength //в байтах
);
Я думаю, теперь будет несложно догадаться какие действия с
диспетчером производит большинство описанных системных сервисов...
В интеренете можно найти примеры к книге "Недокументированные
возможномти Windows NT" - автор проделал отличную работу. В исходных текстах
описано множество недокументированных системных сервисов и даны примеры их
использования.
---------------------------------------------------------------------------
(c)Gloomy aka Peter Kosyh, Melancholy Coding'2001
http://gloomy.cjb.net
mailto:gl00my@mail.ru