zoukankan      html  css  js  c++  java
  • 内核对象

    0x01  对象  

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

      对于几乎所有的内核对象,windows都提供一个统一的操作模式,就是先通过系统调用打开或创建目标对象,让当前进程与目标对象之间建立起连接,然后再通过别的系统调用进行操作,最后通过系统调用关闭对象。实际上是关闭进程与目标对象的联系。

      常见的内核对象有:

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

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

      所有内核对象都遵循统一的操作模式:

      第一步:先创建对象;

      第二步:打开对象,得到句柄(可与第一步合并在一起,表示创建时就打开)

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

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

      第五步:句柄全部关完并且引用计数降到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,就指向对象目录树的根节点,根节点是一个根目录。 

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

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

        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”等对象类型,由于对象类型本身也是一种有名字的对象,所以也会挂入对象目录中,位置分别为:

    “ObjectTypesDevice”

    “ObjectTypesFile”

    “ObjectTypesDriver”

    于是,我们的驱动就可以创建对应类型的对象了。

     

    符号链接、设备、文件这几类对象都提供了自定义的路径解析函数。因为这几种对象,对象后面的剩余路径并不在对象目录中,对象目录中的叶节点到这几种对象就是终点了。比如物理磁盘卷设备对象上的某一文件路径 “DeviceHarddisk0Partition0Dir1Dir2File.txt” 的解析过程是:

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

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

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

  • 相关阅读:
    net中System.Security.Cryptography 命名空间 下的加密算法
    关于如何生成代码的帮助文档的链接
    Application.EnableVisualStyles();
    VS2010里属性窗口中的生成操作
    把普通的git库变成bare库
    MultiTouch camera controls source code
    android onTouch()与onTouchEvent()的区别
    iOS开发中常见的语句@synthesize obj = _obj 的意义详解
    java nio 快速read大文件
    使用ndk standalone工具链来编译某个平台下的库
  • 原文地址:https://www.cnblogs.com/lsh123/p/8330859.html
Copyright © 2011-2022 走看看