淺析Windows的訪問許可權檢查機制

wyzsk發表於2020-08-19
作者: DanielKing · 2016/01/07 17:27

Author: DanielKing

0x00 簡介


在作業系統中,當我們提到安全的時候,意味著有一些資源需要被保護,在Windows作業系統中,這些被保護的資源大多以物件(Object)的形式存在,物件是對資源的一種抽象。每個物件都可以擁有自己的安全描述符(Security Descriptor),用來描述它能夠被誰、以何種方式而訪問。這些物件是客體,那麼訪問這些物件的主體是什麼呢?這些主體就是作業系統中的各個程式,更準確地說是這些程式中的每個執行緒。每個程式都有一個基本令牌 (Primary Token),可以被程式中的每個執行緒所共享,而有些執行緒比較特殊,它們正在模擬(Impersonating)某個客戶端的身份,因此擁有一個模擬令牌(Impersonation Token),對於這些執行緒來說,有效令牌就是模擬令牌,而其他執行緒的有效令牌則是其所屬程式的基本令牌。當主體嘗試去訪問客體時,作業系統會執行訪問許可權檢查(Access Check),具體來說,是用主體的有效令牌與客體的安全描述符進行比對,從而確定該次訪問是否合法。為了提高效率,訪問許可權檢查(Access Check)提供了一種快取機制,即只有當一個物件被一個程式建立或者開啟時,才會進行訪問許可權檢查,訪問許可權檢查的結果會被快取到程式的控制程式碼表(Handle Table)中,該程式對該物件的後續操作只需要查詢控制程式碼表中內容即可確定訪問的合法性,而不必每次都執行開銷更大的訪問許可權檢查(Access Check)。因為物件和程式都有各自的繼承層次,所以物件的安全描述符和程式的令牌從邏輯上講也是可以繼承的,比如檔案系統中的檔案可以繼承其所在目錄的安全描述符,子程式可以繼承父程式的令牌,這種繼承機制使讓物件和程式擁有了預設的安全屬性,因此,在一個系統中的安全描述符和令牌的例項數目非常有限,大多數情況下是從父輩們繼承過來的預設值。

模擬執行緒

0x01 訪問許可權檢查


訪問許可權檢查包含了多個維度上的檢查:

DACL檢查

DACL(自主訪問控制表,Discretionary Access Control List)檢查是最基本的訪問許可權檢查。

安全描述符

在安全描述符中存在著一個DACL表(見安全描述符圖解),裡面描述了擁有何種身份的主體申請的何種訪問許可權會被允許或者拒絕。DACL表中的每個表項擁有如下的結構:

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.

其中,對於DACL來說,AceType可以有

#!cpp
#define ACCESS_ALLOWED_ACE_TYPE                 (0x0)
#define ACCESS_DENIED_ACE_TYPE                  (0x1)

兩種可能;Mask是一個代表相關許可權集合的點陣圖;主體的身份使用Sid(Security Identifier)來表示,這是一個變長的資料結構:

#!cpp
kd> dt nt!_SID
   +0x000 Revision         : UChar
   +0x001 SubAuthorityCount : UChar
   +0x002 IdentifierAuthority : _SID_IDENTIFIER_AUTHORITY
   +0x008 SubAuthority     : [1] Uint4B

Sid是Windows安全系統中一個基本型別,可以用來唯一標識多種不同種類的實體,比如標識使用者身份與組身份,標識完整性級別、可信賴級別,AppContainer中的Capability等等。

DACL檢查過程是這樣的,如果物件的安全描述符為空,代表著這個物件不受任何保護,即可以被任何令牌代表的主體訪問;如果物件擁有一個非空的安全描述符,但是安全描述符中的Dacl成員為空,代表該物件沒有顯式地賦予任何主體任何訪問許可權,即不允許被訪問;如果Dacl成員非空,就按照表中的順序逐一與當前主體的身份Sid進行比對,直到該主體所請求的許可權被全部顯式允許,或者某一請求的許可權被顯式地拒絕,檢查結束;如果直到Dacl表檢查結束,主體申請的許可權仍未被全部顯式允許,那麼仍然代表拒絕。

根據以上規則可知,DACL中各個表項的順序對訪問許可權檢查的結果至關重要,比如說有一條有關某主體的顯式拒絕的表項位於表的最後,如果位於其之前的表項已經顯式允許了這一主體申請的許可權,那麼這條顯式拒絕的表項將起不到任何作用。基於此種原因,如果透過Windows右鍵安全選項選單新增的DACL表項,顯式拒絕的表項永遠被置於顯式允許表項前面,以保證其有效性。直接透過API新增則不受此種限制。

特權(Privileges)以及超級特權(Super Privileges)檢查

所謂特權,是指那些不與某一具體物件相關的、影響整個系統的訪問許可權。而超級特權是這樣的一些特權,你一旦擁有其中的一項超級特權,就擁有了取得所有其他特權和許可權的潛質,比如SeDebugPrivilege,在一般情況下,一旦擁有該特權,就可以任意讀寫目標程式的記憶體,而不僅僅侷限於開啟該程式獲得控制程式碼時得到的那些許可權及特權。

完整性級別(Integrity Level)和強制策略(Mandatory Policy)檢查

完整性級別(IL)機制是Windows Vista引入的重要的安全機制,建立在其基礎上的沙盒機制十分簡單又異常強大。

受限令牌(Restricted Token)檢查

透過建立受限令牌,可以獲得一個普通令牌所有擁有的許可權集合的一個子集,用來進行一些低許可權操作,降低安全風險。

AppContainer's Capabilities檢查

LowBox令牌以及AppContainer's Capabilities組身份是從Windows 8開始引入的安全機制,它提供了另一種更加複雜的沙盒機制。Capabilities與Android系統中對於App許可權限制非常類似。

可信級別(Trust Level)檢查

完整性級別檢查是一種粗粒度的、相對穩定的訪問許可權控制手段;伴隨著Windows的不斷進化,對於簽名機制的依賴越來越明顯,簽名代表了一種可信賴程度。筆者認為,Windows正在逐漸建立一套完善的基於可信賴級別的許可權檢查機制,受保護程式(Protected Process)就是這是這套機制中一部分。

上面的內容簡單介紹了Windows的許可權檢查模型,以及許可權檢查的維度;下面將介紹一些實現細節。

0x02 令牌


TOKEN結構體

Token結構體是訪問許可權檢查中的代表主體身份的核心資料結構,下面展示的是Windows 10 x64平臺下的構成。

#!cpp
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

我們比較關注其中的特權點陣圖和三個代表主體身份的Sid陣列:UserAndGroups,RestrictedSids,Capabilities。

_TOKEN結構體

Privileges點陣圖

特權在使用者態是透過LUID來表示,在核心結構體_TOKEN中是使用三個點陣圖來表示:

#!cpp
kd> dt _SEP_TOKEN_PRIVILEGES
nt!_SEP_TOKEN_PRIVILEGES
   +0x000 Present          : Uint8B
   +0x008 Enabled          : Uint8B
   +0x010 EnabledByDefault : Uint8B

分別代表當前的主體可以選用的特權集合(Present)、已經開啟的特權集合(Enabled)和預設開啟的特權集合(EnabledByDefault),後兩個集合應該是Present集合的子集。

與代表主體身份的Sid陣列不同,特權集合的表示簡單,而且沒有任何保護。從使用者態透過API只能開啟或者關閉某一項已經存在的特權,而不能增加可選的特權,換句話說,使用者態只能修改Enabled特權集合,而不能修改Present特權集合;從核心態可以直接修改Present特權集合,比如給普通程式增加SeAssignPrimaryTokenPrivilege特權,以便為子程式顯式指定令牌,而不是繼承當前程式的令牌,可以達到擴大子程式許可權的效果。

代表主體身份的Sid陣列

三個代表主體身份的Sid陣列分別是:

UserAndGroups:

代表著主體的普通使用者身份和組身份,是不可或缺的成員;

RestrictedSids:

可選成員,如果不為空,則代表著當前的令牌是受限令牌,受限令牌透過從普通令牌中過濾掉一些比較敏感的身份轉化而來,受限令牌中的UserAndGroups成員與普通令牌相同,但是RestriectedSids成員不為空,裡面儲存著過濾後的身份子集;由於在訪問許可權檢查時,三個身份Sid陣列要同時進行檢查,只有結果都透過才允許該次訪問,因此透過增加代表著受限制的許可權集合的RestrictedSids成員,既達到了限制令牌許可權的目的,又在UserAndGroups成員中保留了原有令牌的完整身份資訊。

Capabilities:

可選成員,僅用於AppContainer,其中的Sid代表著與App相關的身份,比如擁有連線網路、訪問當前位置等許可權的身份。

這三個Sid陣列都關聯了雜湊資訊,以保護其完整性,因此,即使從核心態直接修改,也會因為無法透過完整性驗證而失敗。不過好在雜湊的演算法非常簡單,下面展示的就是Windows 10 x64平臺下面該演算法的C++演示程式碼:

#!cpp
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;
}

該演算法有兩處明顯的侷限,只計算每個Sid的最後一個Sub Authority的最低位位元組,以及最多隻有前64個Sid參與計算。

UAC與關聯令牌

UAC是Vista版本引入的比較重要的安全機制,很多使用者抱怨它帶來的不便性,然而它就像Linux作業系統中的sudo一樣,是保護系統安全的一道重要屏障。當使用者登入Windows時,作業系統會為使用者生成一對初始令牌,分別是代表著使用者所擁有的全部許可權的完整版本令牌(即管理員許可權令牌),以及被限制管理員許可權後的普通令牌,二者互為關聯令牌;此後,代表使用者的程式所使用的令牌都是由普通令牌繼承而來,用來進行常規的、非敏感的操作;當使用者需要進行一些需要管理員許可權的操作時,比如安裝軟體、修改重要的系統設定時,都會透過彈出提權對話方塊的形式提示使用者面臨的風險,徵求使用者的同意,一旦使用者同意,將會切換到當前普通令牌關聯的管理員許可權令牌,來進行敏感操作。透過這種與使用者互動的方式,避免一些惡意程式在後臺稍稍執行敏感操作。

關聯令牌是透過Logon Session來實現的,下圖展示了其大致原理:

關聯令牌

並非所有的令牌都有關聯令牌,比如一直執行在較高許可權下不需要進行提權操作的令牌。

0x03 物件


物件的結構

物件是對作業系統中需要保護和集中管理的資源的一種抽象,物件擁有統一的頭部,儲存著抽象出來的資訊。

#!cpp
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

物件頭

物件的頭部包含著指向安全描述符的指標。

安全描述符

安全描述符儲存著作為訪問許可權檢查客體的物件的主要資訊。它包含著4個關鍵成員,根據這4個成員是嵌入在結構體的尾部,還是引用自外部快取位置的差異,可以分為兩種不同的結構形式:

#!cpp
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是系統訪問控制表(System Access Control List)的簡稱,裡面可以包含當前物件的審計(Audit)、完整性級別(Integrity Level)以及可信賴級別(Trust Level)等資訊,與前面提到的Dacl共用同一結構體。

可選頭部

物件除了擁有共同的固定頭部外,還可以有可選的頭部,儲存著名稱等可選資訊。透過固定頭部的InfoMask成員查表可以得到可選頭部的位置,如圖所示:

可選頭部

物件目錄

只有部分物件擁有名稱資訊,它們被稱為命名物件。命名物件的主要作用是方便物件在不同的程式中共享,它們被按類別編纂成物件目錄,因此可以透過在物件目錄中的路徑資訊找到該物件。物件目錄的實現如下圖所示:

物件目錄

物件型別

物件型別是物件中非常重要的資訊,Windows將物件的型別資訊從同一類物件中抽象出來,儲存成一個單獨的型別物件。系統中全部的型別物件被集中放置在一個表中,物件透過維護一個指向該表的索引(TypeIndex)來表明當前物件的型別。這個索引值直接儲存在物件的頭部,而物件體與物件頭部直接相鄰,如果物件體被損壞,有可能導致頭部的索引值被改變,使所謂的型別混淆(Type Confusion)利用成為可能。為了緩解這一問題,Windows 10對該索引值做了特殊保護,如下圖所示:

