您的位置:首页 > 其它

转:浅析Windows的访问权限检查机制

2016-07-13 09:14 330 查看
Author: DanielKing http://drops.wooyun.org/tips/11803

0x00 简介

在操作系统中,当我们提到安全的时候,意味着有一些资源需要被保护,在Windows操作系统中,这些被保护的资源大多以对象(Object)的形式存在,对象是对资源的一种抽象。每个对象都可以拥有自己的安全描述符(Security Descriptor),用来描述它能够被谁、以何种方式而访问。这些对象是客体,那么访问这些对象的主体是什么呢?这些主体就是操作系统中的各个进程,更准确地说是这些进程中的每个线程。每个进程都有一个基本令牌 (Primary Token),可以被进程中的每个线程所共享,而有些线程比较特殊,它们正在模拟(Impersonating)某个客户端的身份,因此拥有一个模拟令牌(Impersonation
Token),对于这些线程来说,有效令牌就是模拟令牌,而其他线程的有效令牌则是其所属进程的基本令牌。当主体尝试去访问客体时,操作系统会执行访问权限检查(Access Check),具体来说,是用主体的有效令牌与客体的安全描述符进行比对,从而确定该次访问是否合法。为了提高效率,访问权限检查(Access Check)提供了一种缓存机制,即只有当一个对象被一个进程创建或者打开时,才会进行访问权限检查,访问权限检查的结果会被缓存到进程的句柄表(Handle Table)中,该进程对该对象的后续操作只需要查询句柄表中内容即可确定访问的合法性,而不必每次都执行开销更大的访问权限检查(Access
Check)。因为对象和进程都有各自的继承层次,所以对象的安全描述符和进程的令牌从逻辑上讲也是可以继承的,比如文件系统中的文件可以继承其所在目录的安全描述符,子进程可以继承父进程的令牌,这种继承机制使让对象和进程拥有了缺省的安全属性,因此,在一个系统中的安全描述符和令牌的实例数目非常有限,大多数情况下是从父辈们继承过来的缺省值。



0x01 访问权限检查

访问权限检查包含了多个维度上的检查:

DACL检查

DACL(自主访问控制表,Discretionary Access Control List)检查是最基本的访问权限检查。



在安全描述符中存在着一个DACL表(见安全描述符图解),里面描述了拥有何种身份的主体申请的何种访问权限会被允许或者拒绝。DACL表中的每个表项拥有如下的结构:
We'll define the structure of the predefined ACE types.  Pictorally
the structure of the predefined ACE's is as follows:

3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+---------------+-------+-------+---------------+---------------+
|    AceFlags   | Resd  |Inherit|    AceSize    |     AceType   |
+---------------+-------+-------+---------------+---------------+
|                              Mask                             |
+---------------------------------------------------------------+
|                                                               |
+                                                               +
|                                                               |
+                              Sid                              +
|                                                               |
+                                                               +
|                                                               |
+---------------------------------------------------------------+

Mask is the access mask associated with the ACE.  This is either the
access allowed, access denied, audit, or alarm mask.

Sid is the Sid associated with the ACE.


其中,对于DACL来说,AceType可以有

两种可能;Mask是一个代表相关权限集合的位图;主体的身份使用Sid(Security Identifier)来表示,这是一个变长的数据结构:

Sid是Windows安全系统中一个基本类型,可以用来唯一标识多种不同种类的实体,比如标识用户身份与组身份,标识完整性级别、可信赖级别,AppContainer中的Capability等等。

DACL检查过程是这样的,如果对象的安全描述符为空,代表着这个对象不受任何保护,即可以被任何令牌代表的主体访问;如果对象拥有一个非空的安全描述符,但是安全描述符中的Dacl成员为空,代表该对象没有显式地赋予任何主体任何访问权限,即不允许被访问;如果Dacl成员非空,就按照表中的顺序逐一与当前主体的身份Sid进行比对,直到该主体所请求的权限被全部显式允许,或者某一请求的权限被显式地拒绝,检查结束;如果直到Dacl表检查结束,主体申请的权限仍未被全部显式允许,那么仍然代表拒绝。

