[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,表示不支持用户权限,不设防。

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

posted @ 2018-12-18 02:06  jadeshu  阅读(626)  评论(0编辑  收藏  举报