内核对象

0x01  对象  程序员

  在计算机中,“对象”是个专有名词,其定义是“一个或一组数据结构及定义在其上的操做” 。windows

  对于几乎全部的内核对象,windows都提供一个统一的操做模式,就是先经过系统调用打开或建立目标对象,让当前进程与目标对象之间创建起链接,而后再经过别的系统调用进行操做,最后经过系统调用关闭对象。其实是关闭进程与目标对象的联系。数组

  常见的内核对象有:安全

  Job、Directory(对象目录中的目录)、SymbolLink(符号连接),Section(内存映射文件)、Port(LPC端口)、IoCompletion(Io完成端口)、File(并不是专指磁盘文件)、同步对象(Mutex、Event、Semaphore、Timer)、Key(注册表中的键)、Token(用户/组令牌)、Process、Thread、Pipe、Mailslot、Debug(调试端口)等数据结构

  内核对象就是一个数据结构,就是一个struct结构体,各类不一样类型的对象有不一样的定义,app

  全部内核对象都遵循统一的操做模式:函数

  第一步:先建立对象;布局

  第二步:打开对象,获得句柄(可与第一步合并在一块儿,表示建立时就打开)ui

  第三步:经过API(系统调用)访问对象;spa

  第四步,关闭句柄,递减引用计数;

  第五步:句柄所有关完而且引用计数降到0后,销毁对象。

  句柄就是用来维系对象的票据,就比如N名纤夫各拿一条绳,同拉一艘船。每打开一次对象就可拿到一个句柄,表示拿到该对象的一次访问权。

  

  

  内核对象是全局的,各个进程均可以访问,好比两个进程想要共享某块内存来进行通讯,就能够约定一个对象名,而后一个进程能够用CreatFileMapping(”SectionName”)建立一个section,而另外一个进程能够用OpenFileMapping(”SectionName”)打开这个section,这样这个section就被两个进程共享了。

   各个对象的结构体虽然不一样,但有一些通用信息记录在对象头中,对象头的结构体定义:

  

typedef struct _OBJECT_HEADER
{
    LONG PointerCount;//引用计数
    union
    {
        LONG HandleCount;//本对象的打开句柄计数(每一个句柄自己也占用一个对象引用计数)
        volatile VOID* NextToFree;//下一个要延迟删除的对象
    };
    OBJECT_TYPE* Type;//本对象的类型,类型自己也是一种内核对象,有人称之为‘类型对象’
    UCHAR NameInfoOffset;//对象名的偏移(无名对象没有Name)
    UCHAR HandleInfoOffset;//各进程的打开句柄统计信息数组
    UCHAR QuotaInfoOffset;//对象自己实际占用内存配额(当不等于该类对象的默认大小时要用到这个)
    UCHAR Flags;//对象的一些属性标志
    union
    {
        OBJECT_CREATE_INFORMATION* ObjectCreateInfo;//来源于建立对象时的OBJECT_ATTRIBUTES
        PVOID QuotaBlockCharged;
    };
    PSECURITY_DESCRIPTOR SecurityDescriptor;//安全描述符(对象的拥有者、ACL等信息)
    QUAD Body;//通用对象头后面紧跟着真正的结构体(这个字段是后面真正结构体中的第一个成员)
} OBJECT_HEADER, *POBJECT_HEADER;

  

typedef struct _OBJECT_HEADER_NAME_INFO
{
    POBJECT_DIRECTORY Directory;//对象目录中的父目录(不必定是文件系统中的目录)
    UNICODE_STRING Name;//相对于Directory的路径或者全路径
ULONG QueryReferences;//对象名查询操做计数
…
} OBJECT_HEADER_NAME_INFO, *POBJECT_HEADER_NAME_INFO;
typedef struct _OBJECT_HEADER_CREATOR_INFO
{
    LIST_ENTRY TypeList;//用来挂入所属‘对象类型’中的链表(也即类型对象内部的对象链表)
PVOID CreatorUniqueProcess;//表示本对象是由哪一个进程建立的
…
} OBJECT_HEADER_CREATOR_INFO, *POBJECT_HEADER_CREATOR_INFO;

  

  对象头中记录了NameInfo、HandleInfo、QuotaInfo、CreatorInfo这4种可选信息。若是这4种可选信息所有都有的话,整个对象的布局从低地址到高地址的内存布局为:

  QuotaInfo-> HandleInfo->NameInfo->CreatorInfo->对象头->对象体;这4种可选信息的相对位置倒不重要,可是必须记住,他们都是在对象头中的上方(也即对象头上面的低地址端)。如下为了方便,不妨叫作“对象头中的可选信息”、“头部中的可选信息”。

  因而有宏定义:

  //由对象体的地址获得对象头的地址

