Windows系统是支持多用户的。每个文件可以设置一个访问控制表(即ACL),在ACL中规定每个用户、每个组对该文件的访问权限。不过,只有Ntfs文件系统中的文件才支持ACL。

(Ntfs文件系统中,每个文件的ACL是作为文件的一个附加属性保存在文件中的)。

不仅ntfs文件支持ACL机制,每个内核对象也支持ACL,不过内核对象的ACL保存在对象头部的安全属性字段中,只存在于内存,对象一销毁,ACL就跟着销毁。因此,内核对象的ACL是临时的,文件的ACL则是永久保存在磁盘上的。文件的ACL由文件的创建者设置后保存在文件中,以后只有创建者和管理员才可以修改ACL,内核对象的ACL由对象的创建者在创建时指定。

 

Windows系统中为每个用户、组、机器指定了一个ID,叫SID。每个用户登录到系统后,每当创建一个进程时,就会为进程创建一个令牌(进程的令牌叫主令牌),该令牌包含了用户、组、特权信息。由于子进程在创建时会继承父进程的令牌,所以一个用户创建的所有进程的令牌都是一样的,包含着相同的用户、组、特权等其他信息,只是令牌ID不同而已。换个角度看,令牌实际上相当于用户身份,进程要访问对象时,就出示它的令牌让系统检查,向系统表明自己是谁,在哪几个组中。

 

这样,当有了令牌和ACL后,当一个进程(准确说是线程)要访问一个对象时,系统就会检查该进程的令牌,申请的访问权限,然后与ACL比较,看看是否满足权限,不满足的话就拒绝访问。

 

下面我们看看相关的数据结构

typedef struct _SID {  //用户ID、组ID、机器ID

  UCHAR
Revision;//版本号

  UCHAR
SubAuthorityCount;//RID数组元素个数,即ID级数,最大支持8

  SID_IDENTIFIER_AUTHORITY
IdentifierAuthority;//该ID的签发机关,6B

  ULONG
SubAuthority[ANYSIZE_ARRAY];//RID数组,即NID

}
SID, *PISID;

一个ID就像一个文件路径一样,由签发机关 + NID组成。

Windows中有几种预定义的签发机关

#define SECURITY_NULL_SID_AUTHORITY         {0,0,0,0,0,0}

#define SECURITY_WORLD_SID_AUTHORITY        {0,0,0,0,0,1}   //世界签发机关

#define SECURITY_LOCAL_SID_AUTHORITY        {0,0,0,0,0,2}   //本机签发机关

#define SECURITY_CREATOR_SID_AUTHORITY      {0,0,0,0,0,3}  

#define SECURITY_NON_UNIQUE_AUTHORITY       {0,0,0,0,0,4}

#define SECURITY_NT_AUTHORITY               {0,0,0,0,0,5}   //NT域签发机关

#define SECURITY_RESOURCE_MANAGER_AUTHORITY
{0,0,0,0,0,9}  

 

typedef struct _TOKEN

{

    TOKEN_SOURCE
TokenSource;                        

    LUID
TokenId;                                     令牌ID

    LUID
AuthenticationId;                           

    LUID
ParentTokenId;                              

    LARGE_INTEGER
ExpirationTime;                     过期时间

    struct
_ERESOURCE *TokenLock;                    

    SEP_AUDIT_POLICY  AuditPolicy;                   

    LUID ModifiedId;                                 

    ULONG
SessionId;                                 

    ULONG
UserAndGroupCount;                          含有的用户、组总数

    ULONG
RestrictedSidCount;                        

    ULONG
PrivilegeCount;                             含有的特权数量

    ULONG
VariableLength;                            

    ULONG
DynamicCharged;                            

    ULONG
DynamicAvailable;                          

    ULONG
DefaultOwnerIndex;                     令牌的默认拥有者在UserAndGroups数组中的位置

    PSID_AND_ATTRIBUTES
UserAndGroups;                关键。包含的一个用户、N个组(一个‘数组’)

    PSID_AND_ATTRIBUTES
RestrictedSids;              

    PSID
PrimaryGroup;                                令牌的基本组ID(即拥有者所属的基本组)

    PLUID_AND_ATTRIBUTES
Privileges;                  关键。包含的特权

    PULONG
DynamicPart;                              

    PACL
DefaultDacl;                                

    TOKEN_TYPE
TokenType;                             令牌类型(自己的/模拟的)

    SECURITY_IMPERSONATION_LEVEL
ImpersonationLevel;  模拟级别

    ULONG
TokenFlags;                                

    BOOLEAN
TokenInUse;                               是否已被指派成了某个进程的令牌

    PVOID
ProxyData;                                 

    PVOID
AuditData;                                 

    LUID
OriginatingLogonSession;                    

    ULONG
VariablePart;                              

}
TOKEN, *PTOKEN;

一个令牌最重要的信息便是它所包含的【特权、用户、组】

 

 

下面的函数用于创建一个SID

NTSTATUS

RtlAllocateAndInitializeSid(PSID_IDENTIFIER_AUTHORITY
IdentifierAuthority,//签发机关

                  UCHAR SubAuthorityCount,//级数

                  ULONG SubAuthority0,

                  ULONG SubAuthority1,

                  ULONG SubAuthority2,

                  ULONG SubAuthority3,

                  ULONG SubAuthority4,

                  ULONG SubAuthority5,

                  ULONG SubAuthority6,

                  ULONG SubAuthority7,

                  PSID *Sid) //返回

{

  PISID
pSid;

  if
(SubAuthorityCount > 8)

    return
STATUS_INVALID_SID;

  pSid
= RtlpAllocateMemory(RtlLengthRequiredSid(SubAuthorityCount),TAG_SID);

  pSid->Revision = SID_REVISION;//固定为1

  pSid->SubAuthorityCount = SubAuthorityCount;//级数

  memcpy(&pSid->IdentifierAuthority,IdentifierAuthority,sizeof(SID_IDENTIFIER_AUTHORITY));

  switch
(SubAuthorityCount)

  {

      case
8:

         pSid->SubAuthority[7] = SubAuthority7;

      case
7:

         pSid->SubAuthority[6] = SubAuthority6;

      case
6:

         pSid->SubAuthority[5] = SubAuthority5;

      case
5:

         pSid->SubAuthority[4] = SubAuthority4;

      case
4:

         pSid->SubAuthority[3] = SubAuthority3;

      case
3:

         pSid->SubAuthority[2] = SubAuthority2;

      case 2:

         pSid->SubAuthority[1] = SubAuthority1;

      case
1:

         pSid->SubAuthority[0] = SubAuthority0;

         break;

  }

  *Sid
= pSid;

  return
STATUS_SUCCESS;

}

 

SID本身是一个结构体,但SID还有另外一种通俗的表示法:“S-版本号-签发机关-N级ID”。

如“S-1-5-23223-23422-286-1025”表示系统中的第24个用户,就是一个4级的SID,其中签发机关为5,表示NT域。

Windows中预定义了些常见的组ID,如

S-1-1-0表示everyone组

S-1-2-0表示Users组

S-1-3-0表示Creators组

前面说了,一个进程在创建时会继承它父进程的令牌,我们看

NTSTATUS  PspInitializeProcessSecurity(IN PEPROCESS Process, IN PEPROCESS Parent OPTIONAL)

{

    NTSTATUS
Status = STATUS_SUCCESS;

    PTOKEN
NewToken, ParentToken;

    if
(Parent)

    {

        ParentToken
= PsReferencePrimaryToken(Parent);//获得父进程的令牌

        //克隆父进程的令牌(但令牌ID不同)

        Status
= SeSubProcessToken(ParentToken,&NewToken,TRUE,0);

        ObFastDereferenceObject(&Parent->Token, ParentToken);

        if
(NT_SUCCESS(Status))

            ObInitializeFastReference(&Process->Token, NewToken);//设置为子进程的令牌

    }

    else

    {

        ObInitializeFastReference(&Process->Token, NULL);

        SeAssignPrimaryToken(Process, PspBootAccessToken);//指派令牌

    }

    return
Status;

}

这样,同属于一个用户创建的所有进程的令牌都是一样的,本来就应该如此。

但是进程不是行为的主体,具体要去访问对象时,不是由进程去访问,而是由线程去访问。所以,每个线程也得有令牌。默认情况下,每个线程的令牌就是其所属进程的令牌。但是,线程可以模拟使用其他进程的令牌,用来以其他线程的名义去访问对象。为此,ETHREAD结构中有一个ImpersonationInfo字段,是一个PS_IMPERSONATION_INFORMATION结构指针,记录了该线程使用的模拟令牌信息。

 

 

下面的函数用来创建一个令牌(令牌本身也是一种内核对象)

NTSTATUS

NtCreateToken(OUT PHANDLE TokenHandle,//返回句柄

              IN ACCESS_MASK DesiredAccess,

              IN POBJECT_ATTRIBUTES ObjectAttributes,

              IN TOKEN_TYPE TokenType,//主令牌/模拟令牌

              IN PLUID AuthenticationId,

              IN PLARGE_INTEGER ExpirationTime,//过期时间

              IN PTOKEN_USER TokenUser,//该令牌代表的用户

              IN PTOKEN_GROUPS TokenGroups,//该令牌含有的所有组

              IN PTOKEN_PRIVILEGES
TokenPrivileges,//该令牌含有的所有特权

              IN PTOKEN_OWNER TokenOwner,//令牌的默认拥有者

              IN PTOKEN_PRIMARY_GROUP
TokenPrimaryGroup,//令牌的基本组

              IN PTOKEN_DEFAULT_DACL
TokenDefaultDacl,//默认的ACL

              IN PTOKEN_SOURCE TokenSource)

{

    HANDLE
hToken;

    KPROCESSOR_MODE
PreviousMode;

    ULONG
nTokenPrivileges = 0;

    LARGE_INTEGER
LocalExpirationTime = {{0, 0}};

    NTSTATUS
Status;

    PreviousMode
= ExGetPreviousMode();

    if
(PreviousMode != KernelMode)//if来自用户模式发起的调用

    {

        _SEH2_TRY

        {

            ProbeForWriteHandle(TokenHandle);

            ProbeForRead(AuthenticationId,sizeof(LUID),sizeof(ULONG));

            LocalExpirationTime
= ProbeForReadLargeInteger(ExpirationTime);

            ProbeForRead(TokenUser,sizeof(TOKEN_USER),sizeof(ULONG));

            ProbeForRead(TokenGroups,sizeof(TOKEN_GROUPS),sizeof(ULONG));

            ProbeForRead(TokenPrivileges,sizeof(TOKEN_PRIVILEGES),sizeof(ULONG));

            ProbeForRead(TokenOwner,sizeof(TOKEN_OWNER),sizeof(ULONG));

            ProbeForRead(TokenPrimaryGroup,sizeof(TOKEN_PRIMARY_GROUP),sizeof(ULONG));

            ProbeForRead(TokenDefaultDacl,sizeof(TOKEN_DEFAULT_DACL),sizeof(ULONG));

            ProbeForRead(TokenSource,sizeof(TOKEN_SOURCE),sizeof(ULONG));

            nTokenPrivileges
= TokenPrivileges->PrivilegeCount;

        }

        。。。

    }

    else

    {

        nTokenPrivileges
= TokenPrivileges->PrivilegeCount;

        LocalExpirationTime
= *ExpirationTime;

    }

    Status
= SepCreateToken(&hToken,PreviousMode,DesiredAccess,ObjectAttributes,TokenType,

                            ObjectAttributes->SecurityQualityOfService->ImpersonationLevel,

                            AuthenticationId,

                            &LocalExpirationTime,

                            &TokenUser->User,

                            TokenGroups->GroupCount,

                            TokenGroups->Groups,

                            0,

                            nTokenPrivileges,

                            TokenPrivileges->Privileges,

                            TokenOwner->Owner,

                            TokenPrimaryGroup->PrimaryGroup,

                            TokenDefaultDacl->DefaultDacl,

                            TokenSource,

                            FALSE);

    if
(NT_SUCCESS(Status))

    {

        _SEH2_TRY

        {

            *TokenHandle = hToken;

        }

        。。。

    }

    return
Status;

}

 

NTSTATUS

SepCreateToken(OUT PHANDLE TokenHandle,

               IN KPROCESSOR_MODE PreviousMode,

               IN ACCESS_MASK DesiredAccess,

               IN POBJECT_ATTRIBUTES
ObjectAttributes,

               IN TOKEN_TYPE TokenType,

               IN SECURITY_IMPERSONATION_LEVEL
ImpersonationLevel,

               IN PLUID AuthenticationId,

               IN PLARGE_INTEGER ExpirationTime,

               IN PSID_AND_ATTRIBUTES
User,

               IN ULONG GroupCount,

               IN PSID_AND_ATTRIBUTES
Groups,

               IN ULONG GroupLength,

               IN ULONG PrivilegeCount,

               IN PLUID_AND_ATTRIBUTES
Privileges,

               IN PSID Owner,//令牌的默认拥有者用户ID

               IN PSID PrimaryGroup,//令牌的基本组ID

               IN PACL DefaultDacl,

               IN PTOKEN_SOURCE TokenSource,

               IN BOOLEAN SystemToken)