根据以上规则可知,DACL中各个表项的顺序对访问权限检查的结果至关重要,比如说有一条有关某主体的显式拒绝的表项位于表的最后,如果位于其之前的表项已经显式允许了这一主体申请的权限,那么这条显式拒绝的表项将起不到任何作用。基于此种原因,如果通过Windows右键安全选项菜单添加的DACL表项,显式拒绝的表项永远被置于显式允许表项前面,以保证其有效性。直接通过API添加则不受此种限制。

特权(Privileges)以及超级特权(Super Privileges)检查

所谓特权,是指那些不与某一具体对象相关的、影响整个系统的访问权限。而超级特权是这样的一些特权,你一旦拥有其中的一项超级特权,就拥有了取得所有其他特权和权限的潜质,比如SeDebugPrivilege,在一般情况下,一旦拥有该特权,就可以任意读写目标进程的内存,而不仅仅局限于打开该进程获得句柄时得到的那些权限及特权。

完整性级别(Integrity Level)和强制策略(Mandatory Policy)检查

完整性级别(IL)机制是Windows Vista引入的重要的安全机制,建立在其基础上的沙盒机制十分简单又异常强大。

受限令牌(Restricted Token)检查

通过创建受限令牌,可以获得一个普通令牌所有拥有的权限集合的一个子集,用来进行一些低权限操作,降低安全风险。

AppContainer's Capabilities检查

LowBox令牌以及AppContainer's Capabilities组身份是从Windows 8开始引入的安全机制,它提供了另一种更加复杂的沙盒机制。Capabilities与Android系统中对于App权限限制非常类似。

可信级别(Trust Level)检查

完整性级别检查是一种粗粒度的、相对稳定的访问权限控制手段;伴随着Windows的不断进化,对于签名机制的依赖越来越明显,签名代表了一种可信赖程度。笔者认为,Windows正在逐渐建立一套完善的基于可信赖级别的权限检查机制,受保护进程(Protected Process)就是这是这套机制中一部分。

上面的内容简单介绍了Windows的权限检查模型,以及权限检查的维度;下面将介绍一些实现细节。

0x02 令牌

TOKEN结构体

Token结构体是访问权限检查中的代表主体身份的核心数据结构,下面展示的是Windows 10 x64平台下的构成。

我们比较关注其中的特权位图和三个代表主体身份的Sid数组:UserAndGroups,RestrictedSids,Capabilities。



Privileges位图

特权在用户态是通过LUID来表示,在内核结构体_TOKEN中是使用三个位图来表示:

分别代表当前的主体可以选用的特权集合(Present)、已经打开的特权集合(Enabled)和默认打开的特权集合(EnabledByDefault),后两个集合应该是Present集合的子集。

与代表主体身份的Sid数组不同,特权集合的表示简单,而且没有任何保护。从用户态通过API只能打开或者关闭某一项已经存在的特权,而不能增加可选的特权,换句话说,用户态只能修改Enabled特权集合,而不能修改Present特权集合;从内核态可以直接修改Present特权集合,比如给普通进程增加SeAssignPrimaryTokenPrivilege特权,以便为子进程显式指定令牌,而不是继承当前进程的令牌,可以达到扩大子进程权限的效果。

代表主体身份的Sid数组

三个代表主体身份的Sid数组分别是:

UserAndGroups:

代表着主体的普通用户身份和组身份,是不可或缺的成员;

RestrictedSids:

可选成员,如果不为空,则代表着当前的令牌是受限令牌,受限令牌通过从普通令牌中过滤掉一些比较敏感的身份转化而来,受限令牌中的UserAndGroups成员与普通令牌相同,但是RestriectedSids成员不为空,里面保存着过滤后的身份子集;由于在访问权限检查时,三个身份Sid数组要同时进行检查,只有结果都通过才允许该次访问,因此通过增加代表着受限制的权限集合的RestrictedSids成员,既达到了限制令牌权限的目的,又在UserAndGroups成员中保留了原有令牌的完整身份信息。

