淺析Windows的訪(fǎng)問(wèn)權(quán)限檢查機(jī)制
0x00 簡(jiǎn)介
在操作系統(tǒng)中,當(dāng)我們提到安全的時(shí)候,意味著有一些資源需要被保護(hù),在Windows操作系統(tǒng)中,這些被保護(hù)的資源大多以對(duì)象(Object)的形式存在,對(duì)象是對(duì)資源的一種抽象。每個(gè)對(duì)象都可以擁有自己的安全描述符(Security Descriptor),用來(lái)描述它能夠被誰(shuí)、以何種方式而訪(fǎng)問(wèn)。這些對(duì)象是客體,那么訪(fǎng)問(wèn)這些對(duì)象的主體是什么呢?這些主體就是操作系統(tǒng)中的各個(gè)進(jìn)程,更準(zhǔn)確地說(shuō)是這些進(jìn)程中的每個(gè)線(xiàn)程。每個(gè)進(jìn)程都有一個(gè)基本令牌 (Primary Token),可以被進(jìn)程中的每個(gè)線(xiàn)程所共享,而有些線(xiàn)程比較特殊,它們正在模擬(Impersonating)某個(gè)客戶(hù)端的身份,因此擁有一個(gè)模擬令牌(Impersonation Token),對(duì)于這些線(xiàn)程來(lái)說(shuō),有效令牌就是模擬令牌,而其他線(xiàn)程的有效令牌則是其所屬進(jìn)程的基本令牌。當(dāng)主體嘗試去訪(fǎng)問(wèn)客體時(shí),操作系統(tǒng)會(huì)執(zhí)行訪(fǎng)問(wèn)權(quán)限檢查(Access Check),具體來(lái)說(shuō),是用主體的有效令牌與客體的安全描述符進(jìn)行比對(duì),從而確定該次訪(fǎng)問(wèn)是否合法。為了提高效率,訪(fǎng)問(wèn)權(quán)限檢查(Access Check)提供了一種緩存機(jī)制,即只有當(dāng)一個(gè)對(duì)象被一個(gè)進(jìn)程創(chuàng)建或者打開(kāi)時(shí),才會(huì)進(jìn)行訪(fǎng)問(wèn)權(quán)限檢查,訪(fǎng)問(wèn)權(quán)限檢查的結(jié)果會(huì)被緩存到進(jìn)程的句柄表(Handle Table)中,該進(jìn)程對(duì)該對(duì)象的后續(xù)操作只需要查詢(xún)句柄表中內(nèi)容即可確定訪(fǎng)問(wèn)的合法性,而不必每次都執(zhí)行開(kāi)銷(xiāo)更大的訪(fǎng)問(wèn)權(quán)限檢查(Access Check)。因?yàn)閷?duì)象和進(jìn)程都有各自的繼承層次,所以對(duì)象的安全描述符和進(jìn)程的令牌從邏輯上講也是可以繼承的,比如文件系統(tǒng)中的文件可以繼承其所在目錄的安全描述符,子進(jìn)程可以繼承父進(jìn)程的令牌,這種繼承機(jī)制使讓對(duì)象和進(jìn)程擁有了缺省的安全屬性,因此,在一個(gè)系統(tǒng)中的安全描述符和令牌的實(shí)例數(shù)目非常有限,大多數(shù)情況下是從父輩們繼承過(guò)來(lái)的缺省值。

0x01 訪(fǎng)問(wèn)權(quán)限檢查
訪(fǎng)問(wèn)權(quán)限檢查包含了多個(gè)維度上的檢查:
DACL檢查
DACL(自主訪(fǎng)問(wèn)控制表,Discretionary Access Control List)檢查是最基本的訪(fǎng)問(wèn)權(quán)限檢查。