{

    PTOKEN
AccessToken;

    LUID
TokenId;

    LUID
ModifiedId;

    PVOID
EndMem;

    ULONG
uLength;

    ULONG
i;

    NTSTATUS
Status;

    ULONG
TokenFlags = 0;

    for
(i = 0; i
< GroupCount; i++)

    {

        if
(Groups[i].Attributes & SE_GROUP_MANDATORY)
//默认启用所有强制类型的组

            Groups[i].Attributes |= (SE_GROUP_ENABLED
| SE_GROUP_ENABLED_BY_DEFAULT);

        if
(RtlEqualSid(SeAliasAdminsSid,
Groups[i].Sid))

            TokenFlags
|= TOKEN_HAS_ADMIN_GROUP;//标记本令牌中含有一个管理员组,提高效率用

    }

    for
(i = 0; i
< PrivilegeCount; i++)

    {

        if
(((RtlEqualLuid(&Privileges[i].Luid, &SeChangeNotifyPrivilege)) &&

            (Privileges[i].Attributes & SE_PRIVILEGE_ENABLED)))

        {

            TokenFlags
|= TOKEN_HAS_TRAVERSE_PRIVILEGE;//标记本令牌含有对象目录遍历特权

        }

    }

 

    ZwAllocateLocallyUniqueId(&TokenId);//分配一个唯一的令牌ID

ZwAllocateLocallyUniqueId(&ModifiedId);//再分配一个唯一的ModifiedId

 

//关键。创建一个令牌内核对象

    Status
= ObCreateObject(PreviousMode,
SepTokenObjectType,//令牌类型

                            ObjectAttributes,PreviousMode,NULL,sizeof(TOKEN),

                            0,0, (PVOID*)&AccessToken);

    RtlZeroMemory(AccessToken, sizeof(TOKEN));

    AccessToken->TokenLock = &SepTokenLock;

    RtlCopyLuid(&AccessToken->TokenSource.SourceIdentifier, &TokenSource->SourceIdentifier);

    memcpy(AccessToken->TokenSource.SourceName,TokenSource->SourceName,

                                                           sizeof(TokenSource->SourceName));

    RtlCopyLuid(&AccessToken->TokenId,
&TokenId);//填写令牌ID

    RtlCopyLuid(&AccessToken->AuthenticationId,
AuthenticationId);

    AccessToken->ExpirationTime = *ExpirationTime;

    RtlCopyLuid(&AccessToken->ModifiedId,
&ModifiedId);

    AccessToken->UserAndGroupCount = GroupCount
+ 1;//一个用户N个组

    AccessToken->PrivilegeCount = PrivilegeCount;

    AccessToken->TokenFlags = TokenFlags;

    AccessToken->TokenType = TokenType;

    AccessToken->ImpersonationLevel = ImpersonationLevel;

 

    uLength
= sizeof(SID_AND_ATTRIBUTES)
* AccessToken->UserAndGroupCount;

   
uLength += RtlLengthSid(User);

    for
(i = 0; i
< GroupCount; i++)

        uLength
+= RtlLengthSid(Groups[i].Sid);

 

    AccessToken->UserAndGroups =

    (PSID_AND_ATTRIBUTES)ExAllocatePoolWithTag(PagedPool,uLength,’uKOT’);

    EndMem
= &AccessToken->UserAndGroups[AccessToken->UserAndGroupCount];

    //填写用户SID到令牌中

    Status
= RtlCopySidAndAttributesArray(1,User,uLength,AccessToken->UserAndGroups,

                                         
EndMem,&EndMem,&uLength);

    if
(NT_SUCCESS(Status))

{

   
//填写所有组SID到令牌中

        Status
= RtlCopySidAndAttributesArray(GroupCount,Groups,uLength,

                                             
&AccessToken->UserAndGroups[1],

                                              EndMem,&EndMem,&uLength);

    }

 

    if
(NT_SUCCESS(Status))

    {

       //查找令牌的基本组和拥有者在UserAndGroups数组中的位置,记录在令牌中

  Status = SepFindPrimaryGroupAndDefaultOwner(AccessToken,PrimaryGroup,Owner);

}

 

    //再将所有特权填写到令牌中

    if
(NT_SUCCESS(Status))

    {

        uLength
= PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES);

        AccessToken->Privileges =

        (PLUID_AND_ATTRIBUTES)ExAllocatePoolWithTag(PagedPool,uLength,’pKOT’);

        if
(PreviousMode != KernelMode)

        {

            _SEH2_TRY

            {

                RtlCopyMemory(AccessToken->Privileges,Privileges,

                              PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES));

            }

            。。。

        }

        else

        {

            RtlCopyMemory(AccessToken->Privileges,Privileges,

                          PrivilegeCount * sizeof(LUID_AND_ATTRIBUTES));

        }

    }

 

    if
(NT_SUCCESS(Status))

    {

        AccessToken->DefaultDacl =

        (PACL)
ExAllocatePoolWithTag(PagedPool,DefaultDacl->AclSize,’kDOT’);

        memcpy(AccessToken->DefaultDacl,DefaultDacl,DefaultDacl->AclSize);

    }

 

    if
(!SystemToken)

ObInsertObject(AccessToken,NULL,DesiredAccess,0,NULL,TokenHandle);//插入句柄表中

    else

        *TokenHandle
= (HANDLE)AccessToken;

    return
Status;

}

 

当用户创建了一个令牌对象后,就可以调用NtSetInformationProcess将该令牌指派给任意进程,这个函数内部最终调用下面的函数完成指派工作。

NTSTATUS

NTAPI //将指定令牌指派给指定进程(也即为指定进程设置一个令牌)

PspSetPrimaryToken(IN PEPROCESS Process,//目标进程

                   IN
HANDLE TokenHandle
OPTIONAL,//优先使用这个参数

                   IN PACCESS_TOKEN Token OPTIONAL)//当上面参数为NULL时使用这个参数

{

    KPROCESSOR_MODE
PreviousMode = ExGetPreviousMode();

    BOOLEAN
IsChild;

    PACCESS_TOKEN
NewToken = Token;

    NTSTATUS
Status, AccessStatus;

    BOOLEAN
Result, SdAllocated;

    PSECURITY_DESCRIPTOR
SecurityDescriptor;

    SECURITY_SUBJECT_CONTEXT
SubjectContext;

    if
(TokenHandle)

ObReferenceObjectByHandle(TokenHandle,TOKEN_ASSIGN_PRIMARY,SepTokenObjectType,

PreviousMode, (PVOID*)&NewToken,NULL);

    SeIsTokenChild(NewToken, &IsChild);//检查目标令牌是不是进程自己的那个

    if
(!IsChild) //实际上命名为IsSelf更合适

{

   
//如果指定令牌不是进程自己的令牌,那么必须检查当前进程是否具有把指定令牌指派给

//其他进程的特权(SeAssignPrimaryTokenPrivilege就是指派令牌 这种特权)

        if
(!SeSinglePrivilegeCheck(SeAssignPrimaryTokenPrivilege,PreviousMode))

        {

            if
(TokenHandle) ObDereferenceObject(NewToken);

            return
STATUS_PRIVILEGE_NOT_HELD;

        }

    }

    //将指定令牌指派给目标进程(也即修改那个进程的令牌字段)。

Status = PspAssignPrimaryToken(Process, NULL, NewToken);

//当更换了目标进程的令牌后,目标进程可能对它自己的进程对象的访问权限都没了,所以下面的代//码对目标进程的自我访问权限进行修正

    if
(NT_SUCCESS(Status))

{

   
//获取目标进程对象的安全描述符,即SD

        Status
= ObGetObjectSecurity(Process,&SecurityDescriptor,&SdAllocated);

        if
(NT_SUCCESS(Status))

        {

            SubjectContext.ProcessAuditId = Process;

            SubjectContext.PrimaryToken = PsReferencePrimaryToken(Process);//目标进程的主令牌

            SubjectContext.ClientToken = NULL;

            //根据目标进程对象的SD和新令牌,获得新令牌对目标进程的访问权限

            Result
= SeAccessCheck(SecurityDescriptor,&SubjectContext,FALSE,

                          MAXIMUM_ALLOWED,0,NULL,

                          &PsProcessType->TypeInfo.GenericMapping, PreviousMode,

                          &Process->GrantedAccess,

                          &AccessStatus);

            ObFastDereferenceObject(&Process->Token,SubjectContext.PrimaryToken);

            ObReleaseObjectSecurity(SecurityDescriptor, SdAllocated);

//if更换令牌后导致不能访问目标进程对象了

            if
(!Result) Process->GrantedAccess = 0;

//这个字段表示进程对象的自我进程访问权限。我们知道,每个句柄的表项内部都有一个GrantedAccess字段,记录了句柄授予的访问权限。但是,当前进程句柄(-1)、当前线程句柄(-2),这两个句柄都是伪句柄,不存在对应的句柄表项,所以就没法记录那两个句柄的访问权限,就只好记录进程对象的GrantedAccess这个字段中,表示进程对象对自我进程授予的访问权限。

            //不管如何,下面的这些自我访问权限是起码必须的,所以加上去

            Process->GrantedAccess |= (PROCESS_VM_OPERATION
|

                                      
PROCESS_VM_READ |

                                      
PROCESS_VM_WRITE |

                                       PROCESS_QUERY_INFORMATION
|

                                      
PROCESS_TERMINATE |

                                      
PROCESS_CREATE_THREAD |

                                      
PROCESS_DUP_HANDLE |

                                      
PROCESS_CREATE_PROCESS |

                                      
PROCESS_SET_INFORMATION |

                                      
STANDARD_RIGHTS_ALL |

                                      
PROCESS_SET_QUOTA);

        }

    }

    if
(TokenHandle) ObDereferenceObject(NewToken);

    return
Status;

}

 

 

//这个函数用来判断目标令牌是否就是进程自己的令牌(更名为SeIsTokenSelf更合适)

NTSTATUS  SeIsTokenChild(IN PTOKEN Token,OUT PBOOLEAN IsChild)

{

    PTOKEN
ProcessToken;

    LUID
ProcessLuid, CallerLuid;

    *IsChild
= FALSE;

    ProcessToken
= PsReferencePrimaryToken(PsGetCurrentProcess());

    ProcessLuid
= ProcessToken->TokenId;//自身令牌的IP

    ObFastDereferenceObject(&PsGetCurrentProcess()->Token,
ProcessToken);

    CallerLuid
= Token->TokenId;

    if
(RtlEqualLuid(&CallerLuid,
&ProcessLuid)) *IsChild
= TRUE;

    return
STATUS_SUCCESS;

}

 

如上,如果指定令牌不是进程自己的,就需要检查当前线程是否具有指派令牌的特权。下面的函数就是用来检查当前线程是否具有指定特权的

BOOLEAN

SeSinglePrivilegeCheck(IN LUID PrivilegeValue,//要检查的特权

                       IN KPROCESSOR_MODE PreviousMode)

{

    SECURITY_SUBJECT_CONTEXT
SubjectContext;

    PRIVILEGE_SET
Priv;//特权集,此处仅检查一个特权

    BOOLEAN
Result;

    //获得当前线程的令牌(模拟令牌、主令牌)

    SeCaptureSubjectContext(&SubjectContext);

    Priv.PrivilegeCount = 1;

    Priv.Control = PRIVILEGE_SET_ALL_NECESSARY;//检查所有特权

    Priv.Privilege[0].Luid =
PrivilegeValue;

    Priv.Privilege[0].Attributes
= SE_PRIVILEGE_ENABLED;

    //实质函数

    Result
= SePrivilegeCheck(&Priv,&SubjectContext,PreviousMode);

    SeReleaseSubjectContext(&SubjectContext);

    return
Result;

}

 

继续看

BOOLEAN

SePrivilegeCheck(PPRIVILEGE_SET Privileges,//要检查的所有特权

                 PSECURITY_SUBJECT_CONTEXT SubjectContext,//安全上下文

                 KPROCESSOR_MODE PreviousMode)

{

    PACCESS_TOKEN
Token = NULL;

 

    if
(SubjectContext->ClientToken
== NULL)

        Token
= SubjectContext->PrimaryToken;

    else

    {

        Token
= SubjectContext->ClientToken;//优先使用模拟的令牌

        if
(SubjectContext->ImpersonationLevel
< 2) //模拟令牌的模拟级别必须大于2

            return
FALSE;

    }

    //实质函数

    return
SepPrivilegeCheck(Token,Privileges->Privilege,Privileges->PrivilegeCount,

                             Privileges->Control,PreviousMode);

}

 

如上,上面这个函数会优先使用当前线程的模拟令牌,若没有,再使用所属进程的令牌,拿来进行特权检查。继续看

BOOLEAN

SepPrivilegeCheck(PTOKEN Token,

                  PLUID_AND_ATTRIBUTES Privileges,

                  ULONG PrivilegeCount,

                  ULONG PrivilegeControl,

                  KPROCESSOR_MODE PreviousMode)

{

    ULONG
I,j,k;

    if
(PreviousMode == KernelMode)
//内核模式不用进行安全检查

        return
TRUE;

    k
= 0;

    if
(PrivilegeCount > 0)

    {

        for
(i = 0; i
< Token->PrivilegeCount;
i++)

        {

            for
(j = 0; j
< PrivilegeCount; j++)

            {

                if (Token->Privileges[i].Luid.LowPart == Privileges[j].Luid.LowPart
&&

                    Token->Privileges[i].Luid.HighPart == Privileges[j].Luid.HighPart)

                {

                    if (Token->Privileges[i].Attributes & SE_PRIVILEGE_ENABLED)

                    {

                        Privileges[j].Attributes |= SE_PRIVILEGE_USED_FOR_ACCESS;

                        k++;

                    }

                }

            }

        }

}

//如果要求检查全部通过,并且确实该令牌含有全部要去的特权

    if
((PrivilegeControl & PRIVILEGE_SET_ALL_NECESSARY) && PrivilegeCount == k)

        return
TRUE;

    //if只需部分满足

    if
(k > 0 && !(PrivilegeControl & PRIVILEGE_SET_ALL_NECESSARY))

        return
TRUE;

    return
FALSE;

}

 

 

