关闭

windows Object Manager

标签: windowsreactos对象管理器源码分析
202人阅读 评论(0) 收藏 举报
分类:

Handles Object Manager Initialization and Shutdown

阶段 0 初始化:
1.初始化obci和obnm,创建信息和对象名称链表
2.创建安全描述符缓存
3.初始化默认对象事件和设备映射锁
4.在EPROCESS AND ETHREAD set GrantedAccess 为高等访问权限
5.创建句柄表,并设置到EPROCESS 的ObjectTable上。
6.创建 Type Diretory SymbolicLink 三种对象类型
阶段1初始化:
1.初始化对象目录,L”\”,L”\KernelObjects”, L”\ObjectTypes”
2.检索ObpTypeObjectType->TypeList,将类型对象加入到 L”\ObjectTypes”中
3.创建DosDeviceDirectory L”\GLOBAL??”
4.创建关于dos device directory 的符号连接

对象的具体管理

创建对象 ObCreateObject

我们先看对象头部,这个是对象管理器来管理的,它自己创建对象头部的相关信息。

typedef struct _OBJECT_HEADER
{
    LONG PointerCount;
    union
    {
        LONG HandleCount;
        volatile PVOID NextToFree;
    };
    POBJECT_TYPE Type;
    UCHAR NameInfoOffset;
    UCHAR HandleInfoOffset;
    UCHAR QuotaInfoOffset;
    UCHAR Flags;
    union
    {
        POBJECT_CREATE_INFORMATION ObjectCreateInfo;
        PVOID QuotaBlockCharged;
    };
    PSECURITY_DESCRIPTOR SecurityDescriptor;
    QUAD Body;
} OBJECT_HEADER, *POBJECT_HEADER;

一般情况下创建对象就是根据不同的要求设置对象头,对应的申请空间也是不同大小的,要在ObpAllocateObject中计算对象头的大小,然后申请内存供上层函数完成对象的创建。跟着对象头一起创建的还有quotainfo,handleinfo,nameinfo,creatorinfo.然后将这些信息的偏移地址分别设置到header上,然后初始化header数据结构中的其他变量。设置PointCount 和HandleCount,并设置对象类型,以分辨出是何种对象等等。
以创建timer为例,当上层调用对象管理器的api来创建对象时,使用如下参数创建

    /* Create the Object */
    Status = ObCreateObject(PreviousMode,
                            ExTimerType,
                            ObjectAttributes,
                            PreviousMode,
                            NULL,
                            sizeof(ETIMER),
                            0,
                            0,
                            (PVOID*)&Timer);

我们可以在ObCreateObject获知创建模式,创建属性和创建的对象大小。计算出FinaSize的值(即header中额外的管理信息)和objectSize 是个sizeof(ETIMER)。再根据该对象自己的属性,来决定是创建在换页或是非换页的内存上。

    Header = ExAllocatePoolWithTag(PoolType, FinalSize + ObjectSize, Tag);

然后如上申请了某个对象内存空间,获得该对象的一个指针,此时系统中存在了一个内核对象的数据实例,当创建成功后,ObCreateObject最后一个参数获得的是一个header->Body,此时我们知道,对于对象的其他数据是通过对象管理器来管理的,而且提供对外接口,当外部引用这个数据的时候,我们会增加引用计数和句柄计数,修改对象头内部数据。

插入对象ObInsertObject

当我们创建成功后,我们修改自己的对象内容,比如:

        Timer->ApcAssociated = FALSE;
        Timer->WakeTimer = FALSE;
        Timer->WakeTimerListEntry.Flink = NULL;

设置完对象数据后,我们调用插入对象,这里一般是获得对象操作的句柄,还是以timer为例

        /* Insert the Timer */
        Status = ObInsertObject((PVOID)Timer,
                                NULL,
                                DesiredAccess,
                                0,
                                NULL,
                                &hTimer);

在函数中我们根据对象体Timer来获取对象头header,然后取出对象头中的createinfo,nameinfo, objecttype,objectname.
然后根据对象名在对象管理器中查找对象。

