內核對象

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的產生時機。簡單記憶【柄完清理,引完關閉】

相關文章
相關標籤/搜索