如前所述。每个线程默认都使用它父进程的令牌,但是,如有需要,一个线程也可以模拟其他线程(进程)的令牌行使权力。如服务线程为客户线程提供服务,客户线程把要执行的任务纳入到服务线程中去执行,但是,服务线程中需要把自己的令牌模拟成客户线程的令牌,才不致因为权限方面而引起种种问题。因此,要求模拟的线程通常称为服务线程,提供模拟的线程通常称为客户线程。下面的函数就用于让指定线程模拟使用其他线程的令牌。

NTSTATUS

NTAPI

NtImpersonateThread(IN HANDLE ThreadHandle,//服务线程

                    IN HANDLE ThreadToImpersonateHandle,//客户线程

                    IN PSECURITY_QUALITY_OF_SERVICE
SecurityQualityOfService)//模拟方式与级别

{

    SECURITY_QUALITY_OF_SERVICE
SafeServiceQoS;

    SECURITY_CLIENT_CONTEXT
ClientContext;

    PETHREAD
Thread;

    PETHREAD
ThreadToImpersonate;

    KPROCESSOR_MODE
PreviousMode = ExGetPreviousMode();

    NTSTATUS
Status;

    if
(PreviousMode != KernelMode)

    {

        _SEH2_TRY

        {

            ProbeForRead(SecurityQualityOfService,sizeof(SECURITY_QUALITY_OF_SERVICE),

                         sizeof(ULONG));

            SafeServiceQoS
= *SecurityQualityOfService;

            SecurityQualityOfService
= &SafeServiceQoS;

        }

        。。。

    }

    Status
= ObReferenceObjectByHandle(ThreadHandle,THREAD_DIRECT_IMPERSONATION,PsThreadType,

                                      
PreviousMode, (PVOID*)&Thread,NULL);

    if
(NT_SUCCESS(Status))

    {

        Status
= ObReferenceObjectByHandle(ThreadToImpersonateHandle,THREAD_IMPERSONATE,

                                          
PsThreadType,PreviousMode,

                                          
(PVOID*)&ThreadToImpersonate,NULL);

        if
(NT_SUCCESS(Status))

        {

            //获得或者创建一个模拟令牌,记录到ClientContext

            Status
= SeCreateClientSecurity(ThreadToImpersonate,SecurityQualityOfService,

                                           
0,&ClientContext);

            if
(NT_SUCCESS(Status))

            {

                //行使模拟工作

                SeImpersonateClient(&ClientContext,
Thread);

                if (ClientContext.ClientToken)

                    ObDereferenceObject(ClientContext.ClientToken);

            }

            ObDereferenceObject(ThreadToImpersonate);

        }

        ObDereferenceObject(Thread);

    }

    return
Status;

}

 

如上,上面的函数先调用SeCreateClientSecurity获得或者创建一个客户令牌,然后调用SeImpersonateClient完成模拟工作。

NTSTATUS

SeCreateClientSecurity(IN PETHREAD Thread,//客户线程

                       IN PSECURITY_QUALITY_OF_SERVICE
Qos,//模拟方式与级别等要求

                       IN BOOLEAN RemoteClient,

                       OUT PSECURITY_CLIENT_CONTEXT
ClientContext)//返回得到的模拟令牌

{

    TOKEN_TYPE
TokenType;

    BOOLEAN
ThreadEffectiveOnly;

    SECURITY_IMPERSONATION_LEVEL
ImpersonationLevel;

    PACCESS_TOKEN
Token;

    NTSTATUS
Status;

PACCESS_TOKEN NewToken;

//获得客户线程的有效令牌,以及令牌的类型、允许的被模拟级别等信息

    Token
= PsReferenceEffectiveToken(Thread,&TokenType,&ThreadEffectiveOnly,

                                     
&ImpersonationLevel);

    if
(TokenType != TokenImpersonation)
//if 客户线程的令牌就是所属进程的令牌

        ClientContext->DirectAccessEffectiveOnly = Qos->EffectiveOnly;

    Else //if
客户线程的令牌本身也是模拟得来的

{

//要求的模拟级别不能越过被允许模拟的级别

        if
(Qos->ImpersonationLevel
> ImpersonationLevel)

        {

            if (Token) ObDereferenceObject(Token);

            return
STATUS_BAD_IMPERSONATION_LEVEL;

        }

        if
((ImpersonationLevel == SecurityAnonymous) ||

            (ImpersonationLevel == SecurityIdentification)
||

            ((RemoteClient) && (ImpersonationLevel
!= SecurityDelegation)))

        {

            if
(Token) ObDereferenceObject(Token);

            return
STATUS_BAD_IMPERSONATION_LEVEL;

        }

        ClientContext->DirectAccessEffectiveOnly = ((ThreadEffectiveOnly) ||

                                                    (Qos->EffectiveOnly))
? TRUE : FALSE;

    }

 

    if
(Qos->ContextTrackingMode
== SECURITY_STATIC_TRACKING) //if 模拟方式为克隆

    {

        ClientContext->DirectlyAccessClientToken = FALSE;//非直接引用方式

        //复制一个副本

        Status
= SeCopyClientToken(Token,
ImpersonationLevel, 0, &NewToken);

        if
(!NT_SUCCESS(Status))
return Status;

    }

    else

    {

        ClientContext->DirectlyAccessClientToken = TRUE;//直接引用方式

        NewToken
= Token;//直接引用客户线程的令牌

    }

    ClientContext->SecurityQos.Length
= sizeof(SECURITY_QUALITY_OF_SERVICE);

    ClientContext->SecurityQos.ImpersonationLevel
= Qos->ImpersonationLevel;

    ClientContext->SecurityQos.ContextTrackingMode
= Qos->ContextTrackingMode;

    ClientContext->SecurityQos.EffectiveOnly
= Qos->EffectiveOnly;

    ClientContext->ServerIsRemote = RemoteClient;

    ClientContext->ClientToken = NewToken;//返回获得/创建的模拟令牌

    return
STATUS_SUCCESS;

}

如上,用户可以按引用方式模拟,直接引用客户线程的令牌,也可以要求按克隆方式模拟。

 

下面的函数完成模拟工作

VOID

SeImpersonateClient(IN PSECURITY_CLIENT_CONTEXT ClientContext,//之前得到的模拟令牌

                    IN PETHREAD ServerThread OPTIONAL)//服务线程

{

    UCHAR
b;

    if
(ClientContext->DirectlyAccessClientToken
== FALSE)

        b
= ClientContext->SecurityQos.EffectiveOnly;

    else

        b
= ClientContext->DirectAccessEffectiveOnly;

    if
(ServerThread == NULL)

        ServerThread
= PsGetCurrentThread();

    PsImpersonateClient(ServerThread,ClientContext->ClientToken,1,b,

                        ClientContext->SecurityQos.ImpersonationLevel);

}

 

继续看:

NTSTATUS

PsImpersonateClient(IN PETHREAD Thread,//服务线程

                    IN PACCESS_TOKEN Token,//得到的模拟令牌

                    IN BOOLEAN CopyOnOpen,

                    IN BOOLEAN EffectiveOnly,

                    IN SECURITY_IMPERSONATION_LEVEL
ImpersonationLevel)

{

    PPS_IMPERSONATION_INFORMATION
Impersonation, OldData;

    PTOKEN
OldToken = NULL;

    if
(!Token)//表示要求撤销模拟

    {

        if
(Thread->ActiveImpersonationInfo)//if
线程处于模拟状态

        {

            PspLockThreadSecurityExclusive(Thread);

            if
(Thread->ActiveImpersonationInfo)

            {

                PspClearCrossThreadFlag(Thread,CT_ACTIVE_IMPERSONATION_INFO_BIT);//清除标记

                OldToken = Thread->ImpersonationInfo->Token;

            }

            PspUnlockThreadSecurityExclusive(Thread);

            PspWriteTebImpersonationInfo(Thread, PsGetCurrentThread());

        }

    }

    Else //模拟

    {

        Impersonation
= Thread->ImpersonationInfo;

        if
(!Impersonation)

        {

            Impersonation
= ExAllocatePoolWithTag(PagedPool,sizeof(*Impersonation));

            OldData
= InterlockedCompareExchangePointer((PVOID*)&Thread->ImpersonationInfo,

                                                       
Impersonation,NULL);

            if
(OldData)

            {

                ExFreePool(Impersonation);

                Impersonation = OldData;

            }

        }

        PspLockThreadSecurityExclusive(Thread);

        if
(Thread->ActiveImpersonationInfo)

            OldToken
= Impersonation->Token;

        else

PspSetCrossThreadFlag(Thread,
CT_ACTIVE_IMPERSONATION_INFO_BIT);//打上模拟状态标记

        Impersonation->ImpersonationLevel = ImpersonationLevel;

        Impersonation->CopyOnOpen = CopyOnOpen;

        Impersonation->EffectiveOnly = EffectiveOnly;

        Impersonation->Token = Token;//关键。记录得到的模拟令牌

        ObReferenceObject(Token);

        PspUnlockThreadSecurityExclusive(Thread);

        PspWriteTebImpersonationInfo(Thread, PsGetCurrentThread());

    }

    if
(OldToken) PsDereferenceImpersonationToken(OldToken);//释放销毁原令牌

    return
STATUS_SUCCESS;

}

 

如上,模拟工作其实就是分配一个ImpersonationInfo结构,将模拟得到的令牌记录在这个结构中。

 

下面这个函数用于获得指定线程的有效令牌,所谓有效令牌是指当前正在使用的令牌。

PACCESS_TOKEN

PsReferenceEffectiveToken(IN PETHREAD Thread,

                          OUT IN PTOKEN_TYPE TokenType,

                          OUT PBOOLEAN EffectiveOnly,

                          OUT PSECURITY_IMPERSONATION_LEVEL
Level)

{

    PEPROCESS
Process;

    PACCESS_TOKEN
Token = NULL;

    Process
= Thread->ThreadsProcess;

    if
(!Thread->ActiveImpersonationInfo)//if
指定线程没处于模拟状态

    {

        Token
= ObFastReferenceObject(&Process->Token);//使用所属进程的令牌

        if
(!Token)

        {

            PspLockProcessSecurityShared(Process);

            Token
= ObFastReferenceObjectLocked(&Process->Token);

            PspUnlockProcessSecurityShared(Process);

        }

    }

    Else //if
指定线程正处于模拟状态

    {

        PspLockProcessSecurityShared(Process);

        if
(Thread->ActiveImpersonationInfo)

        {

            Token
= Thread->ImpersonationInfo->Token;//使用它的模拟令牌

            ObReferenceObject(Token);

            *TokenType = TokenImpersonation;

            *EffectiveOnly = Thread->ImpersonationInfo->EffectiveOnly;

            *Level = Thread->ImpersonationInfo->ImpersonationLevel;

            PspUnlockProcessSecurityShared(Process);

            return
Token;

        }

        PspUnlockProcessSecurityShared(Process);

    }

    *TokenType
= TokenPrimary;

    *EffectiveOnly
= FALSE;

    return
Token;

}

可以看出,如果一个线程使用了模拟令牌,那么返回的就是其模拟令牌,否则,返回进程令牌。

 

系统提供了一个服务函数,由于直接获得进程的令牌,打开它,返回一个句柄

NTSTATUS

NtOpenProcessTokenEx(IN HANDLE ProcessHandle,

                     IN ACCESS_MASK DesiredAccess,

                     IN ULONG HandleAttributes,

                     OUT PHANDLE TokenHandle)

{

    PACCESS_TOKEN
Token;

    HANDLE
hToken;

    KPROCESSOR_MODE
PreviousMode = ExGetPreviousMode();

    NTSTATUS
Status;

    if
(PreviousMode != KernelMode)

    {

        _SEH2_TRY

        {

            ProbeForWriteHandle(TokenHandle);

        }

        。。。

    }

    Status
= PsOpenTokenOfProcess(ProcessHandle, &Token);//实质函数

    if
(NT_SUCCESS(Status))

    {

        Status
= ObOpenObjectByPointer(Token,HandleAttributes,NULL,DesiredAccess,

                                      
SepTokenObjectType,PreviousMode,&hToken);

        ObDereferenceObject(Token);

        if
(NT_SUCCESS(Status))

        {

            _SEH2_TRY

            {

                *TokenHandle = hToken;

            }

            。。。

        }

    }

    return
Status;

}

 

 

NTSTATUS  PsOpenTokenOfProcess(IN HANDLE ProcessHandle,OUT PACCESS_TOKEN* Token)

{

    PEPROCESS
Process;

    NTSTATUS
Status;

    Status
= ObReferenceObjectByHandle(ProcessHandle,PROCESS_QUERY_INFORMATION,PsProcessType,

                                      
ExGetPreviousMode(), (PVOID*)&Process,NULL);

    if
(NT_SUCCESS(Status))

    {

        *Token
= PsReferencePrimaryToken(Process);

        ObDereferenceObject(Process);

    }

    return
Status;

}

 

PACCESS_TOKEN  PsReferencePrimaryToken(PEPROCESS
Process)//获得进程的令牌

{

    PACCESS_TOKEN
Token;

    Token
= ObFastReferenceObject(&Process->Token);//看到没

    if
(!Token)

    {

        PspLockProcessSecurityShared(Process);

        Token
= ObFastReferenceObjectLocked(&Process->Token);

        PspUnlockProcessSecurityShared(Process);

    }

    return
Token;

}

以上代码就不想解释了。

相应的,系统提供了一个函数NtOpenThreadTokenEx,用来打开线程的令牌,具体不分析了。

 

令牌的作用就是用来记录承载在上面的用户、组身份以及特权。显然,光有令牌不能提供安全机制,还得有一个ACL,来记录每个用户、组的访问权限。

用户在创建文件、内核对象的时候,可以提供一个安全描述符,来规定安全属性,安全描述符中最主要的就是ACL了。