#define OBJECT_TO_OBJECT_HEADER(pBody)    CONTAINING(pBody,OBJECT_HEADER,Body)

//获得对象的名字

#define OBJECT_HEADER_TO_NAME_INFO(h)

   h->NameInfoOffset?(h - h->NameInfoOffset):NULL

//获得对象的建立者信息

#define OBJECT_HEADER_TO_CREATOR_INFO(h)

h->Flags & OB_FLAG_CREATOR_INFO?h-sizeof(OBJECT_HEADER_CREATOR_INFO):NULL

 

Windows Object完整的结构图:

+----------------------------------------------------------------+
+------->| ( OBJECT_HEADER_QUOTA_INFO )                                   |
|  +---->| ( OBJECT_HEADER_HANDLE_INFO )                                  |
|  |  +->| ( OBJECT_HEADER_NAME_INFO )                                    |
|  |  |  | ( OBJECT_HEADER_CREATOR_INFO )                                 |
|  |  |  +------------------------[ Object Header ]-----------------------+
|  |  |  | nt!_OBJECT_HEADER                                              |
|  |  |  |   +0x000 PointerCount     : Int4B                              |
|  |  |  |   +0x004 HandleCount      : Int4B                              |
|  |  |  |   +0x004 NextToFree       : Ptr32 Void                         |
|  |  |  |   +0x008 Type             : Ptr32 _OBJECT_TYPE                 |
|  |  +--|   +0x00c NameInfoOffset   : UChar                              |
|  +-----|   +0x00d HandleInfoOffset : UChar                              |
+--------|   +0x00e QuotaInfoOffset  : UChar                              |
         |   +0x00f Flags            : UChar                              |
         |   +0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION   |
         |   +0x010 QuotaBlockCharged : Ptr32 Void                        |
         |   +0x014 SecurityDescriptor : Ptr32 Void                       |
         |   +0x018 Body             : _QUAD                              |
         +-------------------------[ Object Body ]------------------------+
         | OBJECT_DIRECTORY, DRIVER_OBJECT, DEVICE_OBJECT, FILE_OBJECT... |
         +----------------------------------------------------------------+

 

0x02  对象目录

     全部有名字的对象都会进入内核中的‘对象目录’中,对象目录就是一棵树。树中的每一个节点都是对象。内核中有一个全局指针变量ObpRootDirectoryObject,就指向对象目录树的根节点,根节点是一个根目录。 

    对象目录的做用就是用来将对象路径解析为对象地址。给定一个对象路径,就能够直接在对象目录中找到对应的对象。就比如给定一个文件的全路径,必定能从磁盘的根目录中向下一直搜索找到对应的文件。

    如某个设备对象的对象名(全路径)是”\Device\MyCdo”,那么从根目录到这个对象的路径中:

    Device是根目录中的子目录,MyDevice则是Device目录中的子节点。

 对象有了名字,应用程序就能够直接调用CreateFile(也有其余的API进行打开不一样的对象)打开这个对象,得到句柄,没有名字的对象没法记录到对象目录中,应用层看不到,只能由内核本身使用。

   

 树的根是一个目录对象(OBJECT_DIRECTORY),树中的全部中间节点,必须是目录对象或者符号连接对象,而普通对象则只能成为“叶节点”。

    目录自己也是一种内核对象,其类型就叫“目录类型”,这种对象的结构体定义:

  

typedef struct _OBJECT_DIRECTORY
{
    struct _OBJECT_DIRECTORY_ENTRY*  HashBuckets[37];//37条hash链
    EX_PUSH_LOCK Lock;
    struct _DEVICE_MAP *DeviceMap;
    …
} OBJECT_DIRECTORY, *POBJECT_DIRECTORY;

  

如上,目录对象中的全部子对象按hash值分门别类的安放在该目录内部不一样的hash链中

其中每一个目录项的结构体定义为:

typedef struct _OBJECT_DIRECTORY_ENTRY
{
    struct _OBJECT_DIRECTORY_ENTRY * ChainLink;//下一个目录项(即下一个子节点)
    PVOID Object;//对象体的地址
    ULONG HashValue;//所在hash链
} OBJECT_DIRECTORY_ENTRY, *POBJECT_DIRECTORY_ENTRY; 

  每一个目录项记录了指向的对象的地址,同时间接记录了对象名信息。

  ObpLookupEntryDirectory函数用来在指定的目录中查找指定名称的子对象:

