http://www.xiangwangfeng.com/2010/10/20/uac%E7%9A%84%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F/
前言:
当初弄这么个博客纯粹出于蛋疼,想体会体会自己买空间,建站的乐趣。现在差不多体会完了,不过也不能就这么关了吧?于是考虑着要定期整点东西,充实充实这个博客。按照我以往的表现来看,除了QQ空间目前还存活着,绝大多数我写过的博客都半途而废了,一来懒,二来也没那么多东西好写:人总不能每天都在伤春悲秋—-毕竟不是靠卖愁滋味赚钱的人,就算写出来也意义不大,过个个把月哪还记得当年在伤啥悲啥,就算记得,感受也不深,而且有时翻出来回味却发现当年的自己如此感性,闷骚—-燥得很,不如难得糊涂,忘了算了。
于是考虑整点技术性的东西(当然以学习笔记和某些蛋疼的技术点为主),主要是我这人一般懒得记笔记,初中到现在唯一记笔记本的还是高三复习物理那会,主要原因是物理实在差得离谱。以至于记笔记给我的感觉很不好,而且很多东西一旦我理解了基本不会忘—-好记性不如烂笔头,但是好的理解力就不同了,记下的东西毕竟是死的。
言归正传,说这次的主题:UAC。这东西很是烦人,转到我手里的bug和问题,估计有5%与它有关,很是烦人,于是在这一篇日志里面详细记录一些我理解的UAC知识。
UAC的基本概念
什么是UAC
UAC即User Account Control(用户帐号控制),是微软从Windows Vista开始为提高安全性而引入的一项新技术。用户通过这项技术既可以以非管理员身份,也能够以管理员身份执行常见的任务,而不需要切换账户或者注销。在大多数的情况下,用户都是以标准用户的状态来执行日常任务,而只有当需要设置系统特定资源的操作的任务才会需要用户以管理员的身份执行,以此来确保进程对系统的“伤害”达到最低。
为什么使用UAC
简单地说使用UAC的理由很简单:保护系统资源和数据的安全。在XP时代,在系统安装完毕后任何新建的账户都会默认划入系统管理员组,于是用户有了安装,卸载,修改,删除系统任何地方任何数据的权限,而这正是万恶之源。而如果能够控制不同程序的权限,那么大部分的恶意软件和病毒就不能起作用了。
UAC正是基于这种思路进行设计的:严格控制进程所能获得的权限。让一个进程无时无刻都拥有管理员权限是无法容忍的:一个恶意程序如果被自动运行于我们的系统,且肆无忌惮地执行某些会进行系统资源读写操作的代码而不为我们所知晓那是多么恐怖的事。(XP正是这么做的)所以UAC的策略就是给予进程尽可能低的权限,如果程序需要管理员权限则需要知会当前用户。同时通过一系列的措施来保障程序的正确运行:
1.在可行的情况下,进行操作的权限将从系统管理员调整为标准用户。(给予最低权限)
2.利用虚拟化技术在没有获得系统管理员权限的情况下协助程序运行。(例如对注册表访问的重定向,保证旧版本程序的兼容性)
3.对程序进行再处理,这样用户帐户控制功能就可以知道在什么情况下需要系统管理员权限。(可执行文件的UAC头,来控制和判断程序所需要的权限,默认是asInvoker或None)
4.确保在系统管理员权限下运行的程序和在标准用户权限下运行的程序是分离的(如UIPI)
UAC带来的影响
对普通用户而言,UAC的引入可能并没有带来多大的影响,更多的可能只是在启动特定程序的时候有时候会跳出提示通知用户以管理员身份运行,仅此而已。但是这个地方有个比较尴尬的问题:UAC(或者其他类似的安全措施)是基于如下假设的:
1.个人用户对于系统安全有一定的认识,能够分辨哪些程序是好的,哪些是坏的。但是实际上对于大多数网民来说,这个假设未必成立,即使恶意软件跳出提示要以管理员身份运行,他们往往也是点确定让它运行。那么UAC的意义又在哪呢?
2.企业用户可以对系统安全一无所知,但是考虑到企业内部会有IT部门帮忙进行安全属性配置和软硬件的安装,UAC对他们来说是很有效的:只需要分配给他们标准用户帐号进行日常任务处理既可。但现实情况却是: 如果企业用户安装软件或其他需要管理员权限的事务都需要知会IT,那整个沟通成本太高,不现实。更何况很多软件产品都有不兼容UAC的问题。(比如部分公司开发的程序为了“绕过”UAC,直接把程序设为必须以管理员帐户运行)
对于开发人员来说可能影响会更大。如何让自己新老程序兼容和适应UAC的规则是一个不大不小的课题。
开发所需要了解的UAC
UAC的基本实现原理
在Windows中有两项比较重要的概念:ACL和Access Token。ACL即Access Control List(直译成:访问控制列表),对于Wdinows中的所有资源来说都会有自己的ACL,这个列表决定了这个资源可以被具有哪些权限的用户/进程所访问。而Access Token即用户的访问令牌,这决定了用户对资源的访问属性。在Vista之前的系统中,如果用户使用了标准用户(如XP中所谓的受限用户),用户就会得到一个和之相对应的Access Token,只能访问和修改有限的用户资源。但只要用户用了管理员组的帐号进行登入,用户就能够获取一个所谓的"Full Access Token",即可以获取到对任意资源的访问权。这显然是多余而且不安全的,于是从Vista起的UAC就做了如下的调整:
1.如果用户是标准用户,那么还是和以前一样分配给用户一个标准的访问令牌。
2.而如果用户是以管理员用户登入,则有所不同:系统不再是和以前一样分配个万能的访问令牌,而是生成两份访问令牌:一个完整的管理员访问令牌和一份经“和谐”的标准用户令牌。在默认的情况下,管理员权限会被移除(或者说被保存到某个地方,等用户主动请求),而用户只拿到了他所需要的标准用户访问权限,并通过它创建了Explorer.exe程序,并以其为父进程创建所有基于标准用户的进程。
具体的流程可参考如图(从MSDN上盗得):
UAC影响到的资源
从上文我们已经可以知道UAC会使得我们的程序运行在一个尽可能低的权限下,而这个权限可能过低,而不在某些敏感资源的ACL允许范围。那么从技术角度来说,搞清楚哪些资源是所谓的敏感资源就很重要—-知己知彼,百战不殆。从Wiki摘抄的需要UAC授权的操作:
- 配置Windows Update
- 增加或删除用户帐户
- 改变用户的帐户类型
- 改变UAC设置
- 安装ActiveX
- 安装或移除程序
- 安装设备驱动程序
- 设置家长控制
- 将文件移动或复制到Program Files或Windows目录
- 查看其他用户文件夹
基本上,只要有涉及到访问系统磁盘的根目录(例如C:),访问Windows目录,Windows系统目录,Program Files目录,访问Windows安全信息以及读写系统登录数据库(Registry)的程序访问动作,都会需要通过UAC的认证。
UAC带来的程序启动选项的变化和注意事项
对于普通用户来讲,UAC最直观的感受就是在很多程序图标多了个小盾,且双击后会出来个用户账户控制的窗口。而对于技术人员来说当然更需要关心真正的内幕:启动的时候进程做了提权的动作,获取了更高权限的用户令牌。(而这又可能导致这个进程的用户相关上下文直接改变,当然这是后话)在Vista以后的程序在默认情况下会有3种启动选项:asInvoker(None),highestAvailable和requireAdministrator。其中highestAvailable最不为大家熟知:这种启动方式请求当前账户可以获取到的最高权限:如果本身是管理员组内成员,则可以得到完整的管理员访问令牌,呼风唤雨。而如果是标准用户则只能得到它这个用户能够得到的最高权限。(具体如何设置程序启动选项在下面的Tips中继续说)上一幅MSDN提供的开启UAC状态下程序启动的流程图:
MS为支持UAC引入的新技术和注意事项
为实现UAC的所有功能,微软可谓煞费苦心,整了很多新的技术和新概念出来。(虽然个人觉得这个技术对于一般用户来说还是很鸡肋)下面就罗列一部分我们平常开发中可能会碰到或者遇到的技术:
1.Installer Dection
这个技术最大的作用是为了兼容以前的以前版本系统中的程序(尤其是安装程序,顾名思义嘛),在UAC下安装程序做的很多事情可能都是十恶不赦,需要最高权限的(如写注册表,写敏感文件目录),而旧版本的程序压根没有做任何特殊处理(或者说是只是填充了一些默认信息),所以一种行之有效的安装程序检测技术是很必要的,否则很多程序安装都不成功,更毋论运行了。
MSDN上总结了一些Installer Dection的原则:
1.文件名包含关键字:"install”,“setup”,"update”等关键字
2.在版本资源的以下字段内包含关键字:Vendor,CompanyName,ProductName,File Description,Original Filename,Internal Name,Export Name。(这两条应该是最SB却又最有效的一个方法,当年闪电邮的UpdateExec没有做任何处理却一直要求能够以管理员权限运行的事让我迷茫了很久)
3.可执行文件的manifest文件中包含关键字
4.在链接到可执行文件的特定String Table中包含关键字 (这个我很迷茫,求解释)
5.链接到可执行文件的资源文件数据包含关键属性
6.可执行文件包含特定的字节序列(这个意思应该是用户在manifest中写入特定属性,然后链接到可执行文件中并填充了某个字段—-现在基本上所有的安装程序/需要提权的程序都是这么做的)
2.Virtualization(虚拟化)
这是一项比较扯同时也是为了保证兼容性设计出来的技术。简单地来说(这个只能简单来说了,具体的原理没有相应的参考资料),就是对老程序所进行的“非法”的 访问系统敏感数据进行重定向,可以分为文件虚拟化和注册表虚拟化。当用户对一个需要管理员权限才能够访问的文件目录或者注册表项进行读写都会被重定向。
如左图,用户对%ProgramFiles%的读写会被定向到%LocalAppData%VirtualStore下,而对于HKLMSoftware的读写会被重定向到HKCUSoftwareClassesVirtualStore下。
特别需要注意的是:因为在XP下养成的习惯,我们对于注册表的读写很多直接就是用KEY_ALL_ACCESS的选项,而到了Vista和Win7后,因为分配给用户的权限低了(即使管理员帐号登入拿到的权限也是经过“和谐”的,上文已经提到),对注册表的访问需要按需设置,如果只是读取一些注册表项值就没必要设置ALL_ACCESS,大多时候READ甚至QUERY的权限就够了。
3.UIPI
这是唯一一项纯粹是出于提高安全性而不是确保兼容性引进的新技术。UIPI即User Interface Privilege Isolation,直译过来就是用户界面特权隔离。在XP时代到处充斥着各种消息粉碎攻击,最典型的就是通过发送WM_CLOSE消息使得接收者退出或者发送WM_SETTEXT给其他窗口输入信息(QQ尾巴算是这种攻击的典型应用)。大多数程序对于这种攻击都是无能为了,很多程序(比如QQ,POPO之类的IM)往往只能自己对信息做特殊的过滤和判断来防范,很是繁琐。而UIPI的基本作用就是使进程可以拦截接受比自身进程MIC等级低的进程发来的消息。在UIPI开启的情况下,只要是低MIC等级的进程向高MIC等级的进程发送消息,所有高于WM_USER的消息都默认被拦截,而低于WM_USER的消息也只有部分能够被选择性地发送成功,一些比较危险的消息也是直接被拦截掉。
所谓MIC即Mandatory Integrity Control,全称为强制完整性控制,是微软对Vista以上的系统做的安全性拓展,主要基于Biba模型。其核心在于达到"no write up,no read down"的效果。(这个no read down貌似在Vista里面反映得不是很明显,或者是没怎么注意到吧)在Vista和Win7里,MIC共分为6级:不可用,低级,中级,高级,系统级别和手保护级别。一般我们的进程包括Explorer.exe是中级,通过管理员身份运行的进程为高级,而值得注意的是IE的MIC级别是低级别—-这个理由就很明显了,不赘述,娃哈哈。
特别声明,某个贱人碰到的问题就和UIPI有关,谁叫IE的MIC等级低呢,可怜的XX宝……
个人总结的一些关于UAC的Tips
1.何时需要提高进程的权限?
答案是:在进程启动的时候。这个问题貌似很SB,却是很多bug会产生的根源。在程序运行的过程是不能再对当前进程进行提权的:如果程序执行过程中和操作系统说:哥要提权。这个时候系统是不会理你的,当然也没有相应的API提供。
2.如何设置一个程序的启动选项
一种比较简单的方法就是通过API启动某个进程的时候带上启动选项,比如ShellExecuteEx有个runas选项
而如果需要让一个程序一直以管理员身份启动的方法就很多了:上文提到的Installer Dection的原则大多可以满足这个需求,让系统认为你的程序是安装程序,给加上小盾盾。不过个人感觉前面的5项都不太靠谱。最标准的做法是在可执行文件中嵌入UAC头。在VS08之后的工程选项Manifest File设置里面有了对启动等级的设置。而05之前则需要自己建立一个manifest文件,并通过Mt.exe向目标进程插入manifest。详见《Create and Embed an Application Manifest (UAC)》
3.UAC对文件系统和窗口消息的影响
因为对HKLM等注册表项和系统文件目录的读写会被重定向,所以尽量不要在非管理权限进程中进行这方面的读写—-合理安排用户数据的存储,而不是像以前一样所有数据都存在程序目录下。(当然也有猥琐的方法可以绕开这个限制,但是不推荐)
启动一个需要提权的进程后需要注意这个进程的环境变量上下文:标准用户下以管理员身份启动某进程后,该进程的环境变量上下文是管理员身份相关的,而非当前标准用户的。(登入用户本身是管理员组成员不会有这个问题)
UIPI的存在使得对于进程间窗口消息传递的控制更严格了,稍不留神一个消息可能就被吃掉了,所以进程间通信最万不得已的情况还是尽量少使用窗口消息—-安全性和可靠性太差。(在管理员权限环境上下文中,拖曳消息会被UIPI给屏蔽掉……)