创建文件时,可以在对象属性中设置ACL

NTSTATUS

NtCreateFile(PHANDLE FileHandle,

             ACCESS_MASK DesiredAccess,

             POBJECT_ATTRIBUTES ObjectAttributes,//对象属性,简称oa

             PIO_STATUS_BLOCK IoStatusBlock,

             PLARGE_INTEGER AllocateSize,

             ULONG FileAttributes,

             ULONG ShareAccess,

             ULONG CreateDisposition,

             ULONG CreateOptions,

             PVOID EaBuffer,

             ULONG
EaLength);

 

 

创建其他内核对象时,也可以设置一个ACL,如进程对象

NTSTATUS

NtCreateProcess(OUT PHANDLE ProcessHandle,

                IN ACCESS_MASK DesiredAccess,

                IN POBJECT_ATTRIBUTES
ObjectAttributes OPTIONAL,//对象属性

                IN HANDLE ParentProcess,

                IN BOOLEAN InheritObjectTable,

                IN HANDLE SectionHandle OPTIONAL,

                IN HANDLE DebugPort OPTIONAL,

                IN
HANDLE ExceptionPort
OPTIONAL);

文件对象属性中的ACL还会保存到磁盘文件中,其他内核对象的ACL则存在于对象头中。

 

typedef struct _OBJECT_ATTRIBUTES {

  ULONG
Length;

  HANDLE
RootDirectory;

  PUNICODE_STRING
ObjectName;

  ULONG
Attributes;

  PVOID
SecurityDescriptor;//安全描述符(简称SD),其实是一个SECURITY_DESCRIPTOR结构指针

  PVOID
SecurityQualityOfService;

} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

typedef CONST OBJECT_ATTRIBUTES *PCOBJECT_ATTRIBUTES;

 

typedef struct _SECURITY_DESCRIPTOR {

  UCHAR
Revision;

  UCHAR
Sbz1;

  SECURITY_DESCRIPTOR_CONTROL
Control;//一些成员标志(Present、相对偏移、默认 等标志)

  PSID
Owner;//可选。该对象的拥有者SID,即创建者用户

  PSID
Group;//可选。该对象的拥有者所属的基本组

  PACL
Sacl;//可选。日志、警报行为控制表

  PACL
Dacl;//可选。该对象的ACL访问控制表

}
SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;

可以看到,一个安全描述符中可以包含四种安全信息。一般内核对象的sd存在于通用对象头中,但是设备对象、文件对象的sd例外,获取设备对象、文件对象sd的方法并不从对象头中取,这一点要特别注意。

 

typedef struct _ACL { //访问控制表(由很多ACE组成)

  UCHAR
AclRevision;

  UCHAR
Sbz1;

  USHORT
AclSize;//整个ACL结构的大小(包含结构体后面的ACE数组部分)

  USHORT
AceCount;//包含的ACE表项个数

  USHORT
Sbz2;

}
ACL, *PACL;

ACL结构后面紧跟一个ACE‘数组’(其实不是数组,因为每个ACE的长度是不定长的)

 

ACL中包含两类ACE,一种拒绝类ACE,一种允许类ACE,拒绝类ACE描述拒绝了哪些用户/组的哪些访问权限,允许类ACE则描述允许哪些用户/组的哪些访问权限

typedef struct _ACCESS_DENIED_ACE {

  ACE_HEADER
Header;//头部

  ACCESS_MASK
Mask;//可读、可写、可执行权限掩码

  ULONG
SidStart;//实际上是本结构体后面紧跟的SID结构体中第一个字段

}
ACCESS_DENIED_ACE, *PACCESS_DENIED_ACE;

 

typedef struct _ACCESS_ALLOWED_ACE {

  ACE_HEADER
Header;

  ACCESS_MASK
Mask;

  ULONG
SidStart;

}
ACCESS_ALLOWED_ACE, *PACCESS_ALLOWED_ACE;

 

两种ACE都有相同的头部结构,记录了该ACE的类型,大小和标志

typedef struct _ACE_HEADER {

  UCHAR
AceType;

  UCHAR
AceFlags;

  USHORT
AceSize;//指整个ACE结构的大小(连同后面紧跟的SID结构)

}
ACE_HEADER, *PACE_HEADER;

 

typedef struct _ACE  //通用ACE

{

    ACE_HEADER
Header;

    ACCESS_MASK
AccessMask;

}
ACE, *PACE;

ACE结构后面紧跟一个SID结构,表示该ACE所针对的用户/

 

经验: 在ACL表中,一般是拒绝类ACE放在前面,允许类ACE放在后面。拒绝类ACE优先级更高。

 

 

一般我们在创建文件、内核对象时,将SD置为NULL,表示采用默认的SD。但也可以手动指定一个SD

当一个文件、内核对象初始设置了一个SD后,并不是一成不变的。需要的时候,我们还可以随时修改、查询、删除他们的sd。对象的sd包含指派、修改、查询、删除这四种操作。下面的函数用于修改一个对象的已有sd(指修改sd内部的4个成分之一)

NTSTATUS  //修改指定对象的sd

NtSetSecurityObject(IN HANDLE Handle,//指定对象(的句柄)

                    IN SECURITY_INFORMATION
SecurityInformation,//sd内容存在标志

                    IN PSECURITY_DESCRIPTOR
SecurityDescriptor)//新sd

{

    KPROCESSOR_MODE
PreviousMode = ExGetPreviousMode();

    PVOID
Object;

   
SECURITY_DESCRIPTOR_RELATIVE *CapturedDescriptor;

    ACCESS_MASK
DesiredAccess = 0;

    NTSTATUS
Status;

    SeSetSecurityAccessMask(SecurityInformation, &DesiredAccess);

    Status
= ObReferenceObjectByHandle(Handle,DesiredAccess,NULL,PreviousMode,&Object,NULL);

    if
(NT_SUCCESS(Status))

    {

       

        //将用户空间中的sd拷贝到内核空间中的CapturedDescriptor

        SeCaptureSecurityDescriptor(SecurityDescriptor,PreviousMode,PagedPool,TRUE, (PSECURITY_DESCRIPTOR*)&CapturedDescriptor);

        //if 标志与内容自相矛盾

   if (((SecurityInformation & OWNER_SECURITY_INFORMATION)
&&

             !(CapturedDescriptor->Owner))
||

            ((SecurityInformation & GROUP_SECURITY_INFORMATION)
&&

             !(CapturedDescriptor->Group)))

        {

             Status = STATUS_INVALID_SECURITY_DESCR;

        }

        else

        {

            //实质函数

            Status
= ObSetSecurityObjectByPointer(Object,//目标对象

                                                 
SecurityInformation,//内容标志

                                                 
CapturedDescriptor);//新sd

        }

        //释放用户传入的那个sd*

        SeReleaseSecurityDescriptor(CapturedDescriptor,PreviousMode,TRUE);

        ObDereferenceObject(Object);

    }

    return
Status;

}

 

 

继续看:

NTSTATUS

ObSetSecurityObjectByPointer(IN PVOID Object,//目标对象

                             IN SECURITY_INFORMATION
SecurityInformation,//内容标志

                             IN PSECURITY_DESCRIPTOR
SecurityDescriptor)//新sd

{

    POBJECT_TYPE
Type;

    POBJECT_HEADER
Header;

    Header
= OBJECT_TO_OBJECT_HEADER(Object);

Type = Header->Type;

//调用相应对象类型提供的sd管理函数(SecurityProcedure表示注册的sd管理函数)

    return
Type->TypeInfo.SecurityProcedure(Object,

                                           
SetSecurityDescriptor,//操作为修改sd

                                           
&SecurityInformation,

                                     
      SecurityDescriptor,

                                           
NULL,

                                           
&Header->SecurityDescriptor,//原sd**

                                           
Type->TypeInfo.PoolType,

                                            &Type->TypeInfo.GenericMapping);

}

 

对于设备对象与文件对象,SecurityProcedure函数是IopSecurityFile函数,而对于普通对象类型,则是SeDefaultObjectMethod函数。为什么文件对象与设备对象的sd管理函数与一般对象的不同呢?因为文件对象的sd并不使用对象头中的那个sd,设备对象的sd也不是对象头中的那个sd,以后我们还会看到,键对象(指注册表中的键)的sd管理函数也不是默认的SeDefaultObjectMethod,而是CmpSecurityMethod,因为每个键也像文件一样,是有ACL存在磁盘上的。下面我们先看设备对象、文件对象是如何管理sd

NTSTATUS

IopSecurityFile(IN PVOID
ObjectBody,//目标对象(设备对象或文件对象)

                IN SECURITY_OPERATION_CODE
OperationCode,//操作码

                IN PSECURITY_INFORMATION
SecurityInformation,//内容标志

                IN PSECURITY_DESCRIPTOR
SecurityDescriptor,//用户提供的sd*

                IN OUT PULONG BufferLength,

                IN OUT PSECURITY_DESCRIPTOR *OldSecurityDescriptor,//原sd**

                IN POOL_TYPE PoolType,

                IN OUT PGENERIC_MAPPING
GenericMapping)

{

    IO_STATUS_BLOCK
IoStatusBlock;

    PIO_STACK_LOCATION
StackPtr;

    PFILE_OBJECT
FileObject;

    PDEVICE_OBJECT
DeviceObject;

    PIRP
Irp;

    BOOLEAN
LocalEvent = FALSE;

    KEVENT
Event;

    NTSTATUS
Status = STATUS_SUCCESS;

    if
(((PFILE_OBJECT)ObjectBody)->Type == IO_TYPE_DEVICE)

    {

        DeviceObject
= (PDEVICE_OBJECT)ObjectBody;

        FileObject
= NULL;

    }

    else

    {

        FileObject
= (PFILE_OBJECT)ObjectBody;

        if
(FileObject->Flags
& FO_DIRECT_DEVICE_OPEN)//if是直接打开物理卷,没打开文件

            DeviceObject
= IoGetAttachedDevice(FileObject->DeviceObject);//栈顶物理卷

        else

            DeviceObject
= FileObject->DeviceObject;//物理卷

}

//if 
FileObject打开者当初不是打开具体的文件,不牵涉文件系统,就使用设备对象结构中的那个sd,而非通用对象头中的那个sd

    if
(!(FileObject) ||

        (!(FileObject->FileName.Length)
&& !(FileObject->RelatedFileObject)) ||

        (FileObject->Flags & FO_DIRECT_DEVICE_OPEN))

    {

        //DeviceObject就为普通的设备或者物理卷设备

        if
(OperationCode == QuerySecurityDescriptor)

        {

            return
SeQuerySecurityDescriptorInfo(SecurityInformation,

SecurityDescriptor,//out到用户的sd*

                                 BufferLength,

                                 &DeviceObject->SecurityDescriptor);//非通用对象头中的那个sd

        }

        else
if (OperationCode
== DeleteSecurityDescriptor)

            return
STATUS_SUCCESS;//设备对象不许删除sd

        else
if (OperationCode
== AssignSecurityDescriptor)

        {

            if
(!(FileObject) || !(FileObject->Flags & FO_STREAM_FILE))

                DeviceObject->SecurityDescriptor
= SecurityDescriptor;//指派sd

            return
STATUS_SUCCESS;

        }

        else

            return
STATUS_SUCCESS;//设备对象不支持修改sd(但支持指派sd

    }

    else
if (OperationCode
== DeleteSecurityDescriptor)

        return
STATUS_SUCCESS;//文件对象、设备对象都不能支持sd删除操作

 

//如果目标是个文件对象,并且确实打开了某个具体的文件,也即如果牵涉文件系统,也

//不能简单的使用通用对象头中的那个sd,而应该向具体文件系统查询该文件的sd(来源于磁盘上保//存的ACL

    ObReferenceObject(FileObject);

    if
(FileObject->Flags
& FO_SYNCHRONOUS_IO)

        IopLockFileObject(FileObject);

    else

    {

        KeInitializeEvent(&Event, SynchronizationEvent,
FALSE);

        LocalEvent
= TRUE;//异步操作必须提供自定义事件

    }

    KeClearEvent(&FileObject->Event);

    DeviceObject
= IoGetRelatedDeviceObject(FileObject);//获取栈顶的文件卷

    Irp
= IoAllocateIrp(DeviceObject->StackSize, FALSE);

    Irp->Tail.Overlay.OriginalFileObject = FileObject;

    Irp->Tail.Overlay.Thread = PsGetCurrentThread();

    Irp->RequestorMode = ExGetPreviousMode();

    Irp->UserIosb = &IoStatusBlock;

    Irp->UserEvent = (LocalEvent)
? &Event : NULL;

    Irp->Flags = (LocalEvent)
? IRP_SYNCHRONOUS_API : 0;

    Irp->Overlay.AsynchronousParameters.UserApcRoutine = NULL;

    StackPtr
= IoGetNextIrpStackLocation(Irp);

    StackPtr->FileObject = FileObject;

    if
(OperationCode == QuerySecurityDescriptor)

    {

        StackPtr->MajorFunction = IRP_MJ_QUERY_SECURITY;//查询sd irp

        StackPtr->Parameters.QuerySecurity.SecurityInformation =

            *SecurityInformation;

        StackPtr->Parameters.QuerySecurity.Length = *BufferLength;

        Irp->UserBuffer = SecurityDescriptor;

    }

    else

    {

        StackPtr->MajorFunction = IRP_MJ_SET_SECURITY;//修改sd irp

        StackPtr->Parameters.SetSecurity.SecurityInformation =

            *SecurityInformation;

        StackPtr->Parameters.SetSecurity.SecurityDescriptor =

            SecurityDescriptor;

    }

    IopQueueIrpToThread(Irp);//挂入线程的pending irp 队列

    IopUpdateOperationCount(IopOtherTransfer);

    Status
= IoCallDriver(DeviceObject,
Irp);//关键。将sd操作发给具体的文件系统去处理

    if
(LocalEvent)//if 异步

    {

        if
(Status == STATUS_PENDING)

        {

            KeWaitForSingleObject(&Event,Executive,KernelMode,FALSE,NULL);

            Status
= IoStatusBlock.Status;

        }

    }

    Else
//if 同步

    {

        if
(Status == STATUS_PENDING)

        {

            KeWaitForSingleObject(&FileObject->Event,Executive,KernelMode,FALSE,NULL);

            Status
= FileObject->FinalStatus;

        }

        IopUnlockFileObject(FileObject);

    }

    //只有Ntfs系统才支持ACL机制,FAT32系统不支持,因此有可能irp处理失败

    if
(Status == STATUS_INVALID_DEVICE_REQUEST)//if
FAT32等不支持ACL的文件系统

    {

        if
(OperationCode == QuerySecurityDescriptor)

        {

            //返回一个伪造的sd

            Status
= SeSetWorldSecurityDescriptor(*SecurityInformation,SecurityDescriptor,

                                                 
BufferLength);

        }

        Else  Status
= STATUS_SUCCESS; //伪造成功

    }

    else
if (OperationCode
== QuerySecurityDescriptor)

    {

        if
(Status == STATUS_BUFFER_OVERFLOW)
Status = STATUS_BUFFER_TOO_SMALL;

 

        _SEH2_TRY

        {

            *BufferLength = IoStatusBlock.Information;

        }

        。。。

    }

    return
Status;

}

 