Capabilities:

可选成员,仅用于AppContainer,其中的Sid代表着与App相关的身份,比如拥有连接网络、访问当前位置等权限的身份。

这三个Sid数组都关联了哈希信息,以保护其完整性,因此,即使从内核态直接修改,也会因为无法通过完整性验证而失败。不过好在哈希的算法非常简单,下面展示的就是Windows 10 x64平台下面该算法的C++演示代码:

该算法有两处明显的局限,只计算每个Sid的最后一个Sub Authority的最低位字节,以及最多只有前64个Sid参与计算。

UAC与关联令牌

UAC是Vista版本引入的比较重要的安全机制,很多用户抱怨它带来的不便性,然而它就像Linux操作系统中的sudo一样,是保护系统安全的一道重要屏障。当用户登录Windows时,操作系统会为用户生成一对初始令牌,分别是代表着用户所拥有的全部权限的完整版本令牌(即管理员权限令牌),以及被限制管理员权限后的普通令牌,二者互为关联令牌;此后,代表用户的进程所使用的令牌都是由普通令牌继承而来,用来进行常规的、非敏感的操作;当用户需要进行一些需要管理员权限的操作时,比如安装软件、修改重要的系统设置时,都会通过弹出提权对话框的形式提示用户面临的风险,征求用户的同意,一旦用户同意,将会切换到当前普通令牌关联的管理员权限令牌,来进行敏感操作。通过这种与用户交互的方式,避免一些恶意程序在后台稍稍执行敏感操作。

关联令牌是通过Logon Session来实现的,下图展示了其大致原理:



并非所有的令牌都有关联令牌,比如一直运行在较高权限下不需要进行提权操作的令牌。

0x03 对象

对象的结构

对象是对操作系统中需要保护和集中管理的资源的一种抽象,对象拥有统一的头部,保存着抽象出来的信息。



对象的头部包含着指向安全描述符的指针。

安全描述符

安全描述符保存着作为访问权限检查客体的对象的主要信息。它包含着4个关键成员,根据这4个成员是嵌入在结构体的尾部,还是引用自外部缓存位置的差异,可以分为两种不同的结构形式:

Owner和Group代表了该安全描述符的属主Sid,Sacl是系统访问控制表(System Access Control List)的简称,里面可以包含当前对象的审计(Audit)、完整性级别(Integrity Level)以及可信赖级别(Trust Level)等信息,与前面提到的Dacl共用同一结构体。

可选头部

对象除了拥有共同的固定头部外,还可以有可选的头部,保存着名称等可选信息。通过固定头部的InfoMask成员查表可以得到可选头部的位置,如图所示:



对象目录

只有部分对象拥有名称信息,它们被称为命名对象。命名对象的主要作用是方便对象在不同的进程中共享,它们被按类别编纂成对象目录,因此可以通过在对象目录中的路径信息找到该对象。对象目录的实现如下图所示:



对象类型

对象类型是对象中非常重要的信息,Windows将对象的类型信息从同一类对象中抽象出来,保存成一个单独的类型对象。系统中全部的类型对象被集中放置在一个表中,对象通过维护一个指向该表的索引(TypeIndex)来表明当前对象的类型。这个索引值直接保存在对象的头部,而对象体与对象头部直接相邻,如果对象体被损坏,有可能导致头部的索引值被改变,使所谓的类型混淆(Type Confusion)利用成为可能。为了缓解这一问题,Windows 10对该索引值做了特殊保护,如下图所示:



这种保护方式简单而强大,新的索引值由3部分经过异或操作得到:类型对象在类型对象表中的真实索引值,对象头部地址的第二个字节(即第8到第15位),保存在ObHeaderCookie全局变量中的因每次系统启动而异的Cookie字节。其中,ObHeaderCookie的引入,使同一类型的对象在不同机器上,甚至是同一机器上两次启动之间的索引值不同,然而这样并不足以缓解类型混淆利用,我们还可以利用信息泄露(Info Leak)来绕过(Bypass)该保护,因此还引入了对象头部地址,使得在同一时刻、同一系统中的两个相同类型对象的不同实例间的索引值也不相同,从而有效地缓解类型混淆利用。

