[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内核情景分析---线程同步
基于同步对象的等待.唤醒机制: 一个线程可以等待一个对象或多个对象而进入等待状态(也叫睡眠状态),另一个线程可以触发那个等待对象,唤醒在那个对象上等待的所有线程. 一个线程可以等待一个对象或多个对象, ...
随机推荐
- Chap3:文件系统中跳转[The Linux Command Line]
1 introduce the following commands pwd - Print name of current working directory cd-Change directory ...
- c++ Stl 随笔
1. template <class InputIterator, class Distance> void advance (InputIterator& it, Distanc ...
- Python中Mock的示例(转)
原文:https://segmentfault.com/a/1190000008753754 一些常用的mock示例 先简单定义个类,方便举例: class Person: def __init__( ...
- WPF 参数在Page见传递
void goButton_Click(object sender, RoutedEventArgs e) { this.NavigationService.Navigate(new ContentP ...
- 谈谈javascript数组排序方法sort()的使用,重点介绍参数使用及内部机制?
语法:arrayObject.sort(sortby) 参数sortby可选,规定排序顺序,必须是函数: 注:如果调用该方法时没有使用参数,将按字符编码的顺序进行排序,要实现这一点,首先应把数组的元素 ...
- 安全需求-建模归类——By Me
漏洞与Bug并不等同,他们之间的关系基本可以描述为:大部分的Bug影响功能性,并不涉及安全性,也就不构成漏洞:大部分的漏洞来源于Bug,但并不是全部,它们之间只是有一个很大的交集.可以用如下这个图来展 ...
- InnoDB启用大内存页
在 Linux 操作系统上运行内存需求量较大的应用程序时,由于其采用的默认页面大小为 4KB,因而将会产生较多 TLB Miss 和缺页中断,从而大大影响应用程序的性能.当操作系统以 2MB 甚至更大 ...
- windows(64位)下使用curl安装
windows(64位)下使用curl安装 转自:https://blog.csdn.net/wkj001/article/details/54889907 2017年02月06日 09:46:47 ...
- 帝国cms搜索关键字调用标签(showsearch)怎么用
前面ytkah介绍了如何让帝国CMS7.2搜索模板支持动态标签调用,现在我们来说说怎么调用帝国cms搜索关键字调用标签(showsearch).在帝国cms后台那边的使用方法:[showsearch] ...
- css垂直居中怎么设置?文字上下居中和图片垂直居中
css 居中分css垂直居中和css水平居中,水平居中平时比较常用,这里我们主要讲css上下居中的问题.垂直居中又分为css文字上下居中和css图片垂直居中,下面我们就分别来介绍一下. css文字上下 ...