型別索引保護

這種保護方式簡單而強大,新的索引值由3部分經過異或操作得到:型別物件在型別物件表中的真實索引值,物件頭部地址的第二個位元組(即第8到第15位),儲存在ObHeaderCookie全域性變數中的因每次系統啟動而異的Cookie位元組。其中,ObHeaderCookie的引入,使同一型別的物件在不同機器上,甚至是同一機器上兩次啟動之間的索引值不同,然而這樣並不足以緩解型別混淆利用,我們還可以利用資訊洩露(Info Leak)來繞過(Bypass)該保護,因此還引入了物件頭部地址,使得在同一時刻、同一系統中的兩個相同型別物件的不同例項間的索引值也不相同,從而有效地緩解型別混淆利用。

0x04 完整性級別檢查


完整性級別(IL)檢查是沙盒的最簡單實現方式,透過完整性級別在物件和程式層次之間的繼承關係,就如同在作業系統中建立起了“世襲制度”。透過嚴格控制完整性級別的繼承規則,以及設定嚴格的完整性級別檢查制度,可以保證“出身低微”的主體無法訪問到“出身高貴”的客體資源。

完整性擁有以下幾個級別:

#!bash
SeUntrustedMandatorySid
SeLowMandatorySid
SeMediumMandatorySid
SeHighMandatorySid
SeSystemMandatorySid

主體的預設完整性級別是SeUntrustedMandatorySid,而客體的預設完整性級別是SeMediumMandatorySid,這種差異也進一步地強化了這種“世襲制度”。

0x05 保護程式


保護程式(Protected Process)是Windows作業系統為了保護某些關鍵程式,防止其被普通程式除錯、注入、甚至是讀取記憶體資訊而建立起來的一種安全機制。

保護程式與其他普通程式的區別在於,保護程式的Protection成員不為0。

#!cpp
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

保護程式的Type成員可以代表兩種保護型別:Protected Process(PP),Protected Process Lite(PPL),兩者的區別在於PP保護程式擁有更高的許可權。保護程式的Signer成員代表該程式的簽名釋出者的類別。對於Signer為PsProtectedSignerWindows(5)和PsProtectedSignerTcb(6)的保護程式,其Type和Signer資訊會被抽取出來,組裝成一個Sid,代表著該程式的可信賴級別,儲存到基本令牌中的TrustLevelSid成員中。當一個令牌中的TrustLevelSid被使用時,需要保證與當前程式的Protection資訊保持同步,這主要是為了應對令牌在不同程式間傳遞時(比如子程式繼承父程式的令牌)導致的TrustLevelSid成員過時的情形。

當除錯或者建立一個程式時,會呼叫核心函式PspCheckForInvalidAccessByProtection進行許可權檢查,該函式根據當前程式以及目標程式的Protection來判定當前操作是否需要遵守保護程式規定的許可權限制,具體判定規則如下:

  • 如果操作來自於核心態程式碼,不需要遵守限制;
  • 如果目標程式的Protection成員為空,表示目標程式不是保護程式,不需要遵守限制;
  • 如果當前程式是PP型別保護程式,該型別保護程式擁有最高許可權,不需要遵守限制;
  • 如果當前程式與目標程式都是PPL型別保護程式,需要根據RtlProtectedAccess表來判斷當前程式的Signer是否優先於(dominate)目標程式的Signer,如果是,不需要遵守限制;
  • 其他情況,需要遵守限制。

RtlProtectedAccess表如下所示:

#!cpp
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分別代表著程式和執行緒的許可權限制,詳解如下圖:

保護程式與執行緒的許可權限制

我們可以透過NtDebugActiveProcess以及NtCreateUserProcess的演示程式碼來理解保護程式及其限制的邏輯:

#!cpp
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 結語


Windows作業系統中的許可權檢查機制以及安全系統一直都在不斷地進化著,探索其實現的細節以及推測其背後的設計理念是一件非常有趣有益的事情,本文僅僅是作者在探索中的小結,難免有疏漏甚至錯誤,更加詳細的內容可以參考下列材料,或者直接分析和除錯Windows核心,以瞭解最新最真實的Windows。

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章