如上,可以看出,对普通内核对象和设备对象的sd操作都是由系统内核处理的,而对文件的sd操作则是由具体的文件系统完成的。FAT32文件系统不支持ACL,不支持sd操作,所以,从这儿也可看出FAT32系统的一个缺点便是安全性不够!

 

 

下面看看普通内核对象(非设备对象、非文件对象)是怎么处理sd操作的(一律采用通用对象头中的sd)

NTSTATUS

SeDefaultObjectMethod(IN PVOID Object,//目标对象

                      IN SECURITY_OPERATION_CODE
OperationType,//4种sd操作码之一

                      IN PSECURITY_INFORMATION
SecurityInformation,//内容标志

                      IN OUT PSECURITY_DESCRIPTOR SecurityDescriptor,//用户传入的sd*

                      IN OUT PULONG ReturnLength
OPTIONAL,

                      IN OUT PSECURITY_DESCRIPTOR *OldSecurityDescriptor,//原sd**

                      IN POOL_TYPE PoolType,

                      IN PGENERIC_MAPPING
GenericMapping)

{

    switch
(OperationType)

    {

        case
SetSecurityDescriptor:

            return
ObSetSecurityDescriptorInfo(Object,

                                               SecurityInformation,

                                               SecurityDescriptor,

                                               OldSecurityDescriptor,//对象头中的sd**

                                               PoolType,

                                               GenericMapping);

        case
QuerySecurityDescriptor:

            return
ObQuerySecurityDescriptorInfo(Object,

                                                 SecurityInformation,

                                                
SecurityDescriptor,

                                                
ReturnLength,

                                                
OldSecurityDescriptor);//对象头中的sd**

        case
DeleteSecurityDescriptor:

            return
ObDeassignSecurity(OldSecurityDescriptor);

        case
AssignSecurityDescriptor:

            ObAssignObjectSecurityDescriptor(Object, SecurityDescriptor,
PoolType);

            return
STATUS_SUCCESS;

    }

    return
STATUS_SUCCESS;

}

 

以修改sd为例,看看普通对象是如何修改sd

NTSTATUS

ObSetSecurityDescriptorInfo(IN PVOID Object,

                            IN PSECURITY_INFORMATION
SecurityInformation,

                            IN OUT PSECURITY_DESCRIPTOR SecurityDescriptor,//新sd*

                            IN OUT PSECURITY_DESCRIPTOR
*OutputSecurityDescriptor,//原sd**

                            IN POOL_TYPE PoolType,

                            IN PGENERIC_MAPPING
GenericMapping)

{

    NTSTATUS
Status;

    POBJECT_HEADER
ObjectHeader;

    PSECURITY_DESCRIPTOR
OldDescriptor, NewDescriptor,
CachedDescriptor;

    PEX_FAST_REF
FastRef;

    EX_FAST_REF
OldValue;

    ULONG_PTR
Count;

    ObjectHeader
= OBJECT_TO_OBJECT_HEADER(Object);

    OldDescriptor
= ObpReferenceSecurityDescriptor(ObjectHeader);

    NewDescriptor
= OldDescriptor;

    //修改对象的sd为新sd,并返回新sdNewDescriptor参数中

    Status = SeSetSecurityDescriptorInfo(Object,

                                        
SecurityInformation,

                                        
SecurityDescriptor,//新sd

                                         &NewDescriptor,//传入旧sd,返回新sd

                                        
PoolType,

                                        
GenericMapping);

    。。。

    return
Status;

}

 

 

NTSTATUS

SeSetSecurityDescriptorInfo(IN PVOID Object OPTIONAL,

                            IN PSECURITY_INFORMATION _SecurityInformation,

                            IN PSECURITY_DESCRIPTOR
_SecurityDescriptor,//新sd

                            IN OUT PSECURITY_DESCRIPTOR *ObjectsSecurityDescriptor,

                            IN POOL_TYPE PoolType,

                            IN PGENERIC_MAPPING
GenericMapping)

{

    PISECURITY_DESCRIPTOR
ObjectSd;

    PISECURITY_DESCRIPTOR
NewSd;

    PISECURITY_DESCRIPTOR
SecurityDescriptor = _SecurityDescriptor;

    PSID
Owner = 0;

    PSID
Group = 0;

    PACL
Dacl = 0;

    PACL
Sacl = 0;

    ULONG
OwnerLength = 0;

    ULONG
GroupLength = 0;

    ULONG
DaclLength = 0;

    ULONG
SaclLength = 0;

    ULONG
Control = 0;

    ULONG_PTR
Current;

    SECURITY_INFORMATION
SecurityInformation;

    ObjectSd
= *ObjectsSecurityDescriptor;//旧sd*

    SecurityInformation
= *_SecurityInformation;

    if
(SecurityInformation & OWNER_SECURITY_INFORMATION)//如果新sd中含有Owner信息

    {

        if
(SecurityDescriptor->Owner != NULL)

        {

            if
(SecurityDescriptor->Control & SE_SELF_RELATIVE)//ifsd内部字段是相对偏移

                Owner = (PSID)((ULONG_PTR)SecurityDescriptor->Owner +

                               (ULONG_PTR)SecurityDescriptor);

            else

                Owner = (PSID)SecurityDescriptor->Owner;

            OwnerLength
= ROUND_UP(RtlLengthSid(Owner), 4);

        }

        Control
|= (SecurityDescriptor->Control & SE_OWNER_DEFAULTED);

    }

    else

    {

        if
(ObjectSd->Owner
!= NULL) //使用旧sd中的Owner

        {

            Owner
= (PSID)((ULONG_PTR)ObjectSd->Owner
+ (ULONG_PTR)ObjectSd);

            OwnerLength
= ROUND_UP(RtlLengthSid(Owner), 4);

        }

 

        Control
|= (ObjectSd->Control
& SE_OWNER_DEFAULTED);

    }

    if
(SecurityInformation & GROUP_SECURITY_INFORMATION)

    {

        if
(SecurityDescriptor->Group != NULL)

        {

            if(
SecurityDescriptor->Control & SE_SELF_RELATIVE )

                Group = (PSID)((ULONG_PTR)SecurityDescriptor->Group +

                               (ULONG_PTR)SecurityDescriptor);

            else

                Group
= (PSID)SecurityDescriptor->Group;

            GroupLength
= ROUND_UP(RtlLengthSid(Group), 4);

        }

 

        Control
|= (SecurityDescriptor->Control & SE_GROUP_DEFAULTED);

    }

    else

    {

        if
(ObjectSd->Group
!= NULL)

        {

            Group
= (PSID)((ULONG_PTR)ObjectSd->Group
+ (ULONG_PTR)ObjectSd);

            GroupLength
= ROUND_UP(RtlLengthSid(Group), 4);

        }

 

        Control
|= (ObjectSd->Control
& SE_GROUP_DEFAULTED);

    }

 

  

    if
(SecurityInformation & DACL_SECURITY_INFORMATION)//如果新sd中含DACL

    {

        if
((SecurityDescriptor->Control & SE_DACL_PRESENT)
&&

            (SecurityDescriptor->Dacl != NULL))

        {

            if(
SecurityDescriptor->Control & SE_SELF_RELATIVE )

                Dacl = (PACL)((ULONG_PTR)SecurityDescriptor->Dacl +

                              (ULONG_PTR)SecurityDescriptor);

            else

                Dacl = (PACL)SecurityDescriptor->Dacl;

 

            DaclLength
= ROUND_UP((ULONG)Dacl->AclSize,
4);

        }

 

        Control |= (SecurityDescriptor->Control
& (SE_DACL_DEFAULTED | SE_DACL_PRESENT));

    }

    else

    {

        if
((ObjectSd->Control
& SE_DACL_PRESENT) &&

            (ObjectSd->Dacl != NULL))

        {

            Dacl
= (PACL)((ULONG_PTR)ObjectSd->Dacl +
(ULONG_PTR)ObjectSd);

            DaclLength
= ROUND_UP((ULONG)Dacl->AclSize,
4);

        }

 

        Control
|= (ObjectSd->Control
& (SE_DACL_DEFAULTED | SE_DACL_PRESENT));

    }

    if
(SecurityInformation & SACL_SECURITY_INFORMATION)

    {

        if
((SecurityDescriptor->Control & SE_SACL_PRESENT)
&&

            (SecurityDescriptor->Sacl != NULL))

        {

            if(
SecurityDescriptor->Control & SE_SELF_RELATIVE )

                Sacl = (PACL)((ULONG_PTR)SecurityDescriptor->Sacl +

                              (ULONG_PTR)SecurityDescriptor);

            else

                Sacl = (PACL)SecurityDescriptor->Sacl;

            SaclLength
= ROUND_UP((ULONG)Sacl->AclSize,
4);

        }

 

        Control
|= (SecurityDescriptor->Control & (SE_SACL_DEFAULTED
| SE_SACL_PRESENT));

    }

    else

    {

        if
((ObjectSd->Control
& SE_SACL_PRESENT) &&

            (ObjectSd->Sacl != NULL))

        {

            Sacl
= (PACL)((ULONG_PTR)ObjectSd->Sacl +
(ULONG_PTR)ObjectSd);

            SaclLength
= ROUND_UP((ULONG)Sacl->AclSize,
4);

        }

 

        Control
|= (ObjectSd->Control
& (SE_SACL_DEFAULTED | SE_SACL_PRESENT));

    }

 

    //分配一个新的sd

    NewSd = ExAllocatePool(NonPagedPool,sizeof(SECURITY_DESCRIPTOR)
+ OwnerLength + GroupLength
+DaclLength + SaclLength);

    RtlCreateSecurityDescriptor(NewSd,SECURITY_DESCRIPTOR_REVISION1);//初始化新sd

    NewSd->Control = (USHORT)Control | SE_SELF_RELATIVE;

    Current
= (ULONG_PTR)NewSd
+ sizeof(SECURITY_DESCRIPTOR);

    if
(OwnerLength != 0)

    {

        RtlCopyMemory((PVOID)Current,Owner,OwnerLength);

        NewSd->Owner = (PSID)(Current – (ULONG_PTR)NewSd);

        Current
+= OwnerLength;

    }

 

    if
(GroupLength != 0)

    {

        RtlCopyMemory((PVOID)Current,Group,GroupLength);

        NewSd->Group = (PSID)(Current – (ULONG_PTR)NewSd);

        Current
+= GroupLength;

    }

 

    if
(DaclLength != 0)

    {

        RtlCopyMemory((PVOID)Current,Dacl,DaclLength);

        NewSd->Dacl = (PACL)(Current – (ULONG_PTR)NewSd);

        Current
+= DaclLength;

    }

 

    if
(SaclLength != 0)

    {

        RtlCopyMemory((PVOID)Current,Sacl,SaclLength);

        NewSd->Sacl = (PACL)(Current – (ULONG_PTR)NewSd);

        Current
+= SaclLength;

    }

    *ObjectsSecurityDescriptor
= NewSd;//关键。更为新的sd

    return
STATUS_SUCCESS;

}

 

 

 

 

访问权限检查:

下面的函数可以说是安全子系统中最为核心的函数了,用来根据申请者持有的令牌、要求的权限、目标对象的ACL这三个要素,判断是否满足权限要求。

NTSTATUS  //检查访问权限

NtAccessCheck(IN PSECURITY_DESCRIPTOR
SecurityDescriptor,//目标对象的SD(间接表示其ACL

              IN HANDLE TokenHandle,//申请者持有的令牌

              IN ACCESS_MASK DesiredAccess,//申请者申请要求的访问权限

              IN PGENERIC_MAPPING
GenericMapping,//权限映射转换

              OUT PPRIVILEGE_SET PrivilegeSet OPTIONAL,//返回令牌含有的特权集

              IN OUT PULONG PrivilegeSetLength,

              OUT PACCESS_MASK GrantedAccess,//返回最终得到的权限(不会多过申请要求的权限)

              OUT PNTSTATUS AccessStatus)//返回检查结果(只要要求的权限中有一条没满足就失败)

{

    PSECURITY_DESCRIPTOR
CapturedSecurityDescriptor = NULL;

    SECURITY_SUBJECT_CONTEXT
SubjectSecurityContext;

    KPROCESSOR_MODE
PreviousMode = ExGetPreviousMode();

    ACCESS_MASK PreviouslyGrantedAccess = 0;//表示事先授予的访问权限

    PTOKEN
Token;

    NTSTATUS
Status;

    if
(PreviousMode == KernelMode)

    {

        if
(DesiredAccess & MAXIMUM_ALLOWED)//if
用户要求所有可得权限

        {

            *GrantedAccess = GenericMapping->GenericAll;

            *GrantedAccess |= (DesiredAccess
&~ MAXIMUM_ALLOWED);

        }

        else

            *GrantedAccess = DesiredAccess;//要什么给什么

        *AccessStatus
= STATUS_SUCCESS;//来自内核模式的访问要求无需进行权限检查

        return
STATUS_SUCCESS;

    }

    _SEH2_TRY

    {

        ProbeForRead(GenericMapping, sizeof(GENERIC_MAPPING), sizeof(ULONG));

        ProbeForRead(PrivilegeSetLength, sizeof(ULONG), sizeof(ULONG));

        ProbeForWrite(PrivilegeSet, *PrivilegeSetLength,
sizeof(ULONG));

        ProbeForWrite(GrantedAccess, sizeof(ACCESS_MASK), sizeof(ULONG));

        ProbeForWrite(AccessStatus, sizeof(NTSTATUS), sizeof(ULONG));

}

。。。

    Status
= ObReferenceObjectByHandle(TokenHandle,TOKEN_QUERY,SepTokenObjectType,

                                      
PreviousMode, (PVOID*)&Token,NULL);

    if
(Token->TokenType
!= TokenImpersonation)//必须是个模拟令牌

    {

        ObDereferenceObject(Token);

        return
STATUS_NO_IMPERSONATION_TOKEN;

    }

    //if模拟令牌的模拟级别不符合要求

    if
(Token->ImpersonationLevel
< SecurityIdentification)

    {

        ObDereferenceObject(Token);

        return
STATUS_BAD_IMPERSONATION_LEVEL;

    }

    //构造一个令牌上下文

    SubjectSecurityContext.ClientToken = Token;

    SubjectSecurityContext.ImpersonationLevel = Token->ImpersonationLevel;

    SubjectSecurityContext.PrimaryToken = NULL;

    SubjectSecurityContext.ProcessAuditId = NULL;

    SeLockSubjectContext(&SubjectSecurityContext);

 

    if
(DesiredAccess & (WRITE_DAC
| READ_CONTROL | MAXIMUM_ALLOWED))//if
用户要求修改ACL

    {

        if
(SepTokenIsOwner(Token,
SecurityDescriptor))//if本令牌含有的用户是目标对象的拥有者      

{

           if
(DesiredAccess & MAXIMUM_ALLOWED)

             PreviouslyGrantedAccess |= (WRITE_DAC
| READ_CONTROL);//先授予WRITE_DAC等权限

           else

             PreviouslyGrantedAccess |= (DesiredAccess
& (WRITE_DAC | READ_CONTROL));//要就给

            DesiredAccess
&= ~(WRITE_DAC | READ_CONTROL);

        }

    }

    if
(DesiredAccess == 0)//if 用户不在要求其他权限

    {

        *GrantedAccess
= PreviouslyGrantedAccess;

        *AccessStatus
= STATUS_SUCCESS;

    }

    else

    {

        //实质函数,执行权限检查

        SepAccessCheck(SecurityDescriptor,//目标对象的sd

                       &SubjectSecurityContext,//持有的令牌上下文

                       DesiredAccess,//用户要求的权限

                       PreviouslyGrantedAccess,//已得到的权限

                       &PrivilegeSet,

                       GenericMapping,

                       PreviousMode,

                       GrantedAccess,//返回最终得到的权限

                       AccessStatus);//返回检查结果

    }

    SeUnlockSubjectContext(&SubjectSecurityContext);

    ObDereferenceObject(Token);

    return
STATUS_SUCCESS;

}

 

继续看:

BOOLEAN NTAPI

SepAccessCheck(IN PSECURITY_DESCRIPTOR
SecurityDescriptor,//目标对象的sd

               IN PSECURITY_SUBJECT_CONTEXT
SubjectSecurityContext,//持有的令牌上下文

               IN ACCESS_MASK DesiredAccess,//申请要求的权限

               IN ACCESS_MASK PreviouslyGrantedAccess,//之前已得到的权限

               OUT PPRIVILEGE_SET*
Privileges,

               IN PGENERIC_MAPPING
GenericMapping,

               IN KPROCESSOR_MODE AccessMode,

               OUT PACCESS_MASK GrantedAccess,//返回最终得到的权限

               OUT PNTSTATUS AccessStatus)//返回检查结果

{

    ACCESS_MASK
RemainingAccess;//剩余要求的权限(也即剩余尚未满足的权限)

    ACCESS_MASK
TempGrantedAccess = 0;

    ACCESS_MASK
TempDeniedAccess = 0;

    if
(!DesiredAccess) 。。。

    RtlMapGenericMask(&DesiredAccess, GenericMapping);//自我映射转换

    RtlMapGenericMask(&PreviouslyGrantedAccess, GenericMapping);
//自我映射转换

    RemainingAccess
= DesiredAccess;//当前剩余的要求权限

    //取出令牌上下文中的有效令牌(优先使用模拟令牌)

    Token
= SubjectSecurityContext->ClientToken ?

            SubjectSecurityContext->ClientToken
: SubjectSecurityContext->PrimaryToken;

    if
(DesiredAccess & ACCESS_SYSTEM_SECURITY)//if
用户要求这种权限

    {

        Privilege.Luid = SeSecurityPrivilege;//特权名

        Privilege.Attributes = SE_PRIVILEGE_ENABLED;

        //检查令牌是否含有这种特权

        if
(!SepPrivilegeCheck(Token,&Privilege,1,PRIVILEGE_SET_ALL_NECESSARY,AccessMode))

        {

            *AccessStatus = STATUS_PRIVILEGE_NOT_HELD;

            return
FALSE;

        }

        RemainingAccess
&= ~ACCESS_SYSTEM_SECURITY;//剩余的要求权限

        PreviouslyGrantedAccess
|= ACCESS_SYSTEM_SECURITY;//已得到的权限

        if
(RemainingAccess == 0)//如果要求的权限已经全部满足

        {

            *GrantedAccess = PreviouslyGrantedAccess;

            *AccessStatus = STATUS_SUCCESS;

            return
TRUE;

        }

    }

 

    //关键。获取sd中的DACL

    RtlGetDaclSecurityDescriptor(SecurityDescriptor,&Present,&Dacl,&Defaulted);

 

    //if 目标对象没有设立ACL,也即目标对象不设防,那要什么就给什么

    if
(Present == FALSE
|| Dacl == NULL)

    {

        if
(DesiredAccess & MAXIMUM_ALLOWED)

        {

            *GrantedAccess = GenericMapping->GenericAll;

            *GrantedAccess |= (DesiredAccess
& ~MAXIMUM_ALLOWED);

        }

        else

        {

            *GrantedAccess = DesiredAccess | PreviouslyGrantedAccess;

        }

        *AccessStatus
= STATUS_SUCCESS;

        return
TRUE;

}

 

    /*
RULE 2: Check token for ‘take ownership’ privilege */

    if
(DesiredAccess & WRITE_OWNER)//如果用户要求接管目标对象,成为其拥有者

    {

        Privilege.Luid = SeTakeOwnershipPrivilege;

        Privilege.Attributes = SE_PRIVILEGE_ENABLED;

        //检查令牌是否含有接管特权

        if
(SepPrivilegeCheck(Token,&Privilege,1,PRIVILEGE_SET_ALL_NECESSARY,AccessMode))

        {

            RemainingAccess
&= ~WRITE_OWNER;

            PreviouslyGrantedAccess
|= WRITE_OWNER;//已得权限添上这个权限

            if
(RemainingAccess == 0)

            {

                *GrantedAccess = PreviouslyGrantedAccess;

                *AccessStatus = STATUS_SUCCESS;

                return TRUE;

            }

        }

}

 

//if 目标对象有ACL,但ACL表内容为空,即目标对象不允许任何人访问

    if
(Dacl->AceCount
== 0)

    {

        if
(RemainingAccess == MAXIMUM_ALLOWED
&& PreviouslyGrantedAccess != 0)

        {

            *GrantedAccess = PreviouslyGrantedAccess;

            *AccessStatus = STATUS_SUCCESS;

            return TRUE;

        }

        else

        {

            *GrantedAccess = 0;

            *AccessStatus = STATUS_ACCESS_DENIED;

            return
FALSE;

        }

}

 

//下面是目标对象有ACL,且ACL表不为空的情况。这才是最典型的情形。

    if
(DesiredAccess & MAXIMUM_ALLOWED)//if
用户要求该令牌蕴含的所有可得权限(效率低)

    {

        CurrentAce
= (PACE)(Dacl
+ 1);

        for
(i = 0; i
< Dacl->AceCount;
i++)//遍历ACL表项

        {

            if
(!(CurrentAce->Header.AceFlags & INHERIT_ONLY_ACE))

            {

                Sid = (PSID)(CurrentAce + 1);

                if
(CurrentAce->Header.AceType == ACCESS_DENIED_ACE_TYPE)//拒绝类ACE

                {

                    if (SepSidInToken(Token, Sid))//如果本令牌含有被拒绝的用户/

                    {

                        TempAccess = CurrentAce->AccessMask;

                        RtlMapGenericMask(&TempAccess, GenericMapping);

//添加到拒绝权限列表

                        TempDeniedAccess |= (TempAccess
& ~TempGrantedAccess);

                    }

                }

                else if (CurrentAce->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)//允许类ACE

                {

                    if (SepSidInToken(Token, Sid))

                    {

                        TempAccess = CurrentAce->AccessMask;

                        RtlMapGenericMask(&TempAccess,
GenericMapping);

                        //添加到可得权限列表(除去那些拒绝权限)

                        TempGrantedAccess |= (TempAccess
& ~TempDeniedAccess);

                    }

                }

            }

            CurrentAce
= (PACE)((ULONG_PTR)CurrentAce + CurrentAce->Header.AceSize);

        }//end for

        RemainingAccess
&= ~(MAXIMUM_ALLOWED | TempGrantedAccess);//修改剩余要求的权限

        if
(RemainingAccess != 0)//关键。if 要求的权限有部分不满足,返回失败(拒绝访问)

        {

            *GrantedAccess = 0;

            *AccessStatus = STATUS_ACCESS_DENIED;

            return
FALSE;

        }

        //返回最终得到的所有可得权限(不会多过申请者要求的那些权限)

        *GrantedAccess
= TempGrantedAccess | PreviouslyGrantedAccess;

        if
(*GrantedAccess != 0)

        {

            *AccessStatus = STATUS_SUCCESS;

            return
TRUE;

        }

        else

        {

            *AccessStatus = STATUS_ACCESS_DENIED;

            return
FALSE;

        }

    }

 

    //下面是:如果用户只要求得到它所要求的那些权限(而非全部可得权限)

    CurrentAce
= (PACE)(Dacl
+ 1);

    for
(i = 0; i
< Dacl->AceCount;
i++)

    {

        if (!(CurrentAce->Header.AceFlags & INHERIT_ONLY_ACE))

        {

            Sid
= (PSID)(CurrentAce
+ 1);

            if (CurrentAce->Header.AceType
== ACCESS_DENIED_ACE_TYPE)

            {

                if (SepSidInToken(Token,
Sid))//if 令牌包含有被拒绝的用户/

                {

                   
TempAccess = CurrentAce->AccessMask;

                   
RtlMapGenericMask(&TempAccess, GenericMapping);

                    if
(RemainingAccess & TempAccess)

                       
break;//如果已经满足了全部要求,就退出循环了,因此效率高

                }

            }

            else if (CurrentAce->Header.AceType
== ACCESS_ALLOWED_ACE_TYPE)

            {

                if (SepSidInToken(Token,
Sid)) //if 令牌包含有被允许的用户/

                {

                   
TempAccess = CurrentAce->AccessMask;

                   
RtlMapGenericMask(&TempAccess, GenericMapping);

                   
RemainingAccess &= ~TempAccess;//剩余未满足权限又少了一条

                }

            }

        }

        CurrentAce = (PACE)((ULONG_PTR)CurrentAce
+ CurrentAce->Header.AceSize);

}//end for

    if (RemainingAccess
!= 0)//如果要求的权限仍有未满足的部分

    {

        *GrantedAccess
= 0;

        *AccessStatus =
STATUS_ACCESS_DENIED;

        return FALSE;

    }

    *GrantedAccess =
DesiredAccess | PreviouslyGrantedAccess;

    if (*GrantedAccess
== 0)

    {

        *AccessStatus =
STATUS_ACCESS_DENIED;

        return FALSE;

    }

    *AccessStatus =
STATUS_SUCCESS;

    return TRUE;

}

 

 

 

如上,这个函数根据(用户出示的令牌,要求的权限、目标对象的ACL)这三要素,检查用户要求的权限是否可以得到满足。况且从上面的函数还可以看出,访问权限的检查开销大,时间长,因为要比对令牌和ACL,遍历令牌中的所有用户组合ACL表中的所有ACE,所以不可能频繁执行访问权限检,否则会严重影响系统性能。实际上,访问权限的检查集中在CreateFile的时候,如果检查通过,就打开设备/文件,并将得到的访问权限记录在文件句柄中。以后ReadFileWriteFile时就不用再调用上面的函数执行权限检查了,而只需比对文件句柄中的记录的权限即可,这样可大大提高效率;又由于所有的文件操作都必须先打开文件后才能进行,所以,只需把住打开那一关,即可实现权限控制,这也是为什么Windows把权限检查的时机放在打开时候的原因之一。

 

顺道说明:上面的函数是安全子系统内部未导出的函数,下面的函数才导出了,可供驱动程序员调用

BOOLEAN

SeAccessCheck(IN PSECURITY_DESCRIPTOR
SecurityDescriptor,

              IN PSECURITY_SUBJECT_CONTEXT
SubjectSecurityContext,

              IN BOOLEAN SubjectContextLocked,

              IN ACCESS_MASK DesiredAccess,

              IN ACCESS_MASK PreviouslyGrantedAccess,

              OUT PPRIVILEGE_SET*
Privileges,

              IN PGENERIC_MAPPING
GenericMapping,

              IN KPROCESSOR_MODE AccessMode,

              OUT PACCESS_MASK GrantedAccess,

              OUT PNTSTATUS AccessStatus)

{

    BOOLEAN
ret;

 

    if
(AccessMode == KernelMode)//内核模式不用检查访问权限

    {

        if
(DesiredAccess & MAXIMUM_ALLOWED)

        {

            *GrantedAccess = GenericMapping->GenericAll;

            *GrantedAccess |= (DesiredAccess
&~ MAXIMUM_ALLOWED);

            *GrantedAccess |= PreviouslyGrantedAccess;

        }

        else

            *GrantedAccess = DesiredAccess | PreviouslyGrantedAccess;

        *AccessStatus
= STATUS_SUCCESS;

        return
TRUE;

}

//if 模拟令牌的模拟级别小于‘可模拟级别’

    if
((SubjectSecurityContext->ClientToken) &&

      (SubjectSecurityContext->ImpersonationLevel < SecurityImpersonation))

    {

        *AccessStatus
= STATUS_BAD_IMPERSONATION_LEVEL;

        return
FALSE;

    }

    if
(!SubjectContextLocked)

        SeLockSubjectContext(SubjectSecurityContext);

    if
(DesiredAccess & (WRITE_DAC
| READ_CONTROL | MAXIMUM_ALLOWED))

    {

         PACCESS_TOKEN
Token = SubjectSecurityContext->ClientToken ?

             SubjectSecurityContext->ClientToken
: SubjectSecurityContext->PrimaryToken;

        if
(SepTokenIsOwner(Token,SecurityDescriptor))

        {

            if
(DesiredAccess & MAXIMUM_ALLOWED)

                PreviouslyGrantedAccess |= (WRITE_DAC
| READ_CONTROL);

            else

                PreviouslyGrantedAccess |= (DesiredAccess
& (WRITE_DAC | READ_CONTROL));

 

            DesiredAccess
&= ~(WRITE_DAC | READ_CONTROL);

        }

    }

    if
(DesiredAccess == 0)

    {

        *GrantedAccess
= PreviouslyGrantedAccess;

        *AccessStatus
= STATUS_SUCCESS;

        ret
= TRUE;

    }

    else

    {

        //调用内部的实质性函数

        ret
= SepAccessCheck(SecurityDescriptor,

                             SubjectSecurityContext,

                             DesiredAccess,

                             PreviouslyGrantedAccess,

                             Privileges,

                             GenericMapping,

                             AccessMode,

                             GrantedAccess,

                             AccessStatus);

    }

    if
(!SubjectContextLocked)

        SeUnlockSubjectContext(SubjectSecurityContext);

    return
ret;

}

 

WriteFile为例,看看他内部是如何检查访问权限的。

NTSTATUS  NtWriteFile(IN HANDLE FileHandle, 。。。)

{

    Status
= ObReferenceObjectByHandle(FileHandle,

                                       0,//DesiredAccess传递0,表示此处不需检查访问权限

                                      
IoFileObjectType,

                                      
PreviousMode,

                                      
(PVOID*)&FileObject,

                                      
&ObjectHandleInfo);//获得句柄中记录的权限

    if
(!NT_SUCCESS(Status))
return Status;

    if
(PreviousMode != KernelMode)//来自用户模式的调用请求需要执行权限检查

    {

            //检查权限。如果那个句柄中没得FILE_WRITE_DATAFILE_APPEND_DATA权限

            if
(!(ObjectHandleInfo.GrantedAccess
& ((!(FileObject->Flags & FO_NAMED_PIPE)
?

                 FILE_APPEND_DATA : 0) | FILE_WRITE_DATA)))

            {

                ObDereferenceObject(FileObject);

                return STATUS_ACCESS_DENIED;//访问拒绝,权限检查失败返回

            }

    }

 

。。。

}

如上,果不其然,每次进行读写等操作时,只需检查句柄中是否包含相应的权限即可,不再调用神马SeAccessCheck函数从头检查了。这样,大大提高效率。

typedef struct _OBJECT_HANDLE_INFORMATION {  //句柄信息

  ULONG
HandleAttributes;//句柄的属性,如是否可继承

  ACCESS_MASK
GrantedAccess;//该句柄在当初打开对象时得到的权限

}
OBJECT_HANDLE_INFORMATION, *POBJECT_HANDLE_INFORMATION;

 

NTSTATUS

ObReferenceObjectByHandle(IN HANDLE Handle,

                          IN ACCESS_MASK DesiredAccess,//要求的权限(也会在本函数内检查权限)

                          IN POBJECT_TYPE ObjectType,

                          IN KPROCESSOR_MODE AccessMode,

                          OUT PVOID* Object,

                          OUT POBJECT_HANDLE_INFORMATION
HandleInformation OPTIONAL)

{

。。。

HandleEntry = ExMapHandleToPointer(HandleTable, Handle);//根据句柄值获得对应的句柄表项

    if
(HandleEntry)

    {

        ObjectHeader
= ObpGetHandleObject(HandleEntry);

        if
(!(ObjectType) || (ObjectType
== ObjectHeader->Type))

        {

            GrantedAccess
= HandleEntry->GrantedAccess;//看到没。获得那个句柄得到的权限

            //if 来自内核模式则不用检查权限。或者来自用户模式,但要求的权限没超出句柄中的已得权限。也即如果权限检查通过。前面我们看到,NtCreateFile内部在调用本函数时,DesiredAccess参数传的是0,相当于表示不用检查访问权限,它自己会在后面自行检查。

            if
((AccessMode == KernelMode)
|| !(~GrantedAccess & DesiredAccess))

            {

                InterlockedIncrement(&ObjectHeader->PointerCount);

                Attributes = HandleEntry->ObAttributes & OBJ_HANDLE_ATTRIBUTES;

                if (HandleInformation)

                {

                    HandleInformation->HandleAttributes
= Attributes;

                    HandleInformation->GrantedAccess
= GrantedAccess;//返回句柄中的权限

                }

                *Object = &ObjectHeader->Body;

                ExUnlockHandleTableEntry(HandleTable,
HandleEntry);

                KeLeaveCriticalRegion();

                return STATUS_SUCCESS;

            }

            else

            {

                Status
= STATUS_ACCESS_DENIED;//访问拒绝

            }

        }

        else

            Status
= STATUS_OBJECT_TYPE_MISMATCH;

        ExUnlockHandleTableEntry(HandleTable, HandleEntry);

   
}

。。。

}

从上面的函数可以看出,这个函数也是会检查访问权限的,只不过NtCreateFile没要求它检查访问权限而已,因为不必检查,NtCReateFile会在后面自行检查。

 

 

我们说,句柄中记录的权限是当初打开内核对象时,经过权限检查后,最终记录的得到权限。

现在我们就看看对象的打开过程,是如何检查访问权限。Windows中,用来打开内核对象最典型的函数便是CreateFile,我们看。

NTSTATUS

NtCreateFile(PHANDLE FileHandle,//返回生成的句柄(不一定是文件对象的句柄)

             ACCESS_MASK DesiredAccess,//要求的访问权限

             POBJECT_ATTRIBUTES ObjectAttributes,//关键。对象的sd就记录在这个结构中

             PIO_STATUS_BLOCK IoStatusBlock,

             PLARGE_INTEGER AllocateSize,

             ULONG FileAttributes,

             ULONG ShareAccess,

             ULONG CreateDisposition,

             ULONG CreateOptions,

             PVOID EaBuffer,

             ULONG EaLength)

{

    return
IoCreateFile(FileHandle,

                        DesiredAccess,

                        ObjectAttributes,

                        IoStatusBlock,

                        AllocateSize,

                        FileAttributes,

                        ShareAccess,

                        CreateDisposition,

                        CreateOptions,

                        EaBuffer,

                        EaLength,

                        CreateFileTypeNone,

                        NULL,

                        0);

}

 

由于CreateFile的功能是用来打开内核对象,当然,它也可以用来先创建文件,然后再对其打开。

总之,这个函数是用来打开对象,创建句柄的。IoCreateFile它内部会调用ObOpenObjectByName函数,我们看

NTSTATUS

ObOpenObjectByName(IN POBJECT_ATTRIBUTES ObjectAttributes,//包含sd信息

                   IN POBJECT_TYPE ObjectType,

                   IN KPROCESSOR_MODE AccessMode,

                   IN PACCESS_STATE PassedAccessState,//包含令牌、要求的权限、sd等信息

                   IN ACCESS_MASK DesiredAccess,//要求的权限

                   IN OUT PVOID ParseContext,

                   OUT PHANDLE Handle)

{

    PVOID
Object = NULL;

    UNICODE_STRING
ObjectName;

    NTSTATUS
Status;

    POBJECT_HEADER
ObjectHeader;

    PGENERIC_MAPPING
GenericMapping = NULL;

    OB_OPEN_REASON
OpenReason;

    POB_TEMP_BUFFER
TempBuffer;

    *Handle
= NULL;

TempBuffer = ExAllocatePoolWithTag(NonPagedPool,sizeof(OB_TEMP_BUFFER));

//将ObjectAttributes中的sd等信息提取到ObjectCreateInfo中,名字信息提取到ObjectName

    ObpCaptureObjectCreateInformation(ObjectAttributes,AccessMode,TRUE,

                                     
&TempBuffer->ObjectCreateInfo,

                                      &ObjectName);

    if
(!PassedAccessState) //PassedAccessState这个访问状态参数一般传的NULL

    {

        if
(ObjectType) GenericMapping
= &ObjectType->TypeInfo.GenericMapping;

        PassedAccessState
= &TempBuffer->LocalAccessState;

        //构造一个访问状态,用来记录当前线程持有的令牌、要求的权限、目标对象sd等信息

        SeCreateAccessState(&TempBuffer->LocalAccessState,//OUT

                            &TempBuffer->AuxData,//OUT

                            DesiredAccess,//IN

                            GenericMapping);//IN

    }

    if
(TempBuffer->ObjectCreateInfo.SecurityDescriptor)//如果用户给定了一个SD

    {

        PassedAccessState->SecurityDescriptor =

            TempBuffer->ObjectCreateInfo.SecurityDescriptor;//记录到访问状态中

    }

    //在对象目录中查找对象,如果目标对象对一个设备/文件对象,内部还会调用IopParseDevice函数进//行路径解析。在解析的过程中,还会检查当前线程的令牌是否有‘穿越目录’的权限,略。

    Status
= ObpLookupObjectName(TempBuffer->ObjectCreateInfo.RootDirectory,&ObjectName,

                                 TempBuffer->ObjectCreateInfo.Attributes,ObjectType,

                                 AccessMode,ParseContext,

                                 TempBuffer->ObjectCreateInfo.SecurityQos,NULL,

                                 PassedAccessState,//传入

                                
&TempBuffer->LookupContext,

                                
&Object);//返回找到的内核对象 或 内部创建的文件对象

    if
(!NT_SUCCESS(Status))

    {

        ObpReleaseLookupContext(&TempBuffer->LookupContext);

        goto Cleanup;

    }

 

    ObjectHeader
= OBJECT_TO_OBJECT_HEADER(Object);

    if
(ObjectHeader->Flags
& OB_FLAG_CREATE_INFO)

    {

        OpenReason
= ObCreateHandle;//创建时的首次打开

        if
(ObjectHeader->ObjectCreateInfo)

        {

            ObpFreeObjectCreateInformation(ObjectHeader->ObjectCreateInfo);

            ObjectHeader->ObjectCreateInfo = NULL;

        }

    }

    else

    {

        OpenReason
= ObOpenHandle;//以后的打开

    }

    if
(ObjectHeader->Type->TypeInfo.InvalidAttributes
&

        TempBuffer->ObjectCreateInfo.Attributes)

    {

        Status
= STATUS_INVALID_PARAMETER;

        ObpReleaseLookupContext(&TempBuffer->LookupContext);

        ObDereferenceObject(Object);

    }

    else

    {

        //正题。为找到的内核对象 或 文件对象 创建一个句柄(也即打开那个对象)

        Status = ObpCreateHandle(OpenReason,

                                 Object,//目标内核对象(可能是个文件对象)

                                 ObjectType,

                                 PassedAccessState,//(令牌、要求的权限等信息)

                                 0, TempBuffer->ObjectCreateInfo.Attributes,

                                
&TempBuffer->LookupContext,AccessMode,

                                 NULL,Handle);//返回生成的句柄

        if
(!NT_SUCCESS(Status))
ObDereferenceObject(Object);

    }

Cleanup: 。。。

    return
Status;

}

 

里面涉及一个访问状态,用来记录用户持有的令牌、要求的权限等信息。它的结构定义如下

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;//目标对象的sd

  PVOID
AuxData;

  union
{

    INITIAL_PRIVILEGE_SET
InitialPrivilegeSet;

    PRIVILEGE_SET
PrivilegeSet;//令牌含有的所有特权

  } Privileges;

  BOOLEAN
AuditPrivileges;

  UNICODE_STRING
ObjectName;

  UNICODE_STRING