0x04 完整性级别检查

完整性级别(IL)检查是沙盒的最简单实现方式,通过完整性级别在对象和进程层次之间的继承关系,就如同在操作系统中建立起了“世袭制度”。通过严格控制完整性级别的继承规则,以及设置严格的完整性级别检查制度,可以保证“出身低微”的主体无法访问到“出身高贵”的客体资源。

完整性拥有以下几个级别:

主体的缺省完整性级别是SeUntrustedMandatorySid,而客体的缺省完整性级别是SeMediumMandatorySid,这种差异也进一步地强化了这种“世袭制度”。

0x05 保护进程

保护进程(Protected Process)是Windows操作系统为了保护某些关键进程,防止其被普通进程调试、注入、甚至是读取内存信息而建立起来的一种安全机制。

保护进程与其他普通进程的区别在于,保护进程的Protection成员不为0。

保护进程的Type成员可以代表两种保护类型:Protected Process(PP),Protected Process Lite(PPL),两者的区别在于PP保护进程拥有更高的权限。保护进程的Signer成员代表该进程的签名发布者的类别。对于Signer为PsProtectedSignerWindows(5)和PsProtectedSignerTcb(6)的保护进程,其Type和Signer信息会被抽取出来,组装成一个Sid,代表着该进程的可信赖级别,保存到基本令牌中的TrustLevelSid成员中。当一个令牌中的TrustLevelSid被使用时,需要保证与当前进程的Protection信息保持同步,这主要是为了应对令牌在不同进程间传递时(比如子进程继承父进程的令牌)导致的TrustLevelSid成员过时的情形。

当调试或者创建一个进程时,会调用内核函数PspCheckForInvalidAccessByProtection进行权限检查,该函数根据当前进程以及目标进程的Protection来判定当前操作是否需要遵守保护进程规定的权限限制,具体判定规则如下:
如果操作来自于内核态代码,不需要遵守限制;
如果目标进程的Protection成员为空,表示目标进程不是保护进程,不需要遵守限制;
如果当前进程是PP类型保护进程,该类型保护进程拥有最高权限,不需要遵守限制;
如果当前进程与目标进程都是PPL类型保护进程,需要根据RtlProtectedAccess表来判断当前进程的Signer是否优先于(dominate)目标进程的Signer,如果是,不需要遵守限制;
其他情况,需要遵守限制。

RtlProtectedAccess表如下所示:

其中,Process Restrictions以及Thread Restrictions分别代表着进程和线程的权限限制,详解如下图:



我们可以通过NtDebugActiveProcess以及NtCreateUserProcess的演示代码来理解保护进程及其限制的逻辑:

0x06 结语

Windows操作系统中的权限检查机制以及安全系统一直都在不断地进化着,探索其实现的细节以及推测其背后的设计理念是一件非常有趣有益的事情,本文仅仅是作者在探索中的小结,难免有疏漏甚至错误,更加详细的内容可以参考下列材料,或者直接分析和调试Windows内核,以了解最新最真实的Windows。
James Forshaw: The Windows Sandbox Paradox
James Forshaw: A Link to the Past: Abusing Symbolic Links on Windows
Alex Ionescu: The Evolution of Protected Processes Part 1: Pass-the-Hash Mitigations in Windows 8.1
Alex Ionescu: The Evolution of Protected Processes Part 2: Exploit/Jailbreak Mitigations, Unkillable Processes and Protected Services
Alex Ionescu: Protected Processes Part 3 : Windows PKI Internals (Signing Levels, Scenarios, Root Keys, EKUs & Runtime Signers)
Daniel & Azure: Did You Get Your Token?


为您推荐了适合您的技术文章:

学习/认识CPU的GDT
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: