[ZT]Inside WINDOWS NT Object Manager
Inside WINDOWS NT Object Manager
=====================================
Заточенное само в себе
Ласкаемое невинностью, убежище для твоего Я
Рождено однноким под частоколом саркастических небес
Одна любовь, одна жизнь, одна печаль...
(с) by Anathema
目錄
==========
Inside WINDOWS NT Object Manager
00."對象化的" Windows NT
01.Windows NT對象管理器
02.對象類型
03.Object Directory 與 SymbolicLink
04.對象的創建與對象的結構
05.Object Type 結構體
06.Дальнейшая жизнь объекта - простейший случай
07.句柄數據庫
08.對象的安全性
09.命名對象
0a.對象的消亡
0b.其它管理器函數
0c.結語
附錄
13.從用戶模式下獲取對象信息
14.某些與對象管理相關的系統服務
00."對象化的" Windows NT
=========================
如果您熟悉Windows NT體系結構,當然會知道在這個系統中資源都是以對象的形式存在的。這樣集中了對資源的管理。實際上,所有的Windows NT子系統無論如何都要使用資源,也就是說都要經過對象管理器。對象管理器是Windows NT的子系統,它對對象提供支持。對對象的使用貫穿了整個操作系統,真正的“對象化”的NT對應用程序隱藏了Win32子系統,甚至於部分的隱藏了系統調用接口。
通常,當提到NT的體系結構時,都會說到各種管理器(對象管理器、內存管理器等等)。所有這些管理器都是Windows NT內核(ntoskrnl.exe)的一部分。NT的內核主要使用C語言編寫的,每一個管理器本身都是一組函數,這些函數都被封裝在相應的模塊中。並不是所有的都定義了接口。管理器之間藉助於函數調用相互協作(在內核內部)。一部分函數從內核中導出的並公開了使用方法以在內核模式下使用。
對象管理器是一組形如ObXXXX的函數。實際上並不是所有的函數都是從內核中導出的。一般說來,對象管理器主要在內核中實現。例如,許多系統調用(NtCreateProcess、NtCreateEvent...)的處理都與這樣或是那樣的對象相聯繫。但是服務隱藏了對象管理器,不讓我們的程序知道。甚至於驅動程序的設計人員未必需要大量直接的與對象管理器打交道。儘管如此,瞭解內核是如何實際工作的無疑非常重要,更爲重要的是對象管理器還與安全管理器密切相關。理解了對象管理器就揭開了NT中許多起初並不明顯,但潛在具有的可能性……
01.Windows NT對象管理器
================================
對象管理器是NT內核中許多形如ObXXX的函數。某些函數並未導出並只在內核內部由子系統使用。對於在內核之外的使用,主要是通過未公開的函數。
ObAssignSecurity, ObCheckCreateObjectAccess , ObCheckObjectAccess,
ObCreateObject, ObDereferenceObject, ObFindHandleForObject,
ObGetObjectPointerCount, ObGetObjectSecurity, ObInsertObject,
ObMakeTemporaryObject, ObOpenObjectByName, ObOpenObjectByPointer,
ObQueryNameString, ObQueryObjectAuditingByHandle,
ObReferenceObjectByHandle, ObReferenceObjectByName,
ObReferenceObjectByPointer, ObReleaseObjectSecurity,
ObSetSecurityDescriptorInfo, ObfDereferenceObject, ObfReferenceObject
爲了描述對象管理器,我們先來看一下什麼是對象。
02.對象類型
================
對象管理器本身就工作在某些對象之上。其中之一就是object type。object type用於描述所有對象的共同屬性。object type由函數ObCreateObjectType(...)在內核初始化子系統的時候創建(也就是說是在系統初始化的時候)。我們所感興趣的並不是 ObCreateObjectType(...)函數,因爲其並未由內核導出。但是,在object type中的信息是非常重要的。關於這些我們少後再說。
大概的講,任何對象都可以分爲兩個部分:一部分包含管理上必需的信息(我們稱之爲首部),另一部分填充並確定創建該對象的子系統的必需信息(這是對象的 body)。對象管理器對首部進行操作,而實際上並不對對象的內容感興趣,還有一些對象是對象管理器本身使用的。object type正如其名字所示的那樣,定義了對象的類型。每一個對象都有對其object type的引用,對象管理器也會非常頻繁的使用對象的body。object type結構體的一個域是類型的名字。在NT下有以下各類型:
Type, Directory, SymbolicLink, Event, EventPair, Mutant, Semaphore,
Windows, Desktop, Timer, File, IoCompletion, Adapter, Controller, Device,
Driver, Key, Port, Section, Process, Thread, Token, Profile
Type類型的object type不足一懼。要知道扼要的講object type是用於管理器的對象,其和所有其它的對象是一樣的,也有自己的Type類型(它自己的Type類型對象)。如果好好的想一下的話,這樣做似乎也並不奇怪。下面,在Type這個話題下,主要來講幾個與object type相關的名詞。
03.Object Directory與SymbolicLink
===================================
所有列舉出的類型都是由不同的子系統創建的。我們現在討論Directory和SymbolicLink類型,因爲在對象管理器本身的工作中要用到這些類型的對象(這些類型是其在其自己初始化時創建的)。
NT的對象可以有名字,還可以組織成樹型的結構。Directory對象正是用來組織這種結構的。它的用途非常簡單——保存對其它對象的引用,例如,另外一個Directory對象。Directory的body的結構非常簡單:
typedef _DIR_ITEM{
PDIR_ITEM Next;
PVOID Object;
}DIR_ITEM,*PDIR_ITEM;
typedef struct _DIRECTORY{
PDIR_ITEM HashEntries[37];
PDIR_ITEM LastHashAccess; //94h
DWORD LastHashResult; //98h
}DIRECTORY,*PDIRECTORY;
路徑用於保存命名對象。路徑本身是37個entry的哈希表。表中的元素除保存着指向後面元素的指針之外,還保存着指向屬於該路徑對象的body的指針。哈希函數形式如下:
DWORD Hash(const char* str);
{
char *ptr=str;
int str_len=strlen(str);
char Sym;
int hash=0;
while(str_len--)
{
Sym=*ptr++;
ToUpperCase(&Sym);
Sym-=' ';
hash=hash*3+(hash>>1)+Sym;// умножение знаковое
}
return hash%37;
}
當然,這裏的代碼只是給出程序的邏輯,並不是真正的實現。這個例子主要是基於對ObpLookupDirectoryEntry(...)函數的分析寫出的。最後的兩個域反映出對哈希表的最後訪問。LastHashAccess是指向最後訪問的entry的指針。LastHashResult包含成功查找到的單元。NT想優化對路徑的查找,而且如果在查找的結果中找到的entries中的一個entry不在表的開頭,則這個表的元素就要被移到表的開頭(認爲後面對該對象的訪問的機率最大)。
Directory是對象,和其它的對象一樣,它也可以有名字。如果再考慮到對象還有對自己所在目錄的引用,則對可以畫出樹型的層級結構這個問題就能夠理解了。
在Windows NT中包含着根目錄ObpRootDirectoryObject,Windows NT的命名對象樹就從這裏開始。同時,還有ObpTypeDirectoryObject目錄,它包含所有的object type(使用這個樹是爲了防止object type重名)。內核表ObpObjectTypes中保存着指向object type的指針。再有,對於每一個類型的創建,子系統都保存着單獨的指針。
SymbolicLink這個object type用於保存字符串。在樹中進行查找時它們被用作引用。例如,對象/??/C:(這表示,在ObpRootDirectoryObject中有對路徑 “??”的引用,在這個路徑下包含着對象“C:”)就是SymbolicLink對象幷包含字符串“/Device/Harddisk0/ Partition1”。該對象格式如下:
typedef struct _SYMBOLIC_LINK{
LARGE_INTEGER CreationTime; // время создания от 1601 года
UNICODE_STRING Link;
}SYMBOLIC_LINK,*PSYMBOLIC_LINK;
現在有了對管理器用到的類型和對象的一般概念,我們可以來研究更爲具體的信息了。
04.對象的創建與對象的結構
===============================
對象由ObCreateObject函數創建。這個函數由ntoskrnl.exe導出,下面給出其原型:
NTSTATUS NTOSKRNL
ObCreateObject
(
KPROCESSOR_MODE bMode, // kernel / user
POBJECT_TYPE Type, // 對象類型
POBJECT_ATTRIBUTES Attributes, // 屬性
BOOLEAN bObjectMode, // kernel/user對象類型
DWORD Reserved, // 函數未使用
DWORD BodySize, // 對象body的大小
DWORD PagedPoolQuota OPTIONAL, // 如果爲0
DWORD NonPagedPoolQuota OPTIONAL,// 則回收
PVOID* pObjectBody // 指向body的指針
);
參數Reserved可以簡單的忽略。bObjectMode定義了是否要從用戶模式訪問。PagedPollQuota和 NonPagedPollQuota是與這些對象相關聯的分頁池和非分頁池的限額。限額記錄了進程每一次其打開句柄的情況。進程有一個限額的閾值,其不能超越。POBJECT_ATTRIBUTES結構體是公開的(ntdef.h中有),但我這裏還要將其給出,因爲後面會經常引用其中的域。
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES;
typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;
現在來集中討論重要的域RootDirectory和ObjectName。RootDirectory是什麼,現在我想已經能猜到了——將要包含對象(如果需要)的目錄。ObjectName——見名知意。順便說一下,可以不指明RootDirectory,但在ObjectName中要指定對象的完整路徑,這時對象將在起始於ObpRootDirectoryObject的樹中創建。
關於安全方面的話我們單獨講,所以Security域嘛……暫時留下不提。剩下Attributes域。ntdef.h中描述了各位的意義。下面給出這些屬性。
#define OBJ_INHERIT 0x00000002L
#define OBJ_PERMANENT 0x00000010L
#define OBJ_EXCLUSIVE 0x00000020L
#define OBJ_CASE_INSENSITIVE 0x00000040L
#define OBJ_OPENIF 0x00000080L
#define OBJ_OPENLINK 0x00000100L
結果,函數返回指向對象body的指針。
到了描述對象結構的時候啦。
+---------------+<------------------------------> -??h
| |
| ........ | --長度可變的結構體 (到 30h)
| |
+---------------+<--對象首部--------------> 00h
| |
| Header | --標準的首部18h字節
| |
+---------------+<--對象body----------------> 18h
| |
| |
| Body | - тело объекта желаемого размера
| |
+---------------+<------------------------------> 18h+BodySize
管理器經常要操作指向對象首部的指針。它返回給我們的是指向body的指針。首部之上還有一塊區域,這塊區域長度可變並依賴於所建對象的參數和類型。這個“帽子”可以保存下面這個結構體:
typedef struct _POLLED_QUOTA // 如果限額不等於default則增加
{ // 保存限額的消耗量
DWORD PagedPoolQuota;
DWORD NonPagedPoolQuota;
DWORD QuotaInformationSize; // PagedPollQuota的總和
PPROCESS_OBJECT pProcess; // 擁有此對象的進程
}POLLED_QUOTA;
typedef struct _HANDLE_DB // 關於打開句柄的信息
{
union {
PPROCESS_OBJECT pProcess;// 如果只有一個進程打開了
// 指針
PVOID HandlesDBInfo; //-------------+--------
}HanInfo; // |dd Num;2
// +-----------
// |dd pProcess1
// |dd Counter2
// +----------
// |dd pProcess2
// |dd Counter2
// +-----------
// |....
DWORD Counter; // 句柄總數.
}HANDLE_DB;
typedef struct _OBJECT_NAME
{
PDIRECTORY Directory; // 對象所屬的路徑
UNICODE_STRING ObjectName; //對象名
DWORD Reserved; //對齊
}OBJECT_NAME;
typedef struct _CREATOR_INFO
{
LIST_ENTRY Creator; // 閉合的鏈表 (包含着類型)
DWORD UniqueProcessId;// 對象父進程的ID
DWORD Reserved; // 對齊
}CREATOR_INFO;
這個或是其它的結構體可以存在,也可以不存在,但順序不能變。在註釋中寫明瞭這些結構體都是作什麼用的了。它們中最後一個(哪怕只有一個)的後面就是標準的對象首部了。
typedef struct _OBJECT_HEADER
{
DWORD RefCounter; // 對象的引用計數 00
DWORD HandleCounter; // 句柄數目 04
POBJECT_TYPE ObjectType; // 對象類型 08
DWORD SubHeaderInfo; // 後面會講 0c
UNION // 10
{
POBJECT_INFO ObjectInfo;
PQUOTA_BLOCK pQuotaBlock;
} a;
PSECURITY_DESCRIPTOR SecurityDescriptor; // 14 Optional
} OBJECT_HEADER;
先不講這個聯合體,以後再說。其它域的作用都很顯然。對象管理器將統計打開的對象句柄以及對對象的引用,以此來知道何時刪除對象的名字和body(如果對象不是permanent的,也就是說沒有設置OBJ_PERMANENT位)。還有對對象類型的引用和有關安全的信息。首部前面的結構體中的域在 SubHeaderInfo域中也有體現。
SubHeaderInfo中各個位的含義如下:
3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+---------------+-------------+---------------+-----------------+
|U|H|S|P|E|I|M|Q|Quota offset |HandleDB offset| Name Offset |
++-+-+-+-+-+-+-++-----+-------+------+--------+-------+---------+
| | | | | | | | | | |
| | | | | | | | | | OBJECT_NAME距首部起始的正偏移(向上)
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | HANDLE_DB距首部起始的正偏移(向上)
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | POLLED_QUOTA距首部起始的正偏移(向上)
| | | | | | | |
| | | | | | | |
| | | | | | | +- QuotaBlock/ObjectInfo (Quota charged)
| | | | | | +-- 對象的User/Kernel模式
| | | | | +---- 是否有CREATOR_INFO
| | | | +------ 對應於位OBJ_EXCLUSIVE
| | | +-------- 對應於位OBJ_PERMANENT.
| | | 用於該對象的創建,必須對應相應的權限
| | |
| | +---------- 存在SecurityDescriptor
| +------------ HandleInfo未初始化
+-------------- Unused/Reserved
*在SubHeaderInfo沒有CREATOR_INFO的字節偏移,但因爲這個結構體(如果有)總是最後一個,故指向它的指針可以獲得,只需從首部指針中減去sizeof(CREATOR_INFO)就可以了。
當我第一次開始反彙編對象管理器函數並看到ObCreateObject(...)的時候,我斷定這個函數做了使對象開始其“生活”的所有必需的工作。但是一個普遍的原則——直到最後再下定論(很好的原則),在這裏也成立。實際上,對於前面描述的各域,這個函數只填充了結構體的最低限度的域。倒不如說這個函數在內存中把對象折騰了一番,捎帶腳兒地填充了幾個域。
再詳細些。SubHeaderInfo中的位:Q=1 總是,S=0 總是,H=1 如果存在HANDLE_DB。OBJECT_NAME中 Directory = NULL。CREATOR_INFO中的LIST_ENTRY指向自己。POLLED_QUOTA中 pProcess = NULL。HANDLE_DB清零。HandleCounter = 0; RefCounter = 1; 其餘的域以相應的information entries和對象類型中的信息來填充。除此之外,分配並填充OBJECT_INFO結構體,首部的UNION {..}a中的相應指針(達,當然所有的內存都是從NonPaged pool裏分配的)。
typedef struct _OBJECT_INFO{
DWORD Attributes; //00h OBJECT_ATTRIBUTES.Attributes
HANDLE RootDirectory; //04h
DWORD Reserved; //08h - Unknown or Res.
KPROCESSOR_MODE bMode; //0ch
BYTE Reserved[3]; //0dh - Alignment
DWORD PagedPoolQuota; //10h
DWORD NonPagedPoolQuota; //14h
DWORD QotaInformationSize;//18h - 組SID的大小
//+ DACL的大小(圓整後)
PSECURITY_DESCRIPTOR SelfRelSecDescriptor;
//1ch - 指向Self Relativ的指針.
//Non Paed Pool裏的安全描述符
PSECURITY_QUALITY_OF_SERVICE pSecQual; //20h
SECURITY_QUALITY_OF_SERVICE SecQuality; //24h
//30h
} OBJECT_INFO,*POBJECT_INFO;
順便說一句,系統爲OBJECT_INFO結構體維護了一個叫ObpCreateInfoLookasideList的Lookaside list(見DDK)。實際上Reserved域有時也會用到(在對象方法中作參數),但我尚未碰到過這個域的值不爲0的情況。 QuotaInformationSize用在從Paged和NonPaged pool中移除限額用的。
05.Object Type 結構體
=========================
這裏我只描述一下object type的body的結構。
typedef struct _OBJECT_TYPE
{
ERESOURCE TypeAsResource; //0x0 可用作資源
//34h
PLIST_ENTRY FirstCreatorInfo;//38h 我注意到了這個結構體
PLIST_ENTRY LastCreatorInfo; //3ch 只是用於object type
UNICODE_STRING TypeName; //40h 類型名
DWORD Unknown2[2]; //48h
DWORD RefCount; //50h 該類型對象的計數
DWORD HanCount; //54h 該類型句柄的計數
DWORD PeakRef; //58h 對象的峯值
DWORD PeakHandles; //5ch 句柄的峯值
DWORD Unknown3; //60h
DWORD AllowedAttributeMask;//64h 可能的屬性 0 - 允許所有的
GENERIC_MAPPING GenericMapping;//68 отображение родовых прав на специальные
DWORD AllowedAccessMask; //78h (ACCESS_SYSTEM_SECURITY 總是設置的)
BOOLEAN bInNameSpace; //7ch 這個類型的對象在對象路徑中
// 可能我會弄錯, 但也類似.
BOOLEAN bHandleDB; //7dh 是否包含對象句柄的信息(HANDLE_DB)
BOOLEAN bCreatorInfo; //7eh ----//---- CreatorInfo + 38h處的鏈表
BOOLEAN Unknown5; //7fh
DWORD Unknown6; //80h 如果 !=0 則在NpSuccess裏創建
DWORD PagedPoollQuota; //84h default
DWORD NonPagedPollQuota; //88h 限額
PVOID DumpProcedure; //8ch 原型未知 (?)
PVOID OpenProcedure; //90h 原型已知
PVOID CloseProcedure; //94h 原型已知
PVOID DeleteProcedure; //98h 原型已知
PVOID ParseProcedure; //9ch 原型已知
PVOID SecurityProcedure; //a0h 原型已知
// 可以有4種調用情況:
//0-set sec_info, 1-query descriptor, 2-delete, 3-assign
PVOID QueryNameProcedure; //a4h 原型已知
PVOID Tag; //a8h 通過高層次信息判斷
// 這應該是方法OkayToCloseProcedure;
// 實際上, 對於所有的對象我都發現在這個地址上有四字符的類型Tag,例如Dire (Directory)
} OBJECT_TYPE,*POBJECT_TYPE;
我就不給出已知方法的原型了,因爲這裏不大用得到。於是,類型種包含了類型名,操作對象的函數,還有屬於該類型的用於對象的一般信息。結構體的域都已有註釋,不再細說。
我們將視線轉向偏移0處的ERESOURCE結構體(可以在NTDDK.H中找到)。換句話說,這個對象可以用作資源。對象管理器使用 ExAcquireResourceExclusiveLite函數(見DDK)從object type中取得資源。PagedPollQuota和NonPagedPollQuota將限額指定爲默認值。如果我們使用限額值爲零來調用 ObCreateObject函數(或與默認的限額值相同)而且如果沒有設置OBJ_EXCLUSIVE且如果QuotaInformationSize 小於0x800,則在創建對象時將不會創建POLLED_QUOTA結構體。AllowedAttributeMask和 AllowedAccessMask域指定了屬性的掩碼和允許的訪問。最後有指向方法的指針,這些方法在定義的時候調用。例如,在增加句柄的時候,在這個進程句柄的基址裏調用OpenProcedure函數。這並不是說這些域就是我們想象的那樣(以及我從Хелен Касер(譯註:我想是Helen Custer)的關於WIndows NT的書中讀到的那樣)是必需的。但是它們能夠擴展對象的功能。下面我來講調用某些方法的條件。
06.Дальнейшая жизнь объекта - простейший случай
===============================================
ObCreateObject創建對象,但沒有什麼該對象本質上的東西。沒有它的句柄,不能用名字打開它(ObCreateObject函數並不向對象樹中添加對象)。我們回來看一下對象管理器導出的函數。下面的三個函數很令人好奇:ObOpenObjectByName, ObOpenObjectByPointer,ObInsertObject。第一個函數先不說。那是說第二個呢還是第三個? ObOpenObjectByPointer自然應該講,可是ObInsertObject平時更少被提到,所以我們從它開始。
NTSTATUS NTOSKRNL
ObInsertObject(
PVOID pObject, //body
PACCESS_STATE pAccessState OPTIONAL,
ACCESS_MASK Access,
DWORD RefCounterDelta OPTIONAL, //0- default (т.е. 1)
PVOID OUT *ObjectExist OPTIONAL, //如果已經存在
PHANDLE OUT Handle //句柄
);
函數的邏輯十分明顯。它從填充好的先前的OBJECT_INFO結構體體得到主要的信息是其即將用到的從對象首部中獲得的一個指針(指向對象首部的指針可以簡單的用body指針減去18h)。函數的工作方式有兩種:一,對象沒有名字且沒有在類型中設置bInNameSpace標誌;二,設置了該標誌或是有名字。我們來看這兩種情況。
如果對象沒有名字,則調用ObpCreateUnnamedHandle(..)。後面,我將引用一個內部函數而不給出其原型,其原型是已知的。這個函數本身是以許多內部函數爲基礎的,這些內部函數共同完成了該函數的工作。
當前進程記錄了限額。在這種情況下,填充a.pQuotaBlock域並清SubHeaderInfo中的Q位。現在對象“填充好了”限額。 a.pQuotaBlock指向與每一個進程相聯繫的QUOTA_BLOCK結構體。我們現在先不研究對象-進程的結構,我想下面這個結構體值得一提。
typedef _QUOTA_BLOCK{
DWORD KSPIN_LOCK QuotaLock;
DWORD RefCounter; // 用於計數該block的進程
DWORD PeakNonPagedPoolUsage;
DWORD PeakPagedPoolUsage;
DWORD NonPagedpoolUsage;
DWORD PagedPoolUsage;
DWORD NonPagedPoolLimit;
DWORD PagedPoolLimit;
DWORD PeakPagefileUsage;
DWORD PagefileUsage;
DWORD PageFileLimit;
}QUOTA_BLOCK,*PQUOTA_BLOCK;
我想各域的名字已經很清楚的說明了其用途。行,我們接着往下進行。增加句柄的數目並恢復HANDLE_DB中的信息,如果有的話。HANDLE_DB本身是個結構體,保存了對打開對象的各進程的引用。它爲每一個進程都維護了一個計數器。在第一遍中將HanInfo與此結構體相聯結顯示了對“懶惰”原則的遵守。如果對象只是從一個進程中打開,則聯結本身就是指向這個進程的指針。不可命名的對象可以是exclusive的(屬性中的OBJ_EXCLUSIVE 位)。這樣控制了只能從一個與POLLED_QUOTA中的域對應的進程中打開對象。我們記得,這個結構體總是存在於exclusive的對象中。這樣,進程就獨自佔據了對象——如此就沒有了控制訪問的問題。合情合理的,對象獨佔的句柄能夠繼承(OBJ_INHERIT),並被管理器檢查。如果 CREATOR_INFO是必需的信息,則要有CREATOR_INFO鏈表,object type有相應的域指向它。object type本身也在這個鏈中。實際上,我發現bCreatorInfo位只在類型對象“Type”中設置。這表明鏈表只用於對象類型。對於類型已定的對象,則不含此鏈表。好了,只剩下描述ObpIncrementUnnamedHandleCount(..)函數的主要內容的邏輯了。在該函數執行時還會調用 OpenProcedure(如果有)。
當上述操作完成時,會使首部中的計數器RefCounter增加RefCounterDelta+1。最後,使用ExCreateHandle創建句柄。在成功創建句柄後,就離開相應的OBJECT_INFO結構體,這個結構體役期已滿,不再需要……
07.句柄數據庫
======================
進程(也是個對象,這個以後再說)有個與對象相聯繫的句柄數據庫。知道了對象的句柄就可以很容易的對對象進行訪問,而同時也出現了繼承和複製的問題。我們來詳細研究一下句柄數據庫。
我們不在'thread'和'process'對象的具體格式上做停頓,演示如何得到指向句柄數據庫的指針(自然,要在內核模式下)。
mov eax,large fs:124h ; Got current thread body pointer
mov eax,[eax+44h] ; Got current process body pointer
mov ecx,[eax+104h] ; Got handle_db header pointer !
數據庫的結構可以分成兩部分——header和body(實際上就是數據庫本身)。header的形式如下:
typedef struct _HANDLE_DB_HEADER{
WORD Unknown0[2]; //0x0 - эти поля всегда были 0
DWORD Unknown1; //0x4 - -----..-----
SPIN_LOCK SpinLock; //0x8
PVOID Head; //0xc
PVOID Tail; //0x10
DWORD HandleCount; //0x14
PPROCESS pProcess; //0x18
DWORD ProcessUniqueId;//0x1c
WORD GrowDelta; //0x20
LIST_ENTRY HandleTableList; //0x24 系統有個句柄數據庫鏈表. 鏈表的entry - HandleTableListHead
KEVENT EventObj; //0x2c
KSEMAPHORE SemaphoreObj; //0x3c
}HANDLE_DB_HEADER,*PHANDLE_DB_HEADER;
數據庫本身形式如下:
+-------------+
+----------|+----------- |-+---------------------------- -------------+
+->NullEntry>+|Han0|Han1|..+>| LIST_ENTRY<|>LIST_ENTRY>|.. |>LIST_ENTRY<|-+
| +-----------+------------ +-------------------------- ---------------+ |
+--------------------------------------------------------------------------+
[Head] [ Handles ] [ Free spaces for handles ] [Tail]
DB首部中的Head指向數據庫的起始位置,Tail指向末尾。數據庫初始化時,數據庫的全部內存都填充爲循環鏈表(除鏈表外沒別的東西,也就是說每一個元素都是LIST_ENTRY)。並且鏈表是從左到右依次建立的。當向數據庫中添加句柄時,句柄被加在NullEntry之後。head有指向鏈表中空閒位置的指針,句柄就在此處添加。當沒有空閒位置時(鏈表的head指向了自己),就將數據庫擴展GrowDelta個位置。當然,在句柄數據庫工作時會產生相應的限額的“裝填”。所有這些都很簡單……
***************************************************************************
注意!所有上述的信息主要是針對Windows NT 4.0的。在Windows NT 2K下句柄數據庫的結構變了樣子。現在這個表是三級的。
Process Top Level
+-------+ pointers
| | Middle level
|-------| 0 pointers Subhandle
|Handle |---->+------+ 0 table
| Table | | |--->+------+ 0
|-------| |------| | |---->+------+
| | |------| | |
| | |------|
| | | |
| | 255+------+ | |
+-------+ 255+------+ | |
255+------+
現在沒有必需改造的句柄表了。不用再創建一個指針塊並添加子句柄(subhandle)表了。
這樣,OBJECT_HANDLE結構體(見下面)沒有改變。只是指針中的第31位用作了繁忙或加鎖的指示器,通常情況下可拋開(因爲所有的指針都屬於內核空間,第31位不會改變指針指向——所有的地址都屬於高於0x80000000的地址空間)。我給出某些內部函數的僞碼,來說明NT5.0下句柄數據庫的運作。
ExMapHandleToPointer(Table,Handle)
{
TableEntry=ExpLookupHandleTableEntry(Table,Handle);
if(!TableEntry)return 0;
return ExLockHandleTableEntry(Table,TableEntry);
}
ExpLookupHandleTableEntry(Table,Handle)
{
level1=(Handle>>18) & 0xff;
level2=(Handle>>10) & 0xff;
level3=(Handle>>2) & 0xff;
if(Handle&0xfc000000)return 0;
pPtr=Table->0x8;
pPtr=pPtr+level1*4;
if(pPtr)
{
pPtr=pPtr+level2*4;
if(pPtr)
{
return pPtr+level3*8;
}
}
return 0;
}
ExLockHandleTableEntry(Table,TableEntry)
// Table not used
{
ObjectPointer = TableEntry->0x00;
if (!ObjectPointer) return 0;
if (ObjectPointer > 0)
{
NewObjectPointer = ObjectPointer | 0x80000000;
//xmpxchg NewObjectPointer,ObjectPointer in ObjectPointer
}
else {
// wait logic
}
return 1;
}
**************************************************************************
爲了完整,我給出用於句柄數據庫的主要內部函數的原型。
用於創建數據庫的基本函數:
NTSTATUS ExCreateHandleTable(
PPROCESS Process,
WORD Size, //Optional
WORD GrownDelta //Optional
);
這個函數用於數據庫的擴展:
NTSTATUS ExpAllocateHandleTableEntries(
PHANDLE_DB_HEADER HandleDB,
PLIST_ENTRY Head,
DWORD OldSize, // 在元素中
DWORD NewSize
);
更高層的函數:
NTSTATUS ObInitProcess(
PPROCESS pFatherProcess, //如果!=0 - 數據庫是複製的.
//否則 - 是創建的
//包含audit
PPROCESS pProcess
)
對象句柄本身是怎樣的結構呢?看這裏:
typedef struct _OBJECT_HANDLE{
PVOID ObjectHeaderPointer,
ACCESS_MASK GrantedAccess
}OBJECT_HANDLE,*POBJECT_HANDLE;
正如我們看到的——句柄只是簡單的一個指向對象首部的指針,再加上一個訪問掩碼。是的,ObjectHeaderPointer還保存了某些信息。問題在於所有對象的首部的地址都是8字節對齊的。這樣,對於對對象的訪問只需使用ObjectHeaderPointer的位[31:3] 。低三位是OBJECT_HANDLE_INFORMATION結構體中的HandleAttributes(見DDK中的 ObReferenceObjectByHandle函數)。各個位的值:0x01 - protect from close,0x02 - inherit,0x04 - generate on close。因此,使用句柄能進行有效的尋址。返回給用戶的句柄值,左移兩位後等於OBJECT_HANDLE結構體在數據庫中的序號。在ntdef.h中,OBJ_HANDLE_TAGBITS定義的附近可以看到以下幾行:
//
// Low order two bits of a handle are ignored by the system and available
// for use by application code as tag bits. The remaining bits are opaque
// and used to store a serial number and table index.
//
怎麼樣,全明白了吧。
如果您還沒忘的話,我們只是分析過ObInsertObject的最簡單的情況,即非命名對象的情況。如果一定要將對象要放入命名樹中會怎樣呢?簡單講就是對命名樹進行操作,最終將對象放入目錄中,這樣就可以用名字打開對象了。如果這個目錄是系統樹的根目錄,則這個對象就對所有進程可見。如果這個目錄是某個進程的——則只對該進程可見(通常所有這樣的對象都在系統樹中創建,因爲大多數系統服務都使用系統樹)。對此,爲了展示進程的完整格局,我們先來分析清楚某些問題……
08.對象的安全性
========================
之前,我一直都在迴避這個問題,因爲在前面不涉及這方面的東西是可以的。但是,要向下繼續的話,就要知道一些了。幸運的是,這裏要講的大多數結構體都在這個或那個頭文件中有描述,是公開的。但是,我覺得將這些必需的信息結合在一起是合乎情理的,因爲下面我將經常引用這些結構體。有時,我會講到一些未公開的東西。在創建時,帳戶記錄生成一個隨機的SID,這樣就保證了SID的隨機性。
爲了識別認證用戶和組,安全系統使用了SID(Security IDentifier)。
typedef struct _SID_IDENTIFIER_AUTHORITY {
BYTE Value[6];
} SID_IDENTIFIER_AUTHORITY, *PSID_IDENTIFIER_AUTHORITY;
typedef struct _SID {
BYTE Revision;
BYTE SubAuthorityCount;
SID_IDENTIFIER_AUTHORITY IdentifierAuthority;
DWORD SubAuthority[ANYSIZE_ARRAY];
} SID, *PISID;
當前Revision的值爲1。SubAuthorityCount的最大值爲15。SubAuthority域最後一個值爲RID(Reliativ. Id.)。從文本的角度看,NT的SID的形式爲S-1-A-SA1-SA2-...-RID。其中S-1表示SID rev.1,A - IdentifierAuthority,SA? - SubAuthority,RID - SubAuthority的最後一項。在NT中有標識的預定義,同時也有RID值的預定義。例如,RID等於500表示帳戶administrator。詳細信息可參考WINNT.H。
用戶可被授予某種特權級SeXXXPrivilege,每一個特權級在系統中都是8位數字的LUID(Local Unique Id.)。起初,每一個特權級都是一個文本字符串(特權級的文本表示可以參見WINNT.H)。
這個信息不依賴於環境。在用戶進入系統時,LSA(Local Security Administrator)創建訪問令牌(TOKEN),訪問令牌包含着用於安全檢查的重要信息。該結構體形式如下:
typedef struct _ACCESS_TOKEN{
char SourceName[8]; //0 Source of token'a
LUID SourceIdentifier; //8
LUID TokenId; //10
LUID AuthenticationId;//18
LARGE_INTEGER ExpirationTime; //20
LUID ModifiedId; //28 修改token'a時改變
DWORD NumberOfUserAndGroupSids;//30
DWORD NumberOfPrivileges //34
DWORD Unknown; //38
DWORD DynamicCharged; //3c
DWORD DynamicAvailable; //40
DWORD NumberOfOwnerSid //44
PVOID SidDB; //48
PSID PrimaryGroup; //4c
PLUID_AND_ATTRIBUTES TokenPrivileges; //50
DWORD Unknown1; //54
PACL DefaultDacl; //58
TOKEN_TYPE TokenType; //5c 原始的還是
// IMPERSONATION
SECURITY_IMPERSONATION_LEVEL ImpLevel; //60 IMPERSONATION
DWORD UnknownFlag; //64
DWORD Tail[ANYSIZE_ARRAY]; //????
}ACCESS_TOKEN,*PACCESS_TOKEN;
數據庫SidDB大約是下面這個樣子:
+-TokenUser----+
|00 PSID |
|04 Attributes |
+-TokenGroups--+
|.... |
| |
| |
+--------------+
<--NumberOfUserAndGroupSids
+-OwnerSid-----+
| |<--NumberOfOwnerSid
+--------------+
|....
我再給出與之相應的未公開的函數,其用於取得'Token'對象。
PACCESS_TOKEN NTOSKRNL PsReferenceImpersonationToken(
KTHREAD * Thread, //IN
PBOOLEAN* CopyOnOpen, //OUT
PBOOLEAN* EffectiveOnly, //OUT
SECURITY_IMPERSONATION_LEVEL* ImpersonationLevel //OUT
);
PACCESS_TOKEN NTOSKRNL PsReferencePrimaryToken(KPROCESS* Process);
我就不再講訪問令牌結構體都保存了那些信息了(現在還沒到講安全描述符的時候)。同時我也不會解釋Impersonation的機制。指向TOKEN的指針放在'Process'對象body的偏移108h處。
當使用ObCreateObject(..)創建對象時,所穿參數實際上是指向OBJECT_ATTRIBUTES的指針,在其中有SecurityDescriptor和SecurityQualityOfService域——指向公開的結構體的指針。
SecurityQualityOfService包含關於服務客戶的Impersonation Level的信息,我們就不多說了。
SecurityDescriptor描述了對象的安全性——這是需要說的。
typedef struct _SECURITY_DESCRIPTOR {
BYTE Revision;
BYTE Sbz1;
SECURITY_DESCRIPTOR_CONTROL Control;
PSID Owner;
PSID Group;
PACL Sacl;
PACL Dacl;
} SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;
Control指定了不同的標誌,其中一個指示描述符Self Relative(就是說其中所有的指針都是相對於結構體首部的)。詳細描述見WINNT.H。域Owner和Group的值的意義是顯然的。Dacl ——枚舉用戶和組的鏈表,通過其來允許或是禁止對對象的訪問。Sacl與audit密切相關。
與ACL有關的結構體在WINNT.H中都有很好的描述。所以,描述用於各種用戶的對象訪問權限的信息與對象密切相聯(在對象首部中有指向安全描述符的指針)。
在取得對對象的訪問權時(如ObInsertObject)會被賦予ACCESS_MASK。通常,這時要使用相應的類型定義STANDARD_RIGHTS_READ等等。現在,重要的是來看一下這個信息,該信息的位結構體是下面這個樣子:
3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+---------------+---------------+-------------------------------+
|G|G|G|G|Res'd|A| StandardRights| SpecificRights |
|R|W|E|A| |S| | |
++-+-+-+-------++------+--------+--------------+----------------+
| | | | | | |
| | | | | | SpecificRights;
| | | | | StandardRights;
| | | | AccessSystemAcl
| | | +GenericAll
| | +--GenericExecute
| +----GenericWrite
+------GenericRead
Generic類的位是爲了方便程序員(都是標準的而且很簡單)。實際上,在對象類型結構體中有GenericMapping域,該域包含着關於在 SpecificRights(特殊權限——此位的含義依賴於對象類型)中轉換Generic位的信息。StandardRights,從名字就可看出,它對於所有的對象有相同的含義。對象句柄有個GrantedAccess域,這樣,在使用句柄時不必總是解析安全描述符,這裏的信息就足夠了。除此之外,對象類型還包含着AllowedAccessMask,這樣就可以控制對於對象類型的上下文所進行的可能的訪問。
在內部,NT使用了下面這樣一些結構體,如ACCESS_STATE。
typedef struct _ACCESS_STATE {
LUID OperationID;
BOOLEAN SecurityEvaluated;
BOOLEAN GenerateAudit;
BOOLEAN GenerateOnClose;
BOOLEAN PrivilegesAllocated;
ULONG Flags;
ACCESS_MASK RemainingDesiredAccess;
ACCESS_MASK PreviouslyGrantedAccess;
ACCESS_MASK OriginalDesiredAccess;
SECURITY_SUBJECT_CONTEXT SubjectSecurityContext;
PSECURITY_DESCRIPTOR SecurityDescriptor;
PVOID AuxData;
union {
INITIAL_PRIVILEGE_SET InitialPrivilegeSet;
PRIVILEGE_SET PrivilegeSet;
} Privileges;
BOOLEAN AuditPrivileges;
UNICODE_STRING ObjectName;
UNICODE_STRING ObjectTypeName;
} ACCESS_STATE, *PACCESS_STATE;
typedef struct _SECURITY_SUBJECT_CONTEXT {
PACCESS_TOKEN ClientToken;
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
PACCESS_TOKEN PrimaryToken;
PVOID ProcessAuditId;
} SECURITY_SUBJECT_CONTEXT, *PSECURITY_SUBJECT_CONTEXT;
正像我們看到的,這個結構體在其中結合了在訪問對象時所有用於控制對象訪問的必需的信息。我給出幾個與之相關的未公開函數。
// 填充ACCESS_STATE結構體
// 留下了一個關於優先級和對象的信息未填充
NTSTATUS NTOSKRNL SeCreateAccessState(
PACCESS_STATE AccessState,
PVOID AuxData,
ACCESS_MASK Access,
PGENERIC_MAPPING GenericMapping
);
NTSTATUS NTOSKRNL SeDeleteAccessState(PACCESS_STATE AccessState);
// 填充SUBJECT_CONTEXT, 用在SeCreateAccessState
void NTOSKRNL SeCaptureSubjectContext(
PSUBJECT_CONTEXT SubjectContext
);
在DDK中同時描述了ObGetObjectSecurity和ObReleaseObjectSecurity函數。其中第一個函數使用代號1調用了方法SecurityProcedure(參見對象類型結構體的格式)。如果有安全描述符,則這個方法負責收回。否則就通過對象首部來取得描述符。
09.命名對象
==========================
我們來研究ObInsertObject的第二個分支,該分支用於處理命名對象。如果對象是未命名的或是exclusive的,則其不需要安全描述符。否則可以通過名字訪問對象。
在開始部分,函數創建ACCESS_STATE結構體並在以OBJECT_INFO.RootDirectory(這個結構體當時是存在的)爲根在樹中查找這個名字。查找是通過ObpLookupObjectName函數進行的。其工作邏輯十分顯然——解析相應對象的Directory和 SymbolicLink。一般ObpLookupObjectName不只用於查找,還用於在樹中創建對象。在選擇路徑時同時要用安全檢查點(ObpCheckTraverseAccess函數)在樹中驗證對象的訪問權。到目前,暫時還沒有結束對路徑的解析或是在路徑中沒有遇到對象管理器不知道的對象(這叫做另一類的名字空間)。這時調用方法ParseProcedure(如果有的話),其中的一個參數是指向未被選中的路徑的指針。下面的分析要用到這個方法。使用Parse方法可以查找整個名字空間,但對象可能只在其“親本”的樹中。ParseProcedure方法只用於查找並在在樹中創建對象時被忽略(如果它沒有指向默認的函數ObpParseSymbolicLink)。但符號鏈接對於對象描述符來說是自有的,而且總是可以被處理的)。如果發生了名字衝突(對象已存在),則ObInsertObject的進一步的動作就依賴於OBJ_OPENIF標誌。這個標誌可被譯爲open if exist。其它的詞將打開現有的對象。一般情況下正常的控制流在對象創建的最後會調用未公開函數ObAssignSecurity(其“逆”函數 ObGetObjectSecurity是公開的)。這個函數被封裝在了公開函數SeAssignSecurity(...)中。
BOOLEAN NTOSKRNL ObAssignSecurity{
PACCESS_STATE AccessState,
PSECURITY_DESCRIPTOR OPTIONAL OldSecurityDescriptor,
PVOID Object,
POBJECT_TYPE Type);
參數SecurityDecriptor只用作傳遞給SecurityProcedure方法的參數,函數用代號3來調用此方法。所有的必需的安全管理信息都在ACCESS_STATE結構體中。在該函數完成後,對象首部中的SecurityDescriptor域就被填充好(對於非命名對象則無此項工作)。然後調用內部函數ObpCreateHandle。儘管該函數未被導出,但是爲了比較函數ObpCreateHandle和 ObpCreateUnnamedHandle,我在這裏給出其原型。
NTSTATUS ObpCreateHandle(
DWORD SecurityMode, // 通常==1, 訪問在這裏被檢查
PVOID Object,
POBJECT_TYPE Type,
PACCESS_STATE AccessState,
DWORD RefCounterDelta,
DWORD Attributes,
DWORD OPTIONAL UnknErrMode,
KPROCESSOR_MODE bMode,
PVOID Object,
PHANDLE Handle
);
NTSTATUS ObpCreateUnnamedHandle(
PVOID Object,
ACCESS_MASK Access,
DWORD RefCounterDelta,
DWORD Attributes,
KPROCESSOR_MODE bMode,
PVOID Object,
PHANDLE Handle
);
兩個函數做的工作實際上是相同的,最終都要調用ExCreateHandle(..)。但是,從原型中可以直觀的看出,第一個函數處理的是安全描述符,創建句柄的過程我已經描述過,我們都已經知道了。最後,OBJECT_INFO結構體被釋放。我已經說過這些結構體都是分配在LookasideList中的,現在明白是爲什麼了吧。在系統中經常會對象被創建和刪除,對OBJECT_INFO的創建工作會大量進行(這些結構體從對象被創建起就存在於每一個對象中)。LookasideList專門用於優化這類經常用到的內存分配/回收操作(見DDK)。
0a.對象的消亡
=================
如果對象管理器不再需要某對象,或對象不是permanent(OBJ_PERMANENT位)的,則管理器就將對象刪除。這裏對象的消亡分爲兩步。在首部中有兩個域,RefCounter和HandleCounter。在句柄關閉時(ZwClose)會依次調用ExDeleteHandle和 ObpDecrementHandleCount。第一個函數只是從句柄數據庫中刪除句柄(釋放的空間被返回給鏈表的空閒位置)。 ObpDecrementHandleCount包含着關於句柄打開數量的信息,同時保存着句柄對象數據庫的信息(HANDLE_DB)。除此之外,在這些函數中還要調用方法CloseProcedure。如果關閉的是最後一個句柄且對象不是permanent的,則調用 ObpDeleteNameCheck函數,該函數從對象名樹中將有關該對象的信息刪除。這時,以代號delete調用方法 SecurityProcedure。但是對象繼續存在——它只是不再可見。
除此之外,在首部中還有對對象的引用計數域,這個域通過ObDereferenceObject函數來遞減。(在遞增/遞減句柄的引用計數時該域也同時遞增/遞減,但可以修改對象的引用計數而不改變句柄的數量)。如果引用計數值達到了零,則一般會在刪除隊列中添加這個對象(這要依情況而定,對象可以被立即刪除)。這時,RefCount和HandleCount就扮演了LIST_ENTRY的角色。對於這些域,我沒有在對象首部的格式中將它們結合起來,以不使內容過快的複雜化。在直接刪除對象後就要用代號2調用方法SecurityProcedure和DeleteProcedure。現在所提到的所有的內核函數,除了ObDereferenceObject都是內部函數,它們的原型我就不給出了,都是已知的。
0b.其它管理器函數
============================
實際上,對ObInsertObject函數的研究打開了對象管理器邏輯的主要部分。我們來簡要的研究一下剩下的管理器導出函數。
DDK文檔中有以下函數:
ObReferenceObject, ObDereferenceObject, ObGetObjectSecurity,
ObReleaseObjectSecurity, ObReferenceObjectByHandle,
ObReferenceObjectByPointer.
從已知的信息來看,在這些函數裏的管理器的工作邏輯都是明顯的。其工作就是處理對象首部、句柄數據庫和同步內部對象。剩下的一些函數我們不再討論:
ObCheckCreateObjectAccess , ObCheckObjectAccess,
ObFindHandleForObject, ObGetObjectPointerCount,
ObMakeTemporaryObject, ObOpenObjectByName, ObOpenObjectByPointer,
ObQueryNameString, ObQueryObjectAuditingByHandle,
ObReferenceObjectByName, ObSetSecurityDescriptorInfo
關於所有這些函數的信息都是已知的。
// 將完整的對象名返回到參數中
// 該參數就是用於保存UNICODE_STRING和名字的緩衝區
// 通常開始先不用參數Result調用,以獲得
// 緩衝區的長度。
// 如果定義了方法QueryName - 則調用它
NTSTATUS NTOSKRNL ObQueryNameString(
PVOID Object,
PUNICODE_STRING Result OPTIONAL,
DWORD Len,
PDWORD RetLen OPTIONAL
);
// 調用ObpLookupObjectName
NTSTATUS NTOSKRNL ObReferenceObjectByName(
PUNICODE_NAME Name,
DWORD Attributes, // 經常使用OBJ_CASE...
PACCESS_STATE AccessState OPTIONAL,
ACCESS_MASK Access,
POBJECT_TYPE Type,
KPROCESSOR_MODE bMode,
DWORD Unknown OPTIONAL, // 與其說是保留,
// 不如說是隻用作SecurityProcedure的參數
// 總是爲0
PVOID BodyPointer
);
// ObpLookupObjectName/ ObpCreateHandle的主要邏輯
NTSTATUS ObOpenObjectByName(
POBJECT_ATTRIBUTES Attributes,
POBJECT_TYPE Type,
KPROCESSOR_MODE bMode,
PACCESS_STATE AccessState OPTIONAL,
ACCESS_MASK Access,
DWORD Unknown OPTIONAL, // 見ObReferenceObjectByName中的unknown
PHANDLE Handle OUT
);
// ObReferenceObjectByPointer / ObpCreateHandle的主要邏輯
NTSTATUS NTOSKRNL ObOpenObjectByPointer(
PVOID Object,
DWORD Attributes,
PACCESS_STATE AccessState OPTIONAL,
ACCESS_MASK Access,
POBJECT_TYPE Type,
KPROCESSOR_MODE bMode,
PHANDLE Handle OUT
);
// 函數代碼 :
// mov eax,[esp+pObject]
// mov eax,[eax-18h]
// retn 4
// 完了!
DWORD NTOSKRNL ObGetObjectPointerCount(PVOID Object);
// 有趣的函數. 一般不用在內核中同時也未公開.
// 對於域Object, Type和HandleAttributes其值可以爲0,
// 此時將查找任意符合的值
// 此函數可以查找關於句柄的信息
BOOLEAN NTOSKRNL ObFindHandleForObject(
PPROCESS Process,
PVOID Object,
POBJECT_TYPE Type,
DWORD HandleAttributes, //低3位. 見 '句柄表' PHANDLE Handle OUT // 返回句柄
);
// 此函數清除對象首部中的相應標誌
// 如果不再引用該對象,則允許將其刪除,並調用內部函數ObpDeleteNameCheck
NTSTATUS NTOSKRNL ObMakeTemporaryObject(
PVOID Object
);
// ObGetObjectSecurity/SeAccessCheck的主要邏輯
BOOLEAN NTOSKRNL ObCheckObjectAccess(
PVOID Object,
PACCESS_STATE AccessState,
BOOLEAN bTypeLocked,
KPROCESSOR_MODE Mode,
OUT PNTSTATUS AccessStatus
);
// 當在樹中創建對象時,在ObpLookupObjectName中被調用
BOOLEAN NTOSKRNL ObCheckCreateObjectAccess(
PDIRECTORY Directory,
ACCESS_MASK Access,
PACCESS_STATE AccessState,
PUNICODE NameFromDir,
DWORD UnknLockedFlags,
KPROCESSOR_MODE Mode,
OUT PNTSTATUS AccessStatus
);
// последовательность ExMapHandleToPointer/取出位0x4 (見'句柄數據庫')
BOOLEAN NTOSKRNL ObQueryObjectAuditingByHandle(
HANDLE Handle,
PDWORD ReturnValue);
// 此函數在方法SpDefaultObjectMetod中調用,用於代號0 - set security descriptor.
// 該函數的工作是基於SeSetSecurityDesriptorInfo函數的。現在我們不詳細研究安全描述符,除此之外,某些參數還不明瞭,
// 故此函數的原型我就不給出了
// ObSetSecurityDescriptorInfo(...);
Не останавливаться на будущем -
Я понял, что его не будет
И ты знаешь, когда я уйду -
Ты услышишь мой плач в ветре...
(с) by Anathema
0c.結語
==============
實際上,對象管理器的大多數導出函數都不是完全functional的,這點現在是完全顯然的了。一般來說,如果提出關於可行性的問題,可以表達這樣的見解。Kernel驅動並不使用句柄。一般說來,在普通情況下並不知道位於驅動代碼在哪個進程的上下文中執行。實際使用的函數允許用名字或指針來引用對象。
對於剩下的函數,如果我們沒寫過系統服務的話,是很難想到其真實的用法的(但是完全可能的)。對於實現Windows NT的潛力,管理器運作的知識總是很有益的。我們怎樣確信處理對象的系統是十分靈活的——要知道NT的執行系統要使用它來模擬不同的操作系統。當然,本文可能還有不準確的地方。任何評論意見請發至[email protected] (Peter Kosyh)。
Best regards, Gloomy
附錄
13.從用戶模式下獲取對象信息
=================================================
在Windows NT中有許多系統調用以Query或QueryInformation命名。這些函數都能獲得有趣的信息,而且大多是未公開的。下面給出NtQueryObject函數的原型,使用該函數能輕鬆獲取關於對象格式的信息。
typedef enum _OBJECTINFOCLASS {
BaseObjectInfo,
NameObjectInfo,
TypeObjectInfo,
AllTypesObjectInfo,
HandleObjectInfo
} OBJECTINFOCLASS;
NTSYSAPI NTSTATUS NTAPI NtQueryObject(
HANDLE ObjHandle,
OBJECTINFOCLASS ObjectInfoClass,
OUT PVOID ObjectInfo, // 信息緩衝區
DWORD ObjectInfoLen, // 緩衝區長度
OUT PDWORD RetInfoLen // 返回信息的長度
);
typedef struct _BASE_OBJECT_INFO{
DWORD HandleAttributes,
ACCESS_MASK GrantedAccess,
DWORD HandleCount,
DWORD RefCount,
DWORD PagedPollQuota,
DWORD NonPagedPollQuota,
DWORD ReservedAndAlwaysNull[3],
DWORD NameObjectInfoLength,
DWORD TypeObjectInfoLength,
DWORD SecurityDescriptorLengh,
LARGE_INTEGER SymLinkCreationTime //自1601年起的時間
};
typedef struct _NAME_OBJECT_INFO{
UNICODE_STRING Name;
// 這裏應當是名字的位置. 參數ObjectInfo可能爲0
// 以獲取緩衝區的大小
}NAME_OBJECT_INFO;
typedef struct _TYPE_OBJECT_INFO{
UNICODE_STRING Name;
DWORD InstanceCount;
DWORD HandleCount;
DWORD PeakObjects;
DWORD PeakHandles;
DWORD AllowedAttributesMask;
GENERIC_MAPPING GenericMapping;
DWORD AllowedAccessMask;
BOOLEAN bInNameSpace;
BOOLEAN bHandleDBInfo;
BOOLEAN Align[2];
DWORD Unknown6; // 見對象類型的unknown6域
DWORD DefaultPagedPollQuota;
DWORD DefaultNonPagedPollQuota;
}TYPE_OBJECT_INFO;
typedef struct _ALL_TYPES_OBJECT_INFO{
DWORD NumOfTypes;
TYPE_OBJECT_INFO [ANY_SIZE_ARRAY];
}ALL_TYPES_OBJECT_INFO;
typedef struct _HANDLE_OBJECT_INFO{
BOOLEAN Inherit;
BOOLEAN ProtectFromClose;
}HANDLE_OBJECT_INFO;
14.某些與對象管理相關的系統服務
================================================================
在Недокументированные возможности Windows NT一書中А.В. Коберниченко給出了某些系統服務接口的原型。作者的目的是描述未公開系統調用的原型,於是從kernel32.dll中使用的系統調用出發。大概對於這個目的,這是個捷徑(恰巧,這本書也試圖描述NtQueryObject函數,但是其描述在這裏是不適用的,而且書中的函數的描述很不完整)。對於某些函數的反彙編,我檢查了它的輸出並確信定義的正確性。下面我給出某些調用,因爲作者對它們進行了描述。
//用於任何對象的函數
NTSYSAPI NTSTATUS NTAPI
NtClose(IN HANDLE Handle);
NTSYSAPI NTSTATUS NTAPI NtMakeTemporaryObject(
IN HANDLE Handle
);
#define DUPLICATE_CLOSE_SOURCE 0x00000001
#define DUPLICATE_SAME_ACCESS 0x00000002
NTSYSAPI NTSTATUS NTAPI
NtDuplicateObject(
IN HANDLE SourceProcessHandle,
IN HANDLE SourceHandle,
IN HANDLE TargetProcessHandle,
OUT PHANDLE TargetHandle OPTIONAL,
IN ACCESS_MASK DesiredAccess,
IN ULONG Attributes,//OBJ_xxx
IN ULONG Options
);
//對象目錄
#define DIRECTORY_QUERY (0x0001)
#define DIRECTORY_TRAVERSE (0x0002)
#define DIRECTORY_CREATE_OBJECT (0x0004)
#define DIRECTORY_CREATE_SUBDIRECTORY (0x0008)
#define DIRECTORY_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | 0xF)
NTSYSAPI NTSTATUS NTAPI
NtCreateDirectoryObject(
OUT PHANDLE DirectoryHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
NTSYSAPI NTSTATUS NTAPI
NtOpenDirectoryObject(
OUT PHANDLE DirectoryHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
typedef struct _OBJECT_NAMETYPE_INFO {
UNICODE_STRING ObjectName;
UNICODE_STRING ObjectType;
} OBJECT_NAMETYPE_INFO, *POBJECT_NAMETYPE_INFO;
typedef enum _DIRECTORYINFOCLASS {
ObjectArray,
ObjectByOne
} DIRECTORYINFOCLASS, *PDIRECTORYINFOCLASS;
NTSYSAPI NTSTATUS NTAPI
NtQueryDirectoryObject(
IN HANDLE DirectoryObjectHandle,
OUT PVOID ObjectInfoBuffer,
IN ULONG ObjectInfoBufferLength,
IN DIRECTORYINFOCLASS DirectoryInformationClass,
IN BOOLEAN First,
IN OUT PULONG ObjectIndex,
OUT PULONG LengthReturned
);
//對象符號鏈接
#define SYMBOLIC_LINK_QUERY (0x0001)
#define SYMBOLIC_LINK_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | 0x1)
NTSYSAPI NTSTATUS NTAPI
NtCreateSymbolicLinkObject(
OUT PHANDLE ObjectHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PUNICODE_STRING SubstituteString
);
NTSYSAPI NTSTATUS NTAPI
NtOpenSymbolicLinkObject(
OUT PHANDLE ObjectHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
typedef struct _OBJECT_NAME_INFORMATION {
UNICODE_STRING Name;
} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;
NTSYSAPI NTSTATUS NTAPI
NtQuerySymbolicLinkObject(
IN HANDLE ObjectHandle,
OUT POBJECT_NAME_INFORMATION SubstituteString,
OUT PULONG SubstituteStringLength //字節
);
我想,現在領悟描述過的大部分管理器系統調用行爲應該不難了……
在網上可以找到 "Недокументированные возможномти Windows NT"一書的例子——作者做了一件大好事。在源代碼中描述了大部分未公開的系統調用以及給出的例子的用法。