[15]Windows内核情景分析 --- 权限管理
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数组,即N级ID
} SID, *PISID;
//一个ID就像一个文件路径一样,由签发机关 + N级ID组成。
//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,并返回新sd到NewDescriptor参数中
    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的时候,如果检查通过,就打开设备/文件,并将得到的访问权限记录在文件句柄中。以后ReadFile、WriteFile时就不用再调用上面的函数执行权限检查了,而只需比对文件句柄中的记录的权限即可,这样可大大提高效率;又由于所有的文件操作都必须先打开文件后才能进行,所以,只需把住打开那一关,即可实现权限控制,这也是为什么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_DATA和FILE_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会调用它,ObOpenObjectByPointer和ObInertObject也会调用它,凡是涉及生成句柄的地方都会调用它。因此,把住这个关口,在这个函数里面执行权限检查最好不过。简单一句话:【创建句柄时检查访问权限】
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;
}继续看:
TSTATUS
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;
}如上,在打开对象,创建句柄时,系统将执行访问权限检查,检查通过就准许打开对象,创建句柄,并将得到的权限记录在句柄中。以后应用程序拿这个句柄去进行读写等操作时(ReadFile(handle,…)、WriteFile(handle,…)),系统只需检查句柄中记载的权限是否满足,就可以确保用户权限安全了。
CreateFile这个函数可以打开任意具有名字的内核对象,调用这个函数时,用户指定自己想要的权限,传给这个函数,系统就会在内部根据当先线程持有的令牌、目标对象的ACL、和要求的权限 进行检查。当然,用户也可以传递一个GENERIC_ALL标志给CreateFile函数,表示想要得到本令牌(即本用户/组)在目标对象上的所有可得权限。
特别的,当CreateFile要打开的内核对象是个设备对象时,其路径解析函数IopParseDevice会在内部创建的一个文件对象,然会为文件对象创建一个句柄,再返回文件句柄,因而,不存在‘设备句柄’这种概念一说的,所有文献中有关hDevice的说法都是错误的,应该叫hFile。不过,文件句柄并不一定表示文件,因为,文件句柄只是文件对象的句柄,而文件对象仅仅表示对设备的一次打开上下文,或者表示‘打开者’。
只有当用户打开物理卷设备(如磁盘卷、光盘卷等),并指定一个文件路径时,这种方式打开设备后生成的文件对象才对应着一个磁盘文件。当为这种类型的文件对象创建一个句柄时(也即要打开这种类型的文件对象时),系统会调用上面的函数进行权限检查。具体的,系统会请求相应的文件系统查询得到该文件的ACL,然后与用户要求的权限进行比对,完成判断。如果是ntfs文件系,,必然返回存储在该文件中的ACL,若是FAT32系统,就没有ACL,表示不支持用户权限,不设防。
最后总结一句话:【创建句柄,检查权限】
[15]Windows内核情景分析 --- 权限管理的更多相关文章
- [3]windows内核情景分析--内存管理
		32位系统中有4GB的虚拟地址空间 每个进程有一个地址空间,共4GB,(具体分为低2GB的用户地址空间+高2GB的内核地址空间) 各个进程的用户地址空间不同,属于各进程专有,内核地址空间部分则几乎完全 ... 
- [16]Windows内核情景分析 --- 服务管理
		随时可以看到任务管理器中有一个services.exe进程,这个就是系统的服务控制管理进程,简称SCM 这个进程专门用来管理服务(启动.停止.删除.配置等操作) 系统中所有注册的服务都登记在\HKEY ... 
- 几个常用内核函数(《Windows内核情景分析》)
		参考:<Windows内核情景分析> 0x01 ObReferenceObjectByHandle 这个函数从句柄得到对应的内核对象,并递增其引用计数. NTSTATUS ObRefer ... 