在安全描述符中存在著一個(gè)DACL表(見(jiàn)安全描述符圖解),里面描述了擁有何種身份的主體申請(qǐng)的何種訪(fǎng)問(wèn)權(quán)限會(huì)被允許或者拒絕。DACL表中的每個(gè)表項(xiàng)擁有如下的結(jié)構(gòu):
We'll define the structure of the predefined ACE types. Pictorally
the structure of the predefined ACE's is as follows: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
+---------------+-------+-------+---------------+---------------+
| AceFlags | Resd |Inherit| AceSize | AceType |
+---------------+-------+-------+---------------+---------------+
| Mask |
+---------------------------------------------------------------+
| |
+ +
| |
+ Sid +
| |
+ +
| |
+---------------------------------------------------------------+Mask is the access mask associated with the ACE. This is either the
access allowed, access denied, audit, or alarm mask.Sid is the Sid associated with the ACE.
其中,對(duì)于DACL來(lái)說(shuō),AceType可以有
#define ACCESS_ALLOWED_ACE_TYPE (0x0)
#define ACCESS_DENIED_ACE_TYPE (0x1)
兩種可能;Mask是一個(gè)代表相關(guān)權(quán)限集合的位圖;主體的身份使用Sid(Security Identifier)來(lái)表示,這是一個(gè)變長(zhǎng)的數(shù)據(jù)結(jié)構(gòu):
kd> dt nt!_SID
+0x000 Revision : UChar
+0x001 SubAuthorityCount : UChar
+0x002 IdentifierAuthority : _SID_IDENTIFIER_AUTHORITY
+0x008 SubAuthority : [1] Uint4B
Sid是Windows安全系統(tǒng)中一個(gè)基本類(lèi)型,可以用來(lái)唯一標(biāo)識(shí)多種不同種類(lèi)的實(shí)體,比如標(biāo)識(shí)用戶(hù)身份與組身份,標(biāo)識(shí)完整性級(jí)別、可信賴(lài)級(jí)別,AppContainer中的Capability等等。
DACL檢查過(guò)程是這樣的,如果對(duì)象的安全描述符為空,代表著這個(gè)對(duì)象不受任何保護(hù),即可以被任何令牌代表的主體訪(fǎng)問(wèn);如果對(duì)象擁有一個(gè)非空的安全描述符,但是安全描述符中的Dacl成員為空,代表該對(duì)象沒(méi)有顯式地賦予任何主體任何訪(fǎng)問(wèn)權(quán)限,即不允許被訪(fǎng)問(wèn);如果Dacl成員非空,就按照表中的順序逐一與當(dāng)前主體的身份Sid進(jìn)行比對(duì),直到該主體所請(qǐng)求的權(quán)限被全部顯式允許,或者某一請(qǐng)求的權(quán)限被顯式地拒絕,檢查結(jié)束;如果直到Dacl表檢查結(jié)束,主體申請(qǐng)的權(quán)限仍未被全部顯式允許,那么仍然代表拒絕。
根據(jù)以上規(guī)則可知,DACL中各個(gè)表項(xiàng)的順序?qū)υL(fǎng)問(wèn)權(quán)限檢查的結(jié)果至關(guān)重要,比如說(shuō)有一條有關(guān)某主體的顯式拒絕的表項(xiàng)位于表的最后,如果位于其之前的表項(xiàng)已經(jīng)顯式允許了這一主體申請(qǐng)的權(quán)限,那么這條顯式拒絕的表項(xiàng)將起不到任何作用?;诖朔N原因,如果通過(guò)Windows右鍵安全選項(xiàng)菜單添加的DACL表項(xiàng),顯式拒絕的表項(xiàng)永遠(yuǎn)被置于顯式允許表項(xiàng)前面,以保證其有效性。直接通過(guò)API添加則不受此種限制。
特權(quán)(Privileges)以及超級(jí)特權(quán)(Super Privileges)檢查
所謂特權(quán),是指那些不與某一具體對(duì)象相關(guān)的、影響整個(gè)系統(tǒng)的訪(fǎng)問(wèn)權(quán)限。而超級(jí)特權(quán)是這樣的一些特權(quán),你一旦擁有其中的一項(xiàng)超級(jí)特權(quán),就擁有了取得所有其他特權(quán)和權(quán)限的潛質(zhì),比如SeDebugPrivilege,在一般情況下,一旦擁有該特權(quán),就可以任意讀寫(xiě)目標(biāo)進(jìn)程的內(nèi)存,而不僅僅局限于打開(kāi)該進(jìn)程獲得句柄時(shí)得到的那些權(quán)限及特權(quán)。
完整性級(jí)別(Integrity Level)和強(qiáng)制策略(Mandatory Policy)檢查
完整性級(jí)別(IL)機(jī)制是Windows Vista引入的重要的安全機(jī)制,建立在其基礎(chǔ)上的沙盒機(jī)制十分簡(jiǎn)單又異常強(qiáng)大。
受限令牌(Restricted Token)檢查
通過(guò)創(chuàng)建受限令牌,可以獲得一個(gè)普通令牌所有擁有的權(quán)限集合的一個(gè)子集,用來(lái)進(jìn)行一些低權(quán)限操作,降低安全風(fēng)險(xiǎn)。
AppContainer's Capabilities檢查
LowBox令牌以及AppContainer's Capabilities組身份是從Windows 8開(kāi)始引入的安全機(jī)制,它提供了另一種更加復(fù)雜的沙盒機(jī)制。Capabilities與Android系統(tǒng)中對(duì)于A(yíng)pp權(quán)限限制非常類(lèi)似。
可信級(jí)別(Trust Level)檢查
完整性級(jí)別檢查是一種粗粒度的、相對(duì)穩(wěn)定的訪(fǎng)問(wèn)權(quán)限控制手段;伴隨著Windows的不斷進(jìn)化,對(duì)于簽名機(jī)制的依賴(lài)越來(lái)越明顯,簽名代表了一種可信賴(lài)程度。筆者認(rèn)為,Windows正在逐漸建立一套完善的基于可信賴(lài)級(jí)別的權(quán)限檢查機(jī)制,受保護(hù)進(jìn)程(Protected Process)就是這是這套機(jī)制中一部分。
上面的內(nèi)容簡(jiǎn)單介紹了Windows的權(quán)限檢查模型,以及權(quán)限檢查的維度;下面將介紹一些實(shí)現(xiàn)細(xì)節(jié)。
0x02 令牌
TOKEN結(jié)構(gòu)體
Token結(jié)構(gòu)體是訪(fǎng)問(wèn)權(quán)限檢查中的代表主體身份的核心數(shù)據(jù)結(jié)構(gòu),下面展示的是Windows 10 x64平臺(tái)下的構(gòu)成。
kd> dt nt!_TOKEN
+0x000 TokenSource : _TOKEN_SOURCE
+0x010 TokenId : _LUID
+0x018 AuthenticationId : _LUID
+0x020 ParentTokenId : _LUID
+0x028 ExpirationTime : _LARGE_INTEGER
+0x030 TokenLock : Ptr64 _ERESOURCE
+0x038 ModifiedId : _LUID
+0x040 Privileges : _SEP_TOKEN_PRIVILEGES
+0x058 AuditPolicy : _SEP_AUDIT_POLICY
+0x078 SessionId : Uint4B
+0x07c UserAndGroupCount : Uint4B
+0x080 RestrictedSidCount : Uint4B
+0x084 VariableLength : Uint4B
+0x088 DynamicCharged : Uint4B
+0x08c DynamicAvailable : Uint4B
+0x090 DefaultOwnerIndex : Uint4B
+0x098 UserAndGroups : Ptr64 _SID_AND_ATTRIBUTES
+0x0a0 RestrictedSids : Ptr64 _SID_AND_ATTRIBUTES
+0x0a8 PrimaryGroup : Ptr64 Void
+0x0b0 DynamicPart : Ptr64 Uint4B
+0x0b8 DefaultDacl : Ptr64 _ACL
+0x0c0 TokenType : _TOKEN_TYPE
+0x0c4 ImpersonationLevel : _SECURITY_IMPERSONATION_LEVEL
+0x0c8 TokenFlags : Uint4B
+0x0cc TokenInUse : UChar
+0x0d0 IntegrityLevelIndex : Uint4B
+0x0d4 MandatoryPolicy : Uint4B
+0x0d8 LogonSession : Ptr64 _SEP_LOGON_SESSION_REFERENCES
+0x0e0 OriginatingLogonSession : _LUID
+0x0e8 SidHash : _SID_AND_ATTRIBUTES_HASH
+0x1f8 RestrictedSidHash : _SID_AND_ATTRIBUTES_HASH
+0x308 pSecurityAttributes : Ptr64 _AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION
+0x310 Package : Ptr64 Void
+0x318 Capabilities : Ptr64 _SID_AND_ATTRIBUTES
+0x320 CapabilityCount : Uint4B
+0x328 CapabilitiesHash : _SID_AND_ATTRIBUTES_HASH
+0x438 LowboxNumberEntry : Ptr64 _SEP_LOWBOX_NUMBER_ENTRY
+0x440 LowboxHandlesEntry : Ptr64 _SEP_LOWBOX_HANDLES_ENTRY
+0x448 pClaimAttributes : Ptr64 _AUTHZBASEP_CLAIM_ATTRIBUTES_COLLECTION
+0x450 TrustLevelSid : Ptr64 Void
+0x458 TrustLinkedToken : Ptr64 _TOKEN
+0x460 IntegrityLevelSidValue : Ptr64 Void
+0x468 TokenSidValues : Ptr64 _SEP_SID_VALUES_BLOCK
+0x470 IndexEntry : Ptr64 _SEP_LUID_TO_INDEX_MAP_ENTRY
+0x478 VariablePart : Uint8B
我們比較關(guān)注其中的特權(quán)位圖和三個(gè)代表主體身份的Sid數(shù)組:UserAndGroups,RestrictedSids,Capabilities。

