UAC原理.docx
- 文档编号:3730173
- 上传时间:2022-11-25
- 格式:DOCX
- 页数:16
- 大小:27.24KB
UAC原理.docx
《UAC原理.docx》由会员分享,可在线阅读,更多相关《UAC原理.docx(16页珍藏版)》请在冰豆网上搜索。
UAC原理
自从Windows2000以来,Windows开发者一直试图为用户创造一个安全稳妥的工作环境。
Windows2000引入了一种名为“受限访问令牌(RestrictedToken)”的技术,能够有效地限制应用程序的许可和权限。
WindowsXP则在安全方面更进一步,不过对于普通用户来讲,这种安全控制却并不是那么的深入人心……直到现在为止还是如此。
不管你最初反对的理由是什么,现在用户帐号控制(UserAccountControl,UAC)就摆在你的面前,其实它并不像批评中所说的那样一无是处。
作为开发者的我们有责任掌握这项技术,进而让我们所开发的Vista应用程序不会总是弹出那些“讨厌”的提示窗口。
在《WindowsVistaforDevelopers》系列文章的第四部分中,我们将从实际出发探索一下UAC的功能,特别是如何以编程方式使用这些特性。
什么是安全上下文(SecurityContext)?
安全上下文指的是一类定义某个进程允许做什么的许可和权限的集合。
Windows中的安全上下文是通过登录会话(LogonSession)定义的,并通过访问令牌维护。
顾名思义,登录会话表示某个用户在某台计算机上的某次会话过程。
开发者可以通过访问令牌与登录会话进行交互。
访问令牌所有用的许可和权限可以与登录会话的不同,但始终是它的一个子集。
这就是UAC工作原理中的最核心部分。
那么UAC的工作原理是什么呢?
在WindowsVista操作系统中,有两种最主要的用户帐号:
标准用户(standuser)和管理员(administrator)。
你在计算机上创建的第一个用户将成为管理员,而后续用户按照默认设置将成为标准用户。
标准用户用来提供给那些不信任自己能够控制整个计算机的用户,而管理员则为那些希望能够完全控制计算机的用户所准备。
与先前版本的Windows不同,在WindowsVista中,你不再需要以标准用户的身份登录到系统中以便防止某些恶意代码/程序的恶意行为。
标准用户和管理员的登录会话拥有同样的保护计算机安全的能力。
当一个标准用户登录到计算机时,Vista将创建一个新的登录会话,并通过一个操作系统创建的、与刚刚创建的这个登录会话相关联的shell程序(例如WindowsExplorer)作为访问令牌颁发给用户。
而当一个管理员登录到计算机时,WindowsVista的处理方式却与先前版本的Windows有所不同。
虽然系统创建了一个新的登录会话,但却为该登录会话创建了两个不同的访问令牌,而不是先前版本中的一个。
第一个访问令牌提供了管理员所有的许可和权限,而第二个就是所谓的“受限访问令牌”,有时候也叫做“过滤访问令牌(filteredtoken)”,该令牌提供了少得多的许可和权限。
实际上,受限访问令牌所提供的访问权限和标准用户的令牌没什么区别。
然后系统将使用该受限访问令牌创建shell应用程序。
这也就意味着即使用户是以管理员身份登录的,其默认的运行程序许可和权限仍为标准用户。
若是该管理员需要执行某些需要额外许可和权限的、并不在受限访问令牌提供权限之内的操作,那么他/她可以选择使用非限制访问令牌所提供的安全上下文来运行该应用程序。
在由受限访问令牌“提升”到非限制访问令牌的过程中,WindowsVista将通过给管理员提示的方式确认该操作,以其确保计算机系统的安全。
恶意代码不可能绕过该安全提示并在用户不知不觉中得到对计算机的完整控制。
正如我前面提到的那样,受限访问令牌并不是WindowsVista中的新特性,但在WindowsVista中,该特性终于被无缝地集成到用户的点滴操作中,并能够实实在在地保护用户在工作(或游戏)时的安全。
受限访问令牌
虽然在通常情况下,我们不用自行创建受限访问令牌,但了解其创建的过程却非常有用,因为它可以帮助我们更好地理解受限访问令牌能够为我们做什么,进而更深入地了解我们的程序将运行于的环境。
作为开发者,我们可能需要创建一个比UAC提供的更为严格的约束环境,这时了解如何创建受限访问令牌就显得至关重要了。
这个名副其实的CreateRestrictedToken函数用来根据现有的访问令牌的约束创建一个新的访问令牌。
该令牌可以用如下的方式约束访问权限:
1.通过指定禁用安全标示符(deny-onlysecurityidentifier,deny-onlySID)限制访问需要被保护的资源。
2.通过指定受限SID实现额外的访问检查。
3.通过删除权限。
UAC所使用的受限访问令牌在创建时指定了禁用SID并删除了某些权限,而并没有使用受限SID。
让我们通过一个简单示例说明这一点。
第一步就是得到当前正在使用的访问令牌,以便稍后进行复制并基于它删除某些权限:
CHandleprocessToken;
VERIFY(:
:
OpenProcessToken(:
:
GetCurrentProcess(),
TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY|TOKEN_QUERY,
&processToken.m_h));
接下来需要搞定需要禁用的SID数组,以确保这些SID不能被用来访问资源。
下面的代码使用了我编写的WellKnownSid类创建系统内建的管理员组的SID。
WellKnownSid类可以在本文的下载代码中找到。
WellKnownSidadministratorsSid=WellKnownSid:
:
Administrators();
SID_AND_ATTRIBUTESsidsToDisable[]=
{
&administratorsSid,0
//addadditionalSIDstodisablehere
};
Nextweneedanarrayofprivilegestodelete.Wefirstneedtolookuptheprivilege’sLUIDvalue:
LUIDshutdownPrivilege={0};
VERIFY(:
:
LookupPrivilegeValue(0,//localsystem
SE_SHUTDOWN_NAME,
&shutdownPrivilege));
LUID_AND_ATTRIBUTESprivilegesToDelete[]=
{
shutdownPrivilege,0
//addadditionalprivilegestodeletehere
};
然后即可调用CreateRestrictedToken函数创建该受限访问令牌:
CHandlerestrictedToken;
VERIFY(:
:
CreateRestrictedToken(processToken,
0,//flags
_countof(sidsToDisable),
sidsToDisable,
_countof(privilegesToDelete),
privilegesToDelete,
0,//numberofSIDstorestrict,
0,//noSIDstorestrict,
&restrictedToken.m_h));
这样,我们指定了SE_GROUP_USE_FOR_DENY_ONLY标记所得到的访问令牌就将包含内建的管理员组的SID,不过这些SID是用来禁用,而不是用来允许访问的。
我们还剥夺了该访问令牌的SeShutdownPrivilege权限,保证该令牌不能重新启动、休眠或关闭计算机。
若你觉得很有意思,那么可以尝试做个小实验。
将上面的代码拷贝到某个控制台应用程序中,然后添加如下的CreateProcessAsUser函数调用,并相应地更新代码中WindowsExplorer可执行程序的路径:
STARTUPINFOstartupInfo={sizeof(STARTUPINFO)};
ProcessInfoprocessInfo;
VERIFY(:
:
CreateProcessAsUser(restrictedToken,
L"C:
\\Windows\\Explorer.exe",
0,//cmdline
0,//processattributes
0,//threadattributes
FALSE,//don'tinherithandles
0,//flags
0,//inheritenvironment
0,//inheritcurrentdirectory
&startupInfo,
&processInfo));
现在杀掉计算机中的所有Explorer.exe进程并运行上述代码。
你会注意到再也无法重新启动、休眠或关闭计算机了。
开始菜单中的这些选项也会被禁用。
最后还要介绍一个函数:
IsTokenRestricted。
这个函数不会告诉你该访问令牌是否是由CreateRestrictedToken创建的,但却会告诉你该访问令牌是否包含受限SID。
因此,除非你要使用受限SID,否则这个函数并没有什么太大用处。
完整性级别(Integritylevels)
UAC提供了个很少有人注意到的特性,那就是强制完整性控制(MandatoryIntegrityControl)。
这是一个新的添加到进程和安全描述符(securitydescriptor)上的授权特性。
我们可以为需要安全保护的资源在其安全描述符中指定一个完整性级别。
系统中的每个进程也有相应的完整性级别标记,然后即可与资源的完整性级别相互验证,并提供额外的安全保护。
这不但非常简单,也是个极为有用的特性,能够帮助你简单有效地将进程的可访问资源分隔开来。
设想作为开发者的你需要开发一个应用程序,该应用程序必须处理从无法信任的源(例如Internet)中获取的数据。
因为数据中可能包含有恶意代码,所以你必须想方设法保护计算机的安全,因此为你的程序添加一个“深度防御(defenseindepth)”层就显得非常有用。
其中一个非常有效的解决方案就是使用前面一节中描述的受限访问令牌。
但是这种解决方案可能会很复杂,因为你需要明确地指出哪些资源的哪些SID可以被允许、哪些SID需要被禁用,考虑到程序本身也需要一定的权限来正常运行,你不得不做出大量的授权工作。
这正是引入完整性级别的意义所在。
完整性级别一般用来阻止写访问,而允许读访问和运行。
而有了读和运行的权限,程序基本上即可完成大部分的工作,而阻止了写权限则可以限制其对系统的危害,例如覆盖系统文件或修改某些路径信息等。
这也正是IE7的实现方式。
IE7的部分功能运行于一个低完整性级别的独立的进程中,只允许进程修改少数几个位置的文件。
用户态进程可以设置为如下四种完整性级别:
1.Low
2.Medium
3.High
4.System
标准用户访问令牌以及受限(未经提升过)的管理员访问令牌拥有中等(Medium)的完整性级别。
不受限制(经过提升)的管理员访问令牌拥有高(High)完整性级别。
运行于LocalSystem之下的帐号拥有系统(System)完整性级别。
IE进程的完整性级别则为低(Low)。
有一种很简单的查勘进程完整性级别的方法:
用最新版本的ProcessExplorer,它提供了一个可选的列,可以显示出每个进程的完整性级别。
按照默认,子进程将继承父进程的完整性级别。
在创建进程时我们可以更改其完整性级别,但一旦创建完毕就不能再更改。
另外,我们也不能将子进程的完整性级别设置得高于父进程。
这可以阻止低完整性级别的程序借机会窃取更高的完整性级别。
让我们首先看一下如何查询并设置某一进程的完整性级别,然后再讨论如何为需要保护的资源设置完整性级别。
进程完整性级别(Processintegritylevels)
我们可以通过检查进程的访问令牌来取得其完整性级别信息。
GetTokenInformation函数可以返回不同种类的信息。
例如,若想通过访问令牌图的当前的用户帐号,我们可以指定TokenUser类,然后,GetTokenInformation函数将基于该访问令牌生成一个TOKEN_USER结构。
类似地,使用TokenIntegrityLevel类器可查询该进程的完整性级别,随后将返回TOKEN_MANDATORY_LABEL结构。
大多数GetTokenInformation返回的结构体的长度都是可变的,因为只有GetTokenInformation函数本身才知道到底需要多少内存空间,所以我们在调用时必须格外小心。
因为大多数底层的安全相关函数均使用LocalAlloc和LocalFree来分配/释放内存,所以我使用了一个名为LocalMemory的类模板和一个GetTokenInformation函数模板来简化所需要的工作,该类可以在本文的下载代码中找到。
这里我们先把注意力放在手头的主题上:
CHandleprocessToken;
VERIFY(:
:
OpenProcessToken(:
:
GetCurrentProcess(),
TOKEN_QUERY,
&processToken.m_h));
LocalMemory
COM_VERIFY(GetTokenInformation(processToken,
TokenIntegrityLevel,
info));
SID*sid=static_cast
DWORDrid=sid->SubAuthority[0];
switch(rid)
{
caseSECURITY_MANDATORY_LOW_RID:
{
//Lowintegrityprocess
break;
}
caseSECURITY_MANDATORY_MEDIUM_RID:
{
//Mediumintegrityprocess
break;
}
caseSECURITY_MANDATORY_HIGH_RID:
{
//Highintegrityprocess
break;
}
caseSECURITY_MANDATORY_SYSTEM_RID:
{
//Systemintegritylevel
break;
}
default:
{
ASSERT(false);
}
}
这里我们使用了OpenProcessToken来取得需要查询的进程的访问令牌。
然后调用了我编写的GetTokenInformation函数模版(当然,提供了适当的类的信息),并用LocalMemory类模板指定了信息的类型。
函数返回的TOKEN_MANDATORY_LABEL结构包含了表示完整性级别的SID。
分析这个SID即可得到我们想要的表示完整性级别的相对标示符(relativeidentifier,RID)。
设置子进程的完整性级别非常直观简单。
首先复制一份父进程的访问令牌,然后使用前面实例程序中用来查询完整性级别的那些信息类和数据结构设置其完整性级别。
这时即可使用SetTokenInformation函数。
最后调用CreateProcessAsUser函数,并使用修改过的访问令牌即可创建出需要的子进程。
请参考下述代码:
CHandleprocessToken;
VERIFY(:
:
OpenProcessToken(:
:
GetCurrentProcess(),
TOKEN_DUPLICATE,
&processToken.m_h));
CHandleduplicateToken;
VERIFY(:
:
DuplicateTokenEx(processToken,
MAXIMUM_ALLOWED,
0,//tokenattributes
SecurityAnonymous,
TokenPrimary,
&duplicateToken.m_h));
WellKnownSidintegrityLevelSid(WellKnownSid:
:
MandatoryLabelAuthority,
SECURITY_MANDATORY_LOW_RID);
TOKEN_MANDATORY_LABELtokenIntegrityLevel={0};
tokenIntegrityLevel.Label.Attributes=SE_GROUP_INTEGRITY;
tokenIntegrityLevel.Label.Sid=&integrityLevelSid;
VERIFY(:
:
SetTokenInformation(duplicateToken,
TokenIntegrityLevel,
&tokenIntegrityLevel,
sizeof(TOKEN_MANDATORY_LABEL)+:
:
GetLengthSid(&integrityLevelSid)));
STARTUPINFOstartupInfo={sizeof(STARTUPINFO)};
ProcessInfoprocessInfo;
VERIFY(:
:
CreateProcessAsUser(duplicateToken,
L"C:
\\Windows\\Notepad.exe",
0,//cmdline
0,//processattributes
0,//threadattributes
FALSE,//don'tinherithandles
0,//flags
0,//inheritenvironment
0,//inheritcurrentdirectory
&startupInfo,
&processInfo));
这个实例程序将打开记事本程序。
你会注意到,虽然该记事本程序可以打开大多数位置中的文本文件,但却无法保存至任何位置,因为它的完整性级别为低。
还要说一句,我们可以使用LookupAccountSid函数得到完整性级别的可显示名称,但该函数的返回值对用户却并不是那么友好,所以你最好另外设置一个字符串表,包含类似“低”、“中等”、“高”以及“系统”等文字。
系统为标准用户创建的访问令牌的完整性级别为中等。
系统为管理员创建的受限访问令牌的完整性级别也是中等,但未受限管理员访问令牌的完整性级别为高。
现在让我们看看如何为指定的资源设置完整性级别。
资源完整性级别(Resourceintegritylevels)
资源的完整性级别存放在资源安全描述符的系统访问控制表(ystemaccesscontrollist,SACL)中的一个特殊的访问控制条目(accesscontrolentry,ACE)中。
更新该值的最简单方法就是使用SetNamedSecurityInfo函数。
WindowsVista还提供了一个新的名为AddMandatoryAce的函数,用来将一类特殊的ACE(强制ACE)添加至ACL中。
记住,安全相关的缩写词总是会让人一头雾水……认真地说,若你熟悉安全描述符相关编程的话,那么这段代码看起来将相当简单。
首先使用InitializeAcl函数准备了一个足够容纳一个单独ACE的ACL。
接下来创建用SID表示的完整性级别,并使用AddMandatoryAce函数将其添加至ACL中。
最后使用SetNamedSecurityInfo函数更新完整性级别。
注意在下面的代码中,我们使用了一个新的LABEL_SECURITY_INFORMATION标记:
LocalMemory
constDWORDbufferSize=64;
COM_VERIFY(acl.Allocate(bufferSize));
VERIFY(:
:
InitializeAcl(acl.m_p,
bufferSize,
ACL_REVISION));
WellKnownSidsid(WellKnownSid:
:
MandatoryLabelAuthority,
SECURITY_MANDATORY_LOW_RID);
COM_VERIFY(Kerr:
:
AddMandatoryAce(acl.m_p,
&sid));
CStringpath=L"C:
\\SampleFolder";
DWORDresult=:
:
SetNamedSecurityInfo(const_cast
SE_FILE_OBJECT,
LABEL_SECURITY_INFORMATION,
0,//owner
0,//group
0,//dacl
acl.m_p);//sacl
ASSERT(ERROR_SUCCESS==result);
得到资源的完整性级别也非常简单,可以看到大多数资源并没有显式地设定其完整性级别。
系统将没有显式声明完整性级别的资源看作带有中等完整性级别。
首先使用同样的安全信息标记LABEL_SECURITY_INFORMATION调用GetNamedSecurityInfo函数。
然后即可简单地通过GetAce函数得到指向存储了完整性级别SID的ACE的指针,随后通过读取其RID值即可判断其完整性级别。
下面是一段示例:
CStringpath=L"C:
\\SampleFolder";
LocalMemory
PACLacl=0;
DWORDresult=:
:
GetNamedSecurityInfo(const_cast
SE_FILE_OBJECT,
LABEL_SECURITY_INFORMATION,
0,
0,
0,
&acl,
&descriptor.m_p);
ASSERT(ERROR_SUCCESS==result);
DWORDintegrityLevel=SECURITY_MANDATORY_MEDIUM_RID;
if(0!
=acl&&0
{
ASSERT(1==acl->AceCount);
SYSTEM_MANDATORY_LABEL_ACE*ace=0;
VERIFY(:
:
GetAce(acl,
0,
reinterpret_cast
ASSERT(0!
=ace);
SID*sid=reinterpret_cast
integrityLevel=sid->SubAuthority[0];
}
ASSERT(SECURITY_MANDATORY_LOW_RID==integrityLevel);
以管理员身份运行(RunasAdministrator)
目前为止,我们已经注
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- UAC 原理