ObjectTypeName;

} ACCESS_STATE, *PACCESS_STATE;

 

这个结构里面有一个SubjectSecurityContext字段,记录用户持有的令牌

typedef struct _SECURITY_SUBJECT_CONTEXT {

  PACCESS_TOKEN
ClientToken;//优先使用这个客户令牌(即模拟令牌)

  SECURITY_IMPERSONATION_LEVEL
ImpersonationLevel;//模拟级别

  PACCESS_TOKEN
PrimaryToken;//主令牌,即所属进程的令牌

  PVOID
ProcessAuditId;

} SECURITY_SUBJECT_CONTEXT, *PSECURITY_SUBJECT_CONTEXT;

 

NTSTATUS //下面的函数用来构造访问状态

SeCreateAccessState(IN OUT PACCESS_STATE AccessState,

                    IN PAUX_ACCESS_DATA
AuxData,

                    IN ACCESS_MASK Access,

                    IN PGENERIC_MAPPING
GenericMapping)

{

    return
SeCreateAccessStateEx(PsGetCurrentThread(),PsGetCurrentProcess(),

                                 AccessState,AuxData,Access,GenericMapping);

}

 

NTSTATUS

SeCreateAccessStateEx(IN PETHREAD Thread,

                      IN PEPROCESS Process,

                      IN OUT PACCESS_STATE AccessState,

                      IN PAUX_ACCESS_DATA
AuxData,

                      IN ACCESS_MASK Access,

                      IN PGENERIC_MAPPING
GenericMapping)

{

    ACCESS_MASK
AccessMask = Access;

    PTOKEN
Token;

    if
((Access & GENERIC_ACCESS)
&& (GenericMapping))

        RtlMapGenericMask(&AccessMask, GenericMapping);

RtlZeroMemory(AccessState,
sizeof(ACCESS_STATE));

//关键。将指定线程的令牌记录到SubjectSecurityContext

    SeCaptureSubjectContextEx(Thread,Process,&AccessState->SubjectSecurityContext);

    AccessState->AuxData = AuxData;

    AccessState->RemainingDesiredAccess  = AccessMask;

    AccessState->OriginalDesiredAccess = AccessMask;

    ExpAllocateLocallyUniqueId(&AccessState->OperationID);

    Token
= AccessState->SubjectSecurityContext.ClientToken ?

            (PTOKEN)&AccessState->SubjectSecurityContext.ClientToken
:

            (PTOKEN)&AccessState->SubjectSecurityContext.PrimaryToken;

    //穿越目录特权

    if
(Token->TokenFlags
& TOKEN_HAS_TRAVERSE_PRIVILEGE)

        AccessState->Flags = TOKEN_HAS_TRAVERSE_PRIVILEGE;

    AuxData->PrivilegeSet = (PPRIVILEGE_SET)((ULONG_PTR)AccessState
+

                                             FIELD_OFFSET(ACCESS_STATE,Privileges));

    if
(GenericMapping) AuxData->GenericMapping = *GenericMapping;

    return
STATUS_SUCCESS;

}

 

VOID

SeCaptureSubjectContextEx(IN PETHREAD Thread,IN PEPROCESS Process,

                          OUT PSECURITY_SUBJECT_CONTEXT
SubjectContext)

{

    BOOLEAN
CopyOnOpen, EffectiveOnly;

    SubjectContext->ProcessAuditId = Process->UniqueProcessId;

    if
(!Thread)

        SubjectContext->ClientToken = NULL;

    else

    {

        SubjectContext->ClientToken = PsReferenceImpersonationToken(Thread,&CopyOnOpen,

                                      &EffectiveOnly,  &SubjectContext->ImpersonationLevel);

    }

    SubjectContext->PrimaryToken = PsReferencePrimaryToken(Process);

}

 

前面ObOpenObjectByName函数中,最终调用了ObpCreateHandle函数来创建句柄,检查访问权限。这个ObpCreateHandle函数是安全子系统中的枢纽函数,所有的对象(普通内核对象、设备对象)打开操作都得经过这里,不仅ObOpenObjectByName会调用它,ObOpenObjectByPointerObInertObject也会调用它,凡是涉及生成句柄的地方都会调用它。因此,把住这个关口,在这个函数里面执行权限检查最好不过。简单一句话:【创建句柄时检查访问权限】

NTSTATUS

ObpCreateHandle(IN OB_OPEN_REASON
OpenReason,//创建时打开、后续的显式打开、复制句柄时的打开等

                IN PVOID Object,//要打开它为其创建句柄的目标对象

                IN POBJECT_TYPE Type OPTIONAL,

                IN PACCESS_STATE AccessState,//关键。包含持有的令牌、要求的权限

                IN ULONG AdditionalReferences,

                IN ULONG HandleAttributes,//句柄的属性

                IN POBP_LOOKUP_CONTEXT
Context,

                IN KPROCESSOR_MODE AccessMode,

                OUT PVOID *ReturnedObject,

                OUT PHANDLE ReturnedHandle)//返回创建的句柄

{

    BOOLEAN
AttachedToProcess = FALSE,
KernelHandle = FALSE;

    ObjectHeader
= OBJECT_TO_OBJECT_HEADER(Object);

    ObjectType
= ObjectHeader->Type;

    if
((Type) && (ObjectType
!= Type))

    {

        if
(Context) ObpReleaseLookupContext(Context);

        return
STATUS_OBJECT_TYPE_MISMATCH;

    }

    NewEntry.Object = ObjectHeader;//指向目标对象(头部)

   
if (HandleAttributes
& OBJ_KERNEL_HANDLE)

    {

        HandleTable
= ObpKernelHandleTable;

        KernelHandle
= TRUE;

        if
(PsGetCurrentProcess() != PsInitialSystemProcess)

        {

            KeStackAttachProcess(&PsInitialSystemProcess->Pcb,
&ApcState);

            AttachedToProcess
= TRUE;

        }

    }

    else

    {

        HandleTable
= PsGetCurrentProcess()->ObjectTable;

}

//关键。这个函数里面会进行权限检查

    Status
= ObpIncrementHandleCount(Object,

                                    
AccessState,//传入令牌、要求的权限

                                    
AccessMode, HandleAttributes,

                                    
PsGetCurrentProcess(), OpenReason);

    if
(!NT_SUCCESS(Status))//if
权限检查不通过等原因造成的失败

    {

        if
(Context) ObpReleaseLookupContext(Context);

        if
(AttachedToProcess) KeUnstackDetachProcess(&ApcState);

        return
Status;

    }

    if
(AccessState->GenerateOnClose)

        HandleAttributes
|= OBJ_AUDIT_OBJECT_CLOSE;

    NewEntry.ObAttributes |= (HandleAttributes
& OBJ_HANDLE_ATTRIBUTES);

    //用户要求的初始访问权限

    DesiredAccess
= AccessState->RemainingDesiredAccess
|

                    AccessState->PreviouslyGrantedAccess;

    //修正最终得到的权限

    GrantedAccess
= DesiredAccess & (ObjectType->TypeInfo.ValidAccessMask |

                                     ACCESS_SYSTEM_SECURITY);

    AccessState->PreviouslyGrantedAccess = GrantedAccess;

    AuxData
= AccessState->AuxData;

    if
(AdditionalReferences)

        InterlockedExchangeAdd(&ObjectHeader->PointerCount,
AdditionalReferences);

    if
(Context) ObpReleaseLookupContext(Context);

    NewEntry.GrantedAccess = GrantedAccess;//关键。记录最终得到的访问权限在句柄中

    Handle
= ExCreateHandle(HandleTable,
&NewEntry);//分配一个句柄表项,并写入句柄表

    if
(Handle)//if 分配成功

    {

        if
(KernelHandle) Handle
= ObMarkHandleAsKernelHandle(Handle);

        *ReturnedHandle
= Handle;

        if
((AdditionalReferences) && (ReturnedObject))

            *ReturnedObject = Object;

        if
(AttachedToProcess) KeUnstackDetachProcess(&ApcState);

        return
STATUS_SUCCESS;

}

。。。

    return STATUS_INSUFFICIENT_RESOURCES;

}

 

继续看:

NTSTATUS

ObpIncrementHandleCount(IN PVOID Object,//目标对象

                        IN PACCESS_STATE AccessState OPTIONAL,//传入的令牌、要求的权限

                        IN KPROCESSOR_MODE AccessMode,

                        IN ULONG HandleAttributes,

                        IN PEPROCESS Process,

                        IN OB_OPEN_REASON OpenReason)

{

    。。。

    if
((OpenReason == ObOpenHandle)
|| ((OpenReason == ObDuplicateHandle)
&& (AccessState)))

    {

        //执行访问权限检查

        if
(!ObCheckObjectAccess(Object,AccessState,// Object与AccessState这两者之间进行比对

                                 TRUE,ProbeMode,&Status))

        {

            goto
Quickie;

        }

}

。。。

    return
Status;

Quickie:

    ObpReleaseObjectLock(ObjectHeader);

    return
Status;

}

 

 

继续:

BOOLEAN

ObCheckObjectAccess(IN PVOID Object,//目标对象

                    IN OUT PACCESS_STATE AccessState,//用户的令牌、要求的访问权限

                    IN BOOLEAN LockHeld,

                    IN KPROCESSOR_MODE AccessMode,

                    OUT
PNTSTATUS ReturnedStatus)

{

    POBJECT_HEADER
ObjectHeader;

    POBJECT_TYPE
ObjectType;

    PSECURITY_DESCRIPTOR
SecurityDescriptor = NULL;

    BOOLEAN
SdAllocated;

    NTSTATUS
Status;

    BOOLEAN
Result;

    ACCESS_MASK
GrantedAccess;

    PPRIVILEGE_SET
Privileges = NULL;

    ObjectHeader
= OBJECT_TO_OBJECT_HEADER(Object);

    ObjectType
= ObjectHeader->Type;

 

    //获取对象的sd。普通对象的sd直接从通用对象头中的sd获得,设备对象的sd从其内部结构中的sd获得,文件对象的sd则从相应的文件系统获得(FAT32不支持ACL

    Status
= ObGetObjectSecurity(Object,
&SecurityDescriptor, &SdAllocated);

    if
(!NT_SUCCESS(Status))

    {

        *ReturnedStatus
= Status;

        return
FALSE;

    }

    else
if (!SecurityDescriptor)//目标对象没有sd,则表示目标对象不设防

    {

        *ReturnedStatus
= Status;

        return
TRUE;

    }

    SeLockSubjectContext(&AccessState->SubjectSecurityContext);

    //果然。在此调用这个函数执行权限检查

    Result
= SeAccessCheck(SecurityDescriptor,//目标对象的sd

                           &AccessState->SubjectSecurityContext,//持有的令牌

                           TRUE,

                           AccessState->RemainingDesiredAccess,//剩余要求的权限

                           AccessState->PreviouslyGrantedAccess,//已得权限

                           &Privileges,

                           &ObjectType->TypeInfo.GenericMapping,

                           AccessMode,

                           &GrantedAccess,

                           ReturnedStatus);

    if
(Privileges)

    {

        Status
= SeAppendPrivileges(AccessState,
Privileges);

        SeFreePrivileges(Privileges);

    }

    if
(Result)//if 权限检查通过

    {

        AccessState->RemainingDesiredAccess &= ~(GrantedAccess |MAXIMUM_ALLOWED);//一般为0

        AccessState->PreviouslyGrantedAccess |= GrantedAccess;//一般就是最初要求的所有权限

}

//SACL用户行为日志警报相关,略。

    SeOpenObjectAuditAlarm(&ObjectType->Name,Object,NULL,SecurityDescriptor,AccessState,

                           FALSE,Result,AccessMode,&AccessState->GenerateOnClose);

    SeUnlockSubjectContext(&AccessState->SubjectSecurityContext);

    ObReleaseObjectSecurity(SecurityDescriptor, SdAllocated);

    return
Result;

}

 

如上,在打开对象,创建句柄时,系统将执行访问权限检查,检查通过就准许打开对象,创建句柄,并将得到的权限记录在句柄中。以后应用程序拿这个句柄去进行读写等操作时(ReadFilehandle,…)、WriteFile(handle,…)),系统只需检查句柄中记载的权限是否满足,就可以确保用户权限安全了。

CreateFile这个函数可以打开任意具有名字的内核对象,调用这个函数时,用户指定自己想要的权限,传给这个函数,系统就会在内部根据当先线程持有的令牌、目标对象的ACL、和要求的权限 进行检查。当然,用户也可以传递一个GENERIC_ALL标志给CreateFile函数,表示想要得到本令牌(即本用户/组)在目标对象上的所有可得权限。

特别的,当CreateFile要打开的内核对象是个设备对象时,其路径解析函数IopParseDevice会在内部创建的一个文件对象,然会为文件对象创建一个句柄,再返回文件句柄,因而,不存在‘设备句柄’这种概念一说的,所有文献中有关hDevice的说法都是错误的,应该叫hFile。不过,文件句柄并不一定表示文件,因为,文件句柄只是文件对象的句柄,而文件对象仅仅表示对设备的一次打开上下文,或者表示‘打开者’。

只有当用户打开物理卷设备(如磁盘卷、光盘卷等),并指定一个文件路径时,这种方式打开设备后生成的文件对象才对应着一个磁盘文件。当为这种类型的文件对象创建一个句柄时(也即要打开这种类型的文件对象时),系统会调用上面的函数进行权限检查。具体的,系统会请求相应的文件系统查询得到该文件的ACL,然后与用户要求的权限进行比对,完成判断。如果是ntfs文件系,,必然返回存储在该文件中的ACL,若是FAT32系统,就没有ACL,表示不支持用户权限,不设防。

最后总结一句话:【创建句柄,检查权限】

打赏