Status = ObpLookupObjectName(ObjectCreateInfo->RootDirectory,
                                     ObjectName,
                                     ObjectCreateInfo->Attributes,
                                     ObjectType,
                                     (ObjectHeader->Flags & OB_FLAG_KERNEL_MODE) ?
                                     KernelMode : UserMode,
                                     ObjectCreateInfo->ParseContext,
                                     ObjectCreateInfo->SecurityQos,
                                     Object,
                                     AccessState,
                                     &Context,
                                     &InsertObject);

当找到对象后,ObpCreateHandle来创建对象句柄。这种情况一般是向上层提供句柄,供上层使用。

创建对象类型

NTSTATUS
NTAPI
ObCreateObjectType(IN PUNICODE_STRING TypeName,
                   IN POBJECT_TYPE_INITIALIZER ObjectTypeInitializer,
                   IN PVOID Reserved,
                   OUT POBJECT_TYPE *ObjectType);

我们现在对象管理器的类型目录中查找该对象类型,看是否已经存在该对象。如果存在我们返回,如果不存在,那么调用ObpAllocateObject申请对象
并设置header->Type = ObpTypeObjectType,设置成为一个类型对象。

对象目录

在系统启动时初始化对象类型目录,如下创建目录对象,一共创建三个基础目录L”\”,L”\KernelObjects”, L”\ObjectTypes”

NTSTATUS
NTAPI
NtCreateDirectoryObject(OUT PHANDLE DirectoryHandle,
                        IN ACCESS_MASK DesiredAccess,
                        IN POBJECT_ATTRIBUTES ObjectAttributes) 

如下创建自己的根目录对象

    /* Initialize Object Types directory attributes */
    RtlInitUnicodeString(&Name, L"\\");
    InitializeObjectAttributes(&ObjectAttributes,
                               &Name,
                               OBJ_CASE_INSENSITIVE | OBJ_PERMANENT,
                               NULL,
                               SePublicDefaultUnrestrictedSd);

    /* Create the directory */
    Status = NtCreateDirectoryObject(&Handle,
                                     DIRECTORY_ALL_ACCESS,
                                     &ObjectAttributes);
    if (!NT_SUCCESS(Status)) return FALSE;

    /* Get a handle to it */
    Status = ObReferenceObjectByHandle(Handle,
                                       0,
                                       ObDirectoryType,
                                       KernelMode,
                                       (PVOID*)&ObpRootDirectoryObject,
                                       NULL);
    if (!NT_SUCCESS(Status)) return FALSE;

    /* Close the extra handle */
    Status = NtClose(Handle);
    if (!NT_SUCCESS(Status)) return FALSE;

当我们要查找对象时,就指定在那个目录下查找,这个范围通过ObpRootDirectoryObject来指定。
自己创建的类型对象放置于 L”\ObjectTypes”中,新创建也会检索这个路径下是否已存在。而类型也是一个对象,创建起来没有和正常对象又太大的区别。
这个函数中,我们通过对象属性和访问标志,创建对象目录。内部调用ObCreateObject来创建对象,使用的参数是ObDirectoryType,来表示创建的是个目录对象。
当我们要创建新的对象类型时,要查找类型目录,看这个对象类型是否已存在。

在对象目录中查找对象

PVOID
NTAPI
ObpLookupEntryDirectory(IN POBJECT_DIRECTORY Directory,
                        IN PUNICODE_STRING Name,
                        IN ULONG Attributes,
                        IN UCHAR SearchShadow,
                        IN POBP_LOOKUP_CONTEXT Context)

上述函数用来在类型目录中查找已存在的类型。
这个函数中我们使用的是hash算法来查找的,我们找到对应的目录,然查看目录中是否有我们这个名字的对象,最终还是根据对象名来最终匹配是否是我们建立的对象类型。

向目录中插入对象

BOOLEAN
NTAPI
ObpInsertEntryDirectory(IN POBJECT_DIRECTORY Parent,
                        IN POBP_LOOKUP_CONTEXT Context,
                        IN POBJECT_HEADER ObjectHeader);

对象目录中每一项如下定义,函数中申请_OBJECT_DIRECTORY_ENTRY实例内存,填写内部信息,修改ChainLink链表,将新的对象挂入到制定的目录中。并设置ObjectHeader的内部信息,指向这个目录。

//
// Object Directory Structures
//
typedef struct _OBJECT_DIRECTORY_ENTRY
{
    struct _OBJECT_DIRECTORY_ENTRY *ChainLink;
    PVOID Object;
#if (NTDDI_VERSION >= NTDDI_WS03)
    ULONG HashValue;
#endif
} OBJECT_DIRECTORY_ENTRY, *POBJECT_DIRECTORY_ENTRY;

如果我们要删除对象目录时,我们使用ObpDeleteEntryDirectory来删除目录,我们是找到指定的链表,然后断开链表,释放object_directory_entry所指向的空间。

浏览对象

Status = ObpLookupObjectName(ObjectCreateInfo->RootDirectory,
                                     ObjectName,
                                     ObjectCreateInfo->Attributes,
                                     ObjectType,
                                     (ObjectHeader->Flags & OB_FLAG_KERNEL_MODE) ?
                                     KernelMode : UserMode,
                                     ObjectCreateInfo->ParseContext,
                                     ObjectCreateInfo->SecurityQos,
                                     Object,
                                     AccessState,
                                     &Context,
                                     &InsertObject);
it look up a name under only two circumstances:
1. when a process creates a names object
2. when a process opens a handle to a named object.

句柄的管理

句柄的创建

在进程创建的时候即PspCreateProcess,某一时刻调用ObinitProcess 来初始化进程关于对象处理方面的操作。这里一般是继承句柄表或者是创建进程的句柄表。

根据对象名打开对象

NTSTATUS
NTAPI
ObOpenObjectByName(IN POBJECT_ATTRIBUTES ObjectAttributes,
                   IN POBJECT_TYPE ObjectType,
                   IN KPROCESSOR_MODE AccessMode,
                   IN PACCESS_STATE PassedAccessState,
                   IN ACCESS_MASK DesiredAccess,
                   IN OUT PVOID ParseContext,
                   OUT PHANDLE Handle)

一般情况下,以thread操作为例,当我们打开某个线程时使用NtOpenThread函数,在这个函数的内部,检查属性判断是否按线程对象名打开线程,如果是,我们开始调用ObOpenObjectByName函数来处理我们这一操作。在ObOpenObjectByName中,开始我们进行参数检测和安全性检查,然后我们使用ObpLookupObjectName在对象根目录中查找该对象,当我们找到对象的时候,得到了该对象的指针,然后根据对象中是否有创建信息,来判断我们是创建句柄释放创建信息还是打开句柄。无论是创建还是打开,我们都使用ObpCreateHandle来获得对象对应的句柄。

NTSTATUS
NTAPI
ObpCreateHandle(IN OB_OPEN_REASON OpenReason,
                IN PVOID Object,
                IN POBJECT_TYPE Type OPTIONAL,
                IN PACCESS_STATE AccessState,
                IN ULONG AdditionalReferences,
                IN ULONG HandleAttributes,
                IN POBP_LOOKUP_CONTEXT Context,
                IN KPROCESSOR_MODE AccessMode,
                OUT PVOID *ReturnedObject,
                OUT PHANDLE ReturnedHandle);

这个函数来处理对象和句柄的关系,并调用ExCreatehandle来实际创建句柄,首先我们获取创建进程的句柄表即PsGetCurrentProcess()->ObjectTable,并更根据参数属性设置句柄项的值,单例句柄数据结构如下,当我们找到了对象后,使用Object(指向对象体)来分析出内核对象头,然后将对象标记到句柄项_HANDLE_TABLE_ENTRY中,这个实例句柄就已经指向一个对象了。然后我们设置其他相关值,设置句柄的访问属性。当然在创建之前使用了ObpIncrementHandleCount来递增了该对象的句柄计数。我们稍后分析这个函数。首先我们分析一下如何创建的句柄。也就是ExCreateHandle函数。