- [1]windows 内核情景分析---说明
		本文说明:这一系列文章(笔记)是在看雪里面下载word文档,现转帖出来,希望更多的人能看到并分享,感谢原作者的分享精神. 说明 本文结合<Windows内核情景分析>(毛德操著).< ... 
- windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数
		windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数 1.KeRaiseIrql函数 这个 KeRaiseIrql() 只是简单地调用 hal 模块的 KfRa ... 
- [14]Windows内核情景分析 --- 文件系统
		文件系统 一台机器上可以安装很多物理介质来存放资料(如磁盘.光盘.软盘.U盘等).各种物理介质千差万别,都配备有各自的驱动程序,为了统一地访问这些物理介质,windows设计了文件系统机制.应用程序要 ... 
- [4]Windows内核情景分析---内核对象
		写过Windows应用程序的朋友都常常听说"内核对象"."句柄"等术语却无从得知他们的内核实现到底是怎样的, 本篇文章就揭开这些技术的神秘面纱. 常见的内核对象 ... 
- [11]Windows内核情景分析---设备驱动
		设备驱动 设备栈:从上层到下层的顺序依次是:过滤设备.类设备.过滤设备.小端口设备[过.类.过滤.小端口] 驱动栈:因设备堆栈原因而建立起来的一种堆栈 老式驱动:指不提供AddDevice的驱动,又叫 ... 
- [7] Windows内核情景分析---线程同步
		基于同步对象的等待.唤醒机制: 一个线程可以等待一个对象或多个对象而进入等待状态(也叫睡眠状态),另一个线程可以触发那个等待对象,唤醒在那个对象上等待的所有线程. 一个线程可以等待一个对象或多个对象, ... 
随机推荐
- ms sql server读取xml文件存储过程-sp_xml_preparedocument
			最近要在存储过程中读取xml中节点的值,然后进行sql操作: 要使用到的系统存储过程如下:sp_xml_preparedocument create procedure [dbo].[pro_Test ... 
- js如何判断哪个按钮被点击了?
			用事件委托,然后判断target,代码如下: $(docuement).on('click',function(event){ event.target... }) 例如:点击.c1之外任意地方的时候 ... 
- 如何获取Android系统APP的Package Name和Activity Name
			有两种方式: 方式一.aapt.exe查看Package Name和入口Activity Name (1) 在安装路径android-sdk\platform-tools下查找aapt.exe: 如 ... 
- bug:  使用 iOS 系统方法进行二维码扫描,扫描区域的问题
			项目里用到了扫描,出现了bug:感觉就是把二维码正好框在扫描框里扫不出来,然后把镜头离二维码拉远,扫描速度很慢,但是能扫描出来.网上找了下代码,发现也没啥哪里不一样,感觉很坑啊,后面发现不设置 AVC ... 
- spring mvc 资源映射配置
			在springmvc配置文件中添加 <mvc:resources location="/css/" mapping="/css/**"/> < ... 
- JSONObject,String,Map互相转换
			JSONObject和String相互转换 JSONObject jsonObject = new JSONObject(); JSONArray jsonArray = new JSONArray( ... 
- 20170719 Mysql 配置远端Mysql访问,增加表/存储过程
			-- 1 .在windows 环境中安装Mysql 会按照到默认的C盘当中,如何修改呢--? -- 2. 如何只安装客户端不安装Mysql 数据库服务 --? -- 3. 表的特殊列,默认采用函数值 ... 
- PHP 测试杂项
			// 驼峰转下划线 function humpToUnderline($str){ if(empty($str)){ return ""; } $arr = str_split($ ... 
- 【Redis】主从同步
			Redis提供了主从复制功能,主要是为了保证服务的高可用性.在redis.conf配置文件中通过设置,可以开启主从复制功能.或者在客户端中使用slaveof 命令开启该功能. slaveof < ... 
- [LeetCode] 610. Triangle Judgement_Easy tag: SQL
			A pupil Tim gets homework to identify whether three line segments could possibly form a triangle. Ho ... 