VOID*
ObpLookupEntryDirectory(IN POBJECT_DIRECTORY Directory,
                        IN PUNICODE_STRING Name,
                        IN ULONG Attributes,
                        IN POBP_LOOKUP_CONTEXT Context)
{
    BOOLEAN CaseInsensitive = FALSE;
    PVOID FoundObject = NULL;

    //表示对象名是否严格大小写匹配查找
    if (Attributes & OBJ_CASE_INSENSITIVE) CaseInsensitive = TRUE;

HashValue=CalcHash(Name->Buffer);//计算对象名的hash值
    HashIndex = HashValue % 37;//得到对应的hash链索引

    //记录本次是在那条hash中查找
    Context->HashValue = HashValue;
    Context->HashIndex = (USHORT)HashIndex;
    if (!Context->DirectoryLocked)
        ObpAcquireDirectoryLockShared(Directory, Context);//锁定目录,以便在其中进行查找操做
    
    //遍历对应hash链中的全部对象
    AllocatedEntry = &Directory->HashBuckets[HashIndex];
    LookupBucket = AllocatedEntry;
    while ((CurrentEntry = *AllocatedEntry))
    {
        if (CurrentEntry->HashValue == HashValue)
        {
            ObjectHeader = OBJECT_TO_OBJECT_HEADER(CurrentEntry->Object);
            HeaderNameInfo = OBJECT_HEADER_TO_NAME_INFO(ObjectHeader);
            if ((Name->Length == HeaderNameInfo->Name.Length) &&
                (RtlEqualUnicodeString(Name, &HeaderNameInfo->Name, CaseInsensitive)))
            {
                break;//找到对应的子对象
            }
        }
        AllocatedEntry = &CurrentEntry->ChainLink;
    }

    if (CurrentEntry)//若是找到了子对象
    {
        if (AllocatedEntry != LookupBucket)
            将找到的子对象挂入链表的开头,方便下次再次查找同一对象时直接找到;
        FoundObject = CurrentEntry->Object;
    }
    if (FoundObject) //若是找到了子对象
    {
        ObjectHeader = OBJECT_TO_OBJECT_HEADER(FoundObject);
        ObpReferenceNameInfo(ObjectHeader);//递增对象名字的引用计数
        ObReferenceObject(FoundObject);//注意递增了对象自己的引用计数

        if (!Context->DirectoryLocked)
            ObpReleaseDirectoryLock(Directory, Context);     
    }
    //检查本次函数调用前,查找上下文中是否已有一个先前的中间节点对象,如有就释放
    if (Context->Object)
    {
        ObjectHeader = OBJECT_TO_OBJECT_HEADER(Context->Object);
        HeaderNameInfo = OBJECT_HEADER_TO_NAME_INFO(ObjectHeader);
        ObpDereferenceNameInfo(HeaderNameInfo);
        ObDereferenceObject(Context->Object);
    }
    Context->Object = FoundObject;
    return FoundObject;//返回找到的子对象
}

  如上,hash查找子对象,找不到就返回NULL。

  注意因为这个函数是在遍历路径的过程当中逐节逐节的调用的,因此会临时查找中间的目录节点,记录到Context中。

   

 

 

0x03  对象类型

  对象是有分类的,也就是有类型(type)的。前面已经列举了一些常见的windows对象类型。用户能够经过安装内核模块即sys模块来达到增长新对象类型的目的。

  对象类型_OBJECT_TYPE结构体定义:

typedef struct _OBJECT_TYPE
{
    ERESOURCE Mutex;
    LIST_ENTRY TypeList;//本类对象的链表,记录全部同类对象
    UNICODE_STRING Name;//类型名
    PVOID DefaultObject;//指本类对象默认使用的同步事件对象
    ULONG Index;//本类型的索引,也即表示这是系统中第几个注册的对象类型
    ULONG TotalNumberOfObjects;//对象链表中总的对象个数
    ULONG TotalNumberOfHandles;//全部同类对象的打开句柄总数
    ULONG HighWaterNumberOfObjects;//历史本类对象个数峰值
ULONG HighWaterNumberOfHandles; //历史本类对象的句柄个数峰值
//关键字段。建立类型对象时,会将类型信息拷贝到下面这个字段中
    OBJECT_TYPE_INITIALIZER TypeInfo; 
    ULONG Key;//事实上用做内存分配的tag,同类对象占用的内存块都标记为同一个tag
    ERESOURCE ObjectLocks[4];
} OBJECT_TYPE;

  WINDOWS内核为新类型对象的定义提供了一个全局的_OBJECT_TYPE_INITIALIZER结构,做为须要填写和递交的申请单:

typedef struct _OBJECT_TYPE_INITIALIZER
{
    USHORT Length;//本结构体自己的长度
    BOOLEAN UseDefaultObject;//是否使用全局默认的同步事件对象
    BOOLEAN CaseInsensitive;//指本类对象的对象名是否大小写不敏感
    ULONG InvalidAttributes;//本类对象不支持的属性集合
    GENERIC_MAPPING GenericMapping;//一直懒得去分析这个字段
    ULONG ValidAccessMask;// 本类对象支持的属性集合
    BOOLEAN SecurityRequired;//本类对象是否须要安全控制(另外:凡有名字的对象都须要安全控制)
    BOOLEAN MaintainHandleCount;//对象头中是否维护句柄统计信息
    BOOLEAN MaintainTypeList;//是否维护建立者信息(也便是否须要挂入到所属对象类型的链表中)
    POOL_TYPE PoolType;//本类对象位于分页池仍是非分页池(通常内核对象都分配在非分页池中)
    ULONG DefaultPagedPoolCharge; //对象占用的分页池整体大小
    ULONG DefaultNonPagedPoolCharge;//对象占用的非分页池整体大小
    OB_DUMP_METHOD DumpProcedure;//?
    OB_OPEN_METHOD OpenProcedure;//打开对象时调用,很是重要
    OB_CLOSE_METHOD CloseProcedure;//关闭句柄时调用,很是重要
OB_DELETE_METHOD DeleteProcedure;//销毁对象时调用,很是重要
OB_PARSE _METHOD ParseProcedure;//自定义的路径解析函数(设备、文件、键都提供了此函数) 
    OB_SECURITY_METHOD SecurityProcedure;//查询、设置对象安全描述符的函数
    OB_QUERYNAME_METHOD QueryNameProcedure;//文件对象提供了自定义的QueryNameString函数
    OB_OKAYTOCLOSE_METHOD OkayToCloseProcedure;//每次关闭句柄前都会调用这个函数检查能否关闭
} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;

  

  Windows内核中有许多预约义的对象类型,程序员也能够本身注册一些自定义的对象类型,就像自注册“窗口类”同样。ObCreateObjectType这个函数用来注册一种对象类型(注意对象类型自己也是一种内核对象,所以,‘对象类型’便是‘类型对象’,‘类型对象’便是‘对象类型’)

  