Privileges位圖
特權(quán)在用戶(hù)態(tài)是通過(guò)LUID來(lái)表示,在內(nèi)核結(jié)構(gòu)體_TOKEN中是使用三個(gè)位圖來(lái)表示:
kd> dt _SEP_TOKEN_PRIVILEGES
nt!_SEP_TOKEN_PRIVILEGES
+0x000 Present : Uint8B
+0x008 Enabled : Uint8B
+0x010 EnabledByDefault : Uint8B
分別代表當(dāng)前的主體可以選用的特權(quán)集合(Present)、已經(jīng)打開(kāi)的特權(quán)集合(Enabled)和默認(rèn)打開(kāi)的特權(quán)集合(EnabledByDefault),后兩個(gè)集合應(yīng)該是Present集合的子集。
與代表主體身份的Sid數(shù)組不同,特權(quán)集合的表示簡(jiǎn)單,而且沒(méi)有任何保護(hù)。從用戶(hù)態(tài)通過(guò)API只能打開(kāi)或者關(guān)閉某一項(xiàng)已經(jīng)存在的特權(quán),而不能增加可選的特權(quán),換句話(huà)說(shuō),用戶(hù)態(tài)只能修改Enabled特權(quán)集合,而不能修改Present特權(quán)集合;從內(nèi)核態(tài)可以直接修改Present特權(quán)集合,比如給普通進(jìn)程增加SeAssignPrimaryTokenPrivilege特權(quán),以便為子進(jìn)程顯式指定令牌,而不是繼承當(dāng)前進(jìn)程的令牌,可以達(dá)到擴(kuò)大子進(jìn)程權(quán)限的效果。
代表主體身份的Sid數(shù)組
三個(gè)代表主體身份的Sid數(shù)組分別是:
UserAndGroups:
代表著主體的普通用戶(hù)身份和組身份,是不可或缺的成員;
RestrictedSids:
可選成員,如果不為空,則代表著當(dāng)前的令牌是受限令牌,受限令牌通過(guò)從普通令牌中過(guò)濾掉一些比較敏感的身份轉(zhuǎn)化而來(lái),受限令牌中的UserAndGroups成員與普通令牌相同,但是RestriectedSids成員不為空,里面保存著過(guò)濾后的身份子集;由于在訪(fǎng)問(wèn)權(quán)限檢查時(shí),三個(gè)身份Sid數(shù)組要同時(shí)進(jìn)行檢查,只有結(jié)果都通過(guò)才允許該次訪(fǎng)問(wèn),因此通過(guò)增加代表著受限制的權(quán)限集合的RestrictedSids成員,既達(dá)到了限制令牌權(quán)限的目的,又在UserAndGroups成員中保留了原有令牌的完整身份信息。
Capabilities:
可選成員,僅用于A(yíng)ppContainer,其中的Sid代表著與App相關(guān)的身份,比如擁有連接網(wǎng)絡(luò)、訪(fǎng)問(wèn)當(dāng)前位置等權(quán)限的身份。
這三個(gè)Sid數(shù)組都關(guān)聯(lián)了哈希信息,以保護(hù)其完整性,因此,即使從內(nèi)核態(tài)直接修改,也會(huì)因?yàn)闊o(wú)法通過(guò)完整性驗(yàn)證而失敗。不過(guò)好在哈希的算法非常簡(jiǎn)單,下面展示的就是Windows 10 x64平臺(tái)下面該算法的C++演示代碼:
- static
- NTSTATUS
- RtlSidHashInitialize
- (
- __in PSID_AND_ATTRIBUTES Groups,
- __in size_t GroupsCount,
- __inout PSID_AND_ATTRIBUTES_HASH HashBuffer
- )
- {
- if (NULL == HashBuffer)
- return 0xC000000D;
- memset(HashBuffer, 0, 0x110);
- if (0 == GroupsCount || NULL == Groups)
- return 0;
- HashBuffer->SidCount = GroupsCount;
- HashBuffer->SidAttr = Groups;
- if (GroupsCount > 0x40)
- GroupsCount = 0x40;
- if (0 == GroupsCount)
- return 0;
- size_t bit_pos = 1;
- for (size_t i = 0; i < GroupsCount; i++)
- {
- PISID sid = reinterpret_cast<PISID>((Groups + i)->Sid);
- size_t sub_authority_count = sid->SubAuthorityCount;
- DWORD sub_authority = sid->SubAuthority[sub_authority_count - 1];
- *(size_t*)(&HashBuffer->Hash[(sub_authority & 0x0000000F)]) |= bit_pos;
- *(size_t*)(&HashBuffer->Hash[((sub_authority & 0x000000F0) >> 4) + 0x10]) |= bit_pos;
- bit_pos <<= 1;
- }
- return 0;
- }
該算法有兩處明顯的局限,只計(jì)算每個(gè)Sid的最后一個(gè)Sub Authority的最低位字節(jié),以及最多只有前64個(gè)Sid參與計(jì)算。
UAC與關(guān)聯(lián)令牌
UAC是Vista版本引入的比較重要的安全機(jī)制,很多用戶(hù)抱怨它帶來(lái)的不便性,然而它就像Linux操作系統(tǒng)中的sudo一樣,是保護(hù)系統(tǒng)安全的一道重要屏障。當(dāng)用戶(hù)登錄Windows時(shí),操作系統(tǒng)會(huì)為用戶(hù)生成一對(duì)初始令牌,分別是代表著用戶(hù)所擁有的全部權(quán)限的完整版本令牌(即管理員權(quán)限令牌),以及被限制管理員權(quán)限后的普通令牌,二者互為關(guān)聯(lián)令牌;此后,代表用戶(hù)的進(jìn)程所使用的令牌都是由普通令牌繼承而來(lái),用來(lái)進(jìn)行常規(guī)的、非敏感的操作;當(dāng)用戶(hù)需要進(jìn)行一些需要管理員權(quán)限的操作時(shí),比如安裝軟件、修改重要的系統(tǒng)設(shè)置時(shí),都會(huì)通過(guò)彈出提權(quán)對(duì)話(huà)框的形式提示用戶(hù)面臨的風(fēng)險(xiǎn),征求用戶(hù)的同意,一旦用戶(hù)同意,將會(huì)切換到當(dāng)前普通令牌關(guān)聯(lián)的管理員權(quán)限令牌,來(lái)進(jìn)行敏感操作。通過(guò)這種與用戶(hù)交互的方式,避免一些惡意程序在后臺(tái)稍稍執(zhí)行敏感操作。
關(guān)聯(lián)令牌是通過(guò)Logon Session來(lái)實(shí)現(xiàn)的,下圖展示了其大致原理:

并非所有的令牌都有關(guān)聯(lián)令牌,比如一直運(yùn)行在較高權(quán)限下不需要進(jìn)行提權(quán)操作的令牌。
0x03 對(duì)象
對(duì)象的結(jié)構(gòu)
對(duì)象是對(duì)操作系統(tǒng)中需要保護(hù)和集中管理的資源的一種抽象,對(duì)象擁有統(tǒng)一的頭部,保存著抽象出來(lái)的信息。
kd> dt _OBJECT_HEADER
nt!_OBJECT_HEADER
+0x000 PointerCount : Int8B
+0x008 HandleCount : Int8B
+0x008 NextToFree : Ptr64 Void
+0x010 Lock : _EX_PUSH_LOCK
+0x018 TypeIndex : UChar
+0x019 TraceFlags : UChar
+0x019 DbgRefTrace : Pos 0, 1 Bit
+0x019 DbgTracePermanent : Pos 1, 1 Bit
+0x01a InfoMask : UChar
+0x01b Flags : UChar
+0x01b NewObject : Pos 0, 1 Bit
+0x01b KernelObject : Pos 1, 1 Bit
+0x01b KernelOnlyAccess : Pos 2, 1 Bit
+0x01b ExclusiveObject : Pos 3, 1 Bit
+0x01b PermanentObject : Pos 4, 1 Bit
+0x01b DefaultSecurityQuota : Pos 5, 1 Bit
+0x01b SingleHandleEntry : Pos 6, 1 Bit
+0x01b DeletedInline : Pos 7, 1 Bit
+0x01c Spare : Uint4B
+0x020 ObjectCreateInfo : Ptr64 _OBJECT_CREATE_INFORMATION
+0x020 QuotaBlockCharged : Ptr64 Void
+0x028 SecurityDescriptor : Ptr64 Void
+0x030 Body : _QUAD

