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