windows源码分析(14)-权限管理篇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、机器IDUCHAR 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域签发机关 #defineSECURITY_RESOURCE_MANAGER_AUTHORITY {0,0,0,0,0,9}typedef struct _TOKEN{TOKEN_SOURCE TokenSource;LUID TokenId; 令牌IDLUID 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;一个令牌最重要的信息便是它所包含的【特权、用户、组】下面的函数用于创建一个SIDNTSTATUSRtlAllocateAndInitializeSid(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;//固定为1pSid->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结构指针,记录了该线程使用的模拟令牌信息。
下面的函数用来创建一个令牌(令牌本身也是一种内核对象)NTSTATUSNtCreateToken(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,//默认的ACLIN 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(UL ONG));ProbeForRead(TokenDefaultDacl,sizeof(TOKEN_DEFAULT_DACL),sizeof(ULON G));ProbeForRead(TokenSource,sizeof(TOKEN_SOURCE),sizeof(ULONG));nTokenPrivileges = TokenPrivileges->PrivilegeCount;}。