對(duì)象的頭部包含著指向安全描述符的指針。
安全描述符
安全描述符保存著作為訪(fǎng)問(wèn)權(quán)限檢查客體的對(duì)象的主要信息。它包含著4個(gè)關(guān)鍵成員,根據(jù)這4個(gè)成員是嵌入在結(jié)構(gòu)體的尾部,還是引用自外部緩存位置的差異,可以分為兩種不同的結(jié)構(gòu)形式:
kd> dt _SECURITY_DESCRIPTOR
ntdll!_SECURITY_DESCRIPTOR
+0x000 Revision : UChar
+0x001 Sbz1 : UChar
+0x002 Control : Uint2B
+0x008 Owner : Ptr64 Void
+0x010 Group : Ptr64 Void
+0x018 Sacl : Ptr64 _ACL
+0x020 Dacl : Ptr64 _ACL
kd> dt _SECURITY_DESCRIPTOR_RELATIVE
nt!_SECURITY_DESCRIPTOR_RELATIVE
+0x000 Revision : UChar
+0x001 Sbz1 : UChar
+0x002 Control : Uint2B
+0x004 Owner : Uint4B
+0x008 Group : Uint4B
+0x00c Sacl : Uint4B
+0x010 Dacl : Uint4B
Owner和Group代表了該安全描述符的屬主Sid,Sacl是系統(tǒng)訪(fǎng)問(wèn)控制表(System Access Control List)的簡(jiǎn)稱(chēng),里面可以包含當(dāng)前對(duì)象的審計(jì)(Audit)、完整性級(jí)別(Integrity Level)以及可信賴(lài)級(jí)別(Trust Level)等信息,與前面提到的Dacl共用同一結(jié)構(gòu)體。
可選頭部
對(duì)象除了擁有共同的固定頭部外,還可以有可選的頭部,保存著名稱(chēng)等可選信息。通過(guò)固定頭部的InfoMask成員查表可以得到可選頭部的位置,如圖所示:

對(duì)象目錄
只有部分對(duì)象擁有名稱(chēng)信息,它們被稱(chēng)為命名對(duì)象。命名對(duì)象的主要作用是方便對(duì)象在不同的進(jìn)程中共享,它們被按類(lèi)別編纂成對(duì)象目錄,因此可以通過(guò)在對(duì)象目錄中的路徑信息找到該對(duì)象。對(duì)象目錄的實(shí)現(xiàn)如下圖所示:

對(duì)象類(lèi)型
對(duì)象類(lèi)型是對(duì)象中非常重要的信息,Windows將對(duì)象的類(lèi)型信息從同一類(lèi)對(duì)象中抽象出來(lái),保存成一個(gè)單獨(dú)的類(lèi)型對(duì)象。系統(tǒng)中全部的類(lèi)型對(duì)象被集中放置在一個(gè)表中,對(duì)象通過(guò)維護(hù)一個(gè)指向該表的索引(TypeIndex)來(lái)表明當(dāng)前對(duì)象的類(lèi)型。這個(gè)索引值直接保存在對(duì)象的頭部,而對(duì)象體與對(duì)象頭部直接相鄰,如果對(duì)象體被損壞,有可能導(dǎo)致頭部的索引值被改變,使所謂的類(lèi)型混淆(Type Confusion)利用成為可能。為了緩解這一問(wèn)題,Windows 10對(duì)該索引值做了特殊保護(hù),如下圖所示:

這種保護(hù)方式簡(jiǎn)單而強(qiáng)大,新的索引值由3部分經(jīng)過(guò)異或操作得到:類(lèi)型對(duì)象在類(lèi)型對(duì)象表中的真實(shí)索引值,對(duì)象頭部地址的第二個(gè)字節(jié)(即第8到第15位),保存在ObHeaderCookie全局變量中的因每次系統(tǒng)啟動(dòng)而異的Cookie字節(jié)。其中,ObHeaderCookie的引入,使同一類(lèi)型的對(duì)象在不同機(jī)器上,甚至是同一機(jī)器上兩次啟動(dòng)之間的索引值不同,然而這樣并不足以緩解類(lèi)型混淆利用,我們還可以利用信息泄露(Info Leak)來(lái)繞過(guò)(Bypass)該保護(hù),因此還引入了對(duì)象頭部地址,使得在同一時(shí)刻、同一系統(tǒng)中的兩個(gè)相同類(lèi)型對(duì)象的不同實(shí)例間的索引值也不相同,從而有效地緩解類(lèi)型混淆利用。
0x04 完整性級(jí)別檢查
完整性級(jí)別(IL)檢查是沙盒的最簡(jiǎn)單實(shí)現(xiàn)方式,通過(guò)完整性級(jí)別在對(duì)象和進(jìn)程層次之間的繼承關(guān)系,就如同在操作系統(tǒng)中建立起了“世襲制度”。通過(guò)嚴(yán)格控制完整性級(jí)別的繼承規(guī)則,以及設(shè)置嚴(yán)格的完整性級(jí)別檢查制度,可以保證“出身低微”的主體無(wú)法訪(fǎng)問(wèn)到“出身高貴”的客體資源。
完整性擁有以下幾個(gè)級(jí)別:
SeUntrustedMandatorySid
SeLowMandatorySid
SeMediumMandatorySid
SeHighMandatorySid
SeSystemMandatorySid
主體的缺省完整性級(jí)別是SeUntrustedMandatorySid,而客體的缺省完整性級(jí)別是SeMediumMandatorySid,這種差異也進(jìn)一步地強(qiáng)化了這種“世襲制度”。
0x05 保護(hù)進(jìn)程
保護(hù)進(jìn)程(Protected Process)是Windows操作系統(tǒng)為了保護(hù)某些關(guān)鍵進(jìn)程,防止其被普通進(jìn)程調(diào)試、注入、甚至是讀取內(nèi)存信息而建立起來(lái)的一種安全機(jī)制。
保護(hù)進(jìn)程與其他普通進(jìn)程的區(qū)別在于,保護(hù)進(jìn)程的Protection成員不為0。
kd> dt nt!_EPROCESS Protection
+0x6b2 Protection : _PS_PROTECTION
kd> dt nt!_PS_PROTECTION
+0x000 Level : UChar
+0x000 Type : Pos 0, 3 Bits
+0x000 Audit : Pos 3, 1 Bit
+0x000 Signer : Pos 4, 4 Bits
保護(hù)進(jìn)程的Type成員可以代表兩種保護(hù)類(lèi)型:Protected Process(PP),Protected Process Lite(PPL),兩者的區(qū)別在于PP保護(hù)進(jìn)程擁有更高的權(quán)限。保護(hù)進(jìn)程的Signer成員代表該進(jìn)程的簽名發(fā)布者的類(lèi)別。對(duì)于Signer為PsProtectedSignerWindows(5)和PsProtectedSignerTcb(6)的保護(hù)進(jìn)程,其Type和Signer信息會(huì)被抽取出來(lái),組裝成一個(gè)Sid,代表著該進(jìn)程的可信賴(lài)級(jí)別,保存到基本令牌中的TrustLevelSid成員中。當(dāng)一個(gè)令牌中的TrustLevelSid被使用時(shí),需要保證與當(dāng)前進(jìn)程的Protection信息保持同步,這主要是為了應(yīng)對(duì)令牌在不同進(jìn)程間傳遞時(shí)(比如子進(jìn)程繼承父進(jìn)程的令牌)導(dǎo)致的TrustLevelSid成員過(guò)時(shí)的情形。
當(dāng)調(diào)試或者創(chuàng)建一個(gè)進(jìn)程時(shí),會(huì)調(diào)用內(nèi)核函數(shù)PspCheckForInvalidAccessByProtection進(jìn)行權(quán)限檢查,該函數(shù)根據(jù)當(dāng)前進(jìn)程以及目標(biāo)進(jìn)程的Protection來(lái)判定當(dāng)前操作是否需要遵守保護(hù)進(jìn)程規(guī)定的權(quán)限限制,具體判定規(guī)則如下:
如果操作來(lái)自于內(nèi)核態(tài)代碼,不需要遵守限制;
如果目標(biāo)進(jìn)程的Protection成員為空,表示目標(biāo)進(jìn)程不是保護(hù)進(jìn)程,不需要遵守限制;
如果當(dāng)前進(jìn)程是PP類(lèi)型保護(hù)進(jìn)程,該類(lèi)型保護(hù)進(jìn)程擁有最高權(quán)限,不需要遵守限制;
如果當(dāng)前進(jìn)程與目標(biāo)進(jìn)程都是PPL類(lèi)型保護(hù)進(jìn)程,需要根據(jù)RtlProtectedAccess表來(lái)判斷當(dāng)前進(jìn)程的Signer是否優(yōu)先于(dominate)目標(biāo)進(jìn)程的Signer,如果是,不需要遵守限制;
其他情況,需要遵守限制。
RtlProtectedAccess表如下所示:
RTL_PROTECTED_ACCESS RtlProtectedAccess[] =
{
// Domination, Process, Thread,
// Mask, Restrictions, Restrictions,
{ 0, 0, 0}, //PsProtectedSignerNone Subject To Restriction Type
{ 2, 0x000fc7fe, 0x000fe3fd}, //PsProtectedSignerAuthenticode 0y00000010
{ 4, 0x000fc7fe, 0x000fe3fd}, //PsProtectedSignerCodeGen 0y00000100
{ 8, 0x000fc7ff, 0x000fe3ff}, //PsProtectedSignerAntimalware 0y00001000
{ 0x10, 0x000fc7ff, 0x000fe3ff}, //PsProtectedSignerLsa 0y00010000
{ 0x3e, 0x000fc7fe, 0x000fe3fd}, //PsProtectedSignerWindows 0y00111110
{ 0x7e, 0x000fc7ff, 0x000fe3ff}, //PsProtectedSignerTcb 0y01111110
};
其中,Process Restrictions以及Thread Restrictions分別代表著進(jìn)程和線(xiàn)程的權(quán)限限制,詳解如下圖:

我們可以通過(guò)NtDebugActiveProcess以及NtCreateUserProcess的演示代碼來(lái)理解保護(hù)進(jìn)程及其限制的邏輯:
- static
- NTSTATUS
- NtDebugActiveProcess(
- __in HANDLE ProcessHandle,
- __in HANDLE DebugObjectHandle
- )
- {
- PEPROCESS target_process = nullptr;
- NTSTATUS result = ObReferenceObjectByHandleWithTag( ProcessHandle, &target_process);
- if (! NT_SUCCESS(result))
- return result;
- PEPROCESS host_process = PsGetCurrentProcess();
- if (host_process == target_process)
- return 0xC0000022;
- if (PsInitialSystemProcess == target_process)
- return 0xC0000022;
- if (PspCheckForInvalidAccessByProtection(PreviousMode, host_process->Protection, target_process->Protection))
- return 0xC0000712;
- // ......
- }
- static
- NTSTATUS
- NtCreateUserProcess(
- __out PHANDLE ProcessHandle,
- __out PHANDLE ThreadHandle,
- __in ACCESS_MASK ProcessDesiredAccess ,
- __in ACCESS_MASK ThreadDesiredAccess ,
- __in POBJECT_ATTRIBUTES ProcessObjectAttributes OPTIONAL ,
- __in POBJECT_ATTRIBUTES ThreadObjectAttributes OPTIONAL ,
- __in ULONG CreateProcessFlags,
- __in ULONG CreateThreadFlags,
- __in PRTL_USER_PROCESS_PARAMETERS ProcessParameters ,
- __in PVOID Parameter9,
- __in PNT_PROC_THREAD_ATTRIBUTE_LIST AttributeList
- )
- {
- ACCESS_MASK allowed_process_access = ProcessDesiredAccess ;
- ACCESS_MASK allowed_thread_access = ThreadDesiredAccess ;
- PS_PROTECTION protection = ProcessContext.member_at_0x20;
- if (PspCheckForInvalidAccessByProtection(PreviousMode, host_process->Protection, target_process->Protection))
- {
- // 1 << 0x19 = 0x80000, WRITE_OWNER
- if ( MAXIMUM_ALLOWED == ProcessDesiredAccess )
- allowed_process_access = (((~RtlProtectedAccess[protection.Signer].DeniedProcessAccess) & 0x1FFFFF) | ProcessDesiredAccess) & (~(1 << 0x19));
- if ( MAXIMUM_ALLOWED == ThreadDesiredAccess )
- allowed_thread_access = (((~RtlProtectedAccess[protection.Signer].DeniedThreadAccess) & 0x1FFFFF) | ThreadDesiredAccess) & (~(1 << 0x19));
- }
- //PspInsertProcess(..., allowed_process_access, ...);
- //PspInsertThread(..., allowed_thread_access, ...);
- }
0x06 結(jié)語(yǔ)
Windows操作系統(tǒng)中的權(quán)限檢查機(jī)制以及安全系統(tǒng)一直都在不斷地進(jìn)化著,探索其實(shí)現(xiàn)的細(xì)節(jié)以及推測(cè)其背后的設(shè)計(jì)理念是一件非常有趣有益的事情,本文僅僅是作者在探索中的小結(jié),難免有疏漏甚至錯(cuò)誤,更加詳細(xì)的內(nèi)容可以參考下列材料,或者直接分析和調(diào)試Windows內(nèi)核,以了解最新最真實(shí)的Windows。
James Forshaw: The Windows Sandbox Paradox
James Forshaw: A Link to the Past: Abusing Symbolic Links on Windows
Alex Ionescu: The Evolution of Protected Processes Part 1: Pass-the-Hash Mitigations in Windows 8.1