NTSTATUS
ObCreateObjectType(IN PUNICODE_STRING TypeName,
                   IN POBJECT_TYPE_INITIALIZER ObjectTypeInitializer,
                   OUT POBJECT_TYPE *ObjectType)
{
ObpInitializeLookupContext(&Context);
//若 \ObjectTypes 目录下已经建立过了这种对象类型。返回失败
    ObpAcquireDirectoryLockExclusive(ObpTypeDirectoryObject, &Context);
    if (ObpLookupEntryDirectory(ObpTypeDirectoryObject,
                                TypeName,
                                OBJ_CASE_INSENSITIVE,
                                FALSE,
                                &Context))
    {
        ObpReleaseLookupContext(&Context);
        return STATUS_OBJECT_NAME_COLLISION;//不能重复建立同一种对象类型
    }
    

    ObjectName.Buffer = ExAllocatePoolWithTag(PagedPool,TypeName->MaximumLength,tag);
    ObjectName.MaximumLength = TypeName->MaximumLength;
    RtlCopyUnicodeString(&ObjectName, TypeName);

    //分配一块内存,建立类型对象
    Status = ObpAllocateObject(NULL, //CreateInfo=NULL
                               &ObjectName,//对象的名字
                               ObpTypeObjectType,//类型对象自己的类型
                               sizeof(OBJECT_TYPE),//对象的大小
                               KernelMode,
                               (POBJECT_HEADER*)&Header);
    LocalObjectType = (POBJECT_TYPE)&Header->Body;
    LocalObjectType->Name = ObjectName;//类型对象的自身的名称
    Header->Flags |= OB_FLAG_KERNEL_MODE | OB_FLAG_PERMANENT;//类型对象全由内核建立并有永久性

    LocalObjectType->TotalNumberOfObjects =0; 
    LocalObjectType->TotalNumberOfHandles =0; //本类对象的个数与句柄个数=0
   //拷贝类型信息(这个TypeInfo就是类型描述符)
    LocalObjectType->TypeInfo = *ObjectTypeInitializer;
    LocalObjectType->TypeInfo.PoolType = ObjectTypeInitializer->PoolType;

   //类型对象的对象体上面的全部头部大小
    HeaderSize = sizeof(OBJECT_HEADER) +
                 sizeof(OBJECT_HEADER_NAME_INFO)+(ObjectTypeInitializer->MaintainHandleCount ?sizeof(OBJECT_HEADER_HANDLE_INFO) : 0);
    if (ObjectTypeInitializer->PoolType == NonPagedPool)
        LocalObjectType->TypeInfo.DefaultNonPagedPoolCharge += HeaderSize;
    else
        LocalObjectType->TypeInfo.DefaultPagedPoolCharge += HeaderSize;
    //查询、设置对象安全描述符的函数
    if (!ObjectTypeInitializer->SecurityProcedure)
        LocalObjectType->TypeInfo.SecurityProcedure = SeDefaultObjectMethod;  
    if (LocalObjectType->TypeInfo.UseDefaultObject)
    {
        LocalObjectType->TypeInfo.ValidAccessMask |= SYNCHRONIZE;//本对象可用于同步操做
        LocalObjectType->DefaultObject = &ObpDefaultObject;//实际上是个全局的Event对象
    }
    //文件对象的结构体中可自带一个事件对象,WaitForSingleObject(FileObject)等待的就是那个事件
    else if ((TypeName->Length == 8) && !(wcscmp(TypeName->Buffer, L"File")))
        LocalObjectType->DefaultObject =FIELD_OFFSET(FILE_OBJECT,Event);//偏移
    else if ((TypeName->Length == 24) && !(wcscmp(TypeName->Buffer, L"WaitablePort")))
        LocalObjectType->DefaultObject = FIELD_OFFSET(LPCP_PORT_OBJECT,WaitEvent);//偏移
    else
        LocalObjectType->DefaultObject = NULL;
    InitializeListHead(&LocalObjectType->TypeList);
    CreatorInfo = OBJECT_HEADER_TO_CREATOR_INFO(Header);
if (CreatorInfo) //将这个类型对象注册、加入全局链表中,注意这两个TypeList的含义是不同的
 InsertTailList(&ObpTypeObjectType->TypeList,&CreatorInfo->TypeList);
LocalObjectType->Index = ObpTypeObjectType->TotalNumberOfObjects;
//将这个类型对象加入全局数组中
    if (LocalObjectType->Index < 32)//对象类型较少,通常够用
        ObpObjectTypes[LocalObjectType->Index - 1] = LocalObjectType;
//将类型对象插入 \ObjectTypes 目录中(目录内部的指定hash链中)
bSucc=ObpInsertEntryDirectory(ObpTypeDirectoryObject, &Context, Header);
    if (bSucc)
    {
        ObpReleaseLookupContext(&Context);
        *ObjectType = LocalObjectType;
        return STATUS_SUCCESS;
}
Else
{
        ObpReleaseLookupContext(&Context);
        return STATUS_INSUFFICIENT_RESOURCES;
    }
}

  如上,大体的流程就是建立一个对象类型,而后加入对象目录中的 \ObjectTypes目录中。

 

  

内核中的对象管理器在初始化的时候,会初始化对象目录。先注册建立名为“Directory”、“SymbolicLink”的对象类型,而后在对象目录中建立根目录“\”,“\ObjectTypes”目录,“\DosDevices”目录等预约义目录。

内核中的IO管理器在初始化的时候,会注册建立名为“Device”、“File”、“Driver”等对象类型,因为对象类型自己也是一种有名字的对象,因此也会挂入对象目录中,位置分别为:

“\ObjectTypes\Device”

“\ObjectTypes\File”

“\ObjectTypes\Driver”

因而,咱们的驱动就能够建立对应类型的对象了。

 

符号连接、设备、文件这几类对象都提供了自定义的路径解析函数。由于这几种对象,对象后面的剩余路径并不在对象目录中,对象目录中的叶节点到这几种对象就是终点了。好比物理磁盘卷设备对象上的某一文件路径 “\Device\Harddisk0\Partition0\Dir1\Dir2\File.txt” 的解析过程是:

先:顺着对象目录中的根目录,按“\Device\Harddisk0\Partition0”这个路径解析到这一层,找到对应的卷设备对象

再:后面剩余的路径“Dir1\Dir2\File.txt”就由具体的文件系统去解析了,最终找到对应的文件对象

另外注意一下,文件对象在句柄关完后,将产生一个IRP_MJ_CLEANUP;文件对象在引用减到0后,销毁前将产生IRP_MJ_CLOSE。这就是这两个irp的产生时机。简单记忆【柄完清理,引完关闭】

相关文章
相关标签/搜索