typedef struct _HANDLE_TABLE_ENTRY
{
    union
    {
        PVOID Object;
        ULONG_PTR ObAttributes;
        PHANDLE_TABLE_ENTRY_INFO InfoTable;
        ULONG_PTR Value;
    };
    union
    {
        ULONG GrantedAccess;
        struct
        {
            USHORT GrantedAccessIndex;
            USHORT CreatorBackTraceIndex;
        };
        LONG NextFreeTableEntry;
    };
} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;

如下函数,返回一个句柄,而返回值typedef void *HANDLE; 由此可以看到在windows 32 位平台下,HANDLE 只不过是一个32位的指针,函数参数也是极其简单,在内核中也是少见的简单的两个参数的函数,一个是句柄表,这个从指定进程的EPROCESS中给出,另外一个是句柄表项,表示一个单例句柄。后期我们会分析关于进程句柄表的组织结构。

HANDLE
NTAPI
ExCreateHandle(IN PHANDLE_TABLE HandleTable,
               IN PHANDLE_TABLE_ENTRY HandleTableEntry)

在函数内部,我们根据句柄表使用ExpAllocateHandleTableEntry创建一个句柄表项,将参数中的表项复制给这个新创建的句柄项。

递增句柄引用计数(todo)

当我们创建句柄时,我们需要在对象上递增句柄的引用计数,
NTSTATUS
NTAPI
ObpIncrementHandleCount(IN PVOID Object,
IN PACCESS_STATE AccessState OPTIONAL,
IN KPROCESSOR_MODE AccessMode,
IN ULONG HandleAttributes,
IN PEPROCESS Process,
IN OB_OPEN_REASON OpenReason)

对象的引用与释放

对象的释放

    /* Check whether the object can now be deleted. */
    OldCount = InterlockedDecrement(&Header->PointerCount);

首先递减指针计数,然后判断指针计数是否为零,如果是,查看是否当前可以进行apc,如果是则延迟删除对象,如果不是则直接删除对象。

删除对象

VOID
NTAPI
ObpDeleteObject(IN PVOID Object,
                IN BOOLEAN CalledFromWorkerThread);

函数中分别通过对象指针获取文件头,命名信息和创建信息。分别释放创建信息和命名信息,如果此对象又安全描述符回调函数和删除对象的回调函数,则调用之。最后我们释放对象体的空间。

根据指针引用对象

NTSTATUS
NTAPI
ObReferenceObjectByPointer(IN PVOID Object,
                           IN ACCESS_MASK DesiredAccess,
                           IN POBJECT_TYPE ObjectType,
                           IN KPROCESSOR_MODE AccessMode)
    /* Get the header */
    Header = OBJECT_TO_OBJECT_HEADER(Object);

这里的一个参数是对象指针,实际上指向对象体,我们通过这个指针得到对象头并验证对象类型和访问方式。而且不能是链接对象。然后检查通过后,递增指针计数

    /* Increment the reference count and return success */
    InterlockedIncrement(&Header->PointerCount);

根据句柄引用对象 ObReferenceObjectByHandle

我们先检查一下该句柄是不是特殊句柄,如果这个句柄是当前进程句柄,那么使用
CurrentProcess = PsGetCurrentProcess(); *Object = CurrentProcess; 直接获取到了进程对象指针
如果是一个当前线程句柄 CurrentThread = PsGetCurrentThread(); *Object = CurrentThread; 然后增加进程和线程对象的引用计数。
否则如果是内核模式,我们使用内核句柄表,如果是用户模式,我们使用当前进程的句柄表。
然后将句柄在句柄表中查询,得到对应句柄项,然后将从句柄项中提取对象头,返回对象体,并递增对象头中的PointerCount。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:15063次
    • 积分:352
    • 等级:
    • 排名:千里之外
    • 原创:21篇
    • 转载:0篇
    • 译文:0篇
    • 评论:3条
    博客专栏
    文章分类
    最新评论