0x00 前言
这是Windows 10上的一个本地提权(LPE)漏洞,由匿名研究者提交,漏洞编号为CVE-2019-1184。成功利用该漏洞后,运行在低完整性级别(integrity level,IL)的沙箱化进程可以在中等完整性级别运行任意代码。
0x01 COM对象及启动权限
该漏洞主要与CoreShellCOMServerRegistrar
这个COM对象有关,对应的条目位于HKCRCLSID
注册表中,使用的进程内服务端为%SystemRoot%system32CoreShellExtFramework.dll
。在注册表中,该类还与一个DCOM AppID
有关:
图1
图2
当用户以交互式方式登录时,Windows会启动一个sihost.exe
进程,该进程名全程为“Shell Infrastructure Host”。Windows已经将explorer.exe
中与图形窗口显示的大部分代码迁移到sihost.exe
进程中。在启动时,sihost.exe
会调用CoRegisterClassObject
,将自身注册为多个COM类的本地服务端,其中包括在CoreShellExtFramework.dll
中实现的CoreShellCOMServerRegistrar
类。
图3
这里顺便提一下,如果大家注意观察,会发现上图中CoreShellCOMServerRegistrar
正在执行自身的注册操作。
随后,如果我们尝试将CoreShellComServerRegistrar
激活为本地服务端(也就是进程外服务端),都会绑定到托管在sihost.exe
进程中实例。
由于sihost.exe
会以登录用户身份在中等完整性级别运行,那么如果低完整性级别的进程尝试激活该类的实例,此时会出现什么情况?根据微软官方文档,默认情况下,运行在低完整性级别的进程无法激活运行在较高完整性级别进程所提供的COM对象。这种强制性访问控制(MAC)策略也就是所谓的“No Execute Up”(不能向上执行)。如果COM对象希望覆盖这种默认限制策略,可以在DCOM LaunchPermission
安全描述符中应用完整性级别标签。注册表中与AppID
对应的LaunchPermission
安全描述符如图2所示,但可读性较差。
DCOMCNFG
工具(也就是“组件服务”)可以用来查看并管理AppID
对应的权限及其他标志。由于设计上存在缺陷,我们无法通过GUI接口查看是否存在完整性级别标签,也不能修改这类标签。如果我们在组件服务中打开“Launch and Activation Permission”对话框,可以看到如下信息:
图4
根据DACL,任何INTERACTIVE
用户、SYSTEM
用户以及两个特定功能的SID
具备访问权限。然而我们无法通过这个接口查看任何标签信息,只能通过编程方式来操作。我们可以从注册表的LaunchPermission
键值中读取二进制信息,将其输入ConvertSecurityDescriptorToStringSecurityDescriptor
API,得到如下结果:
图5
上图中的S:(ML;;NX;;;LW)
为SACL,其中包含ML(mandatory label,强制性标签), “No Execute Up”策略(NX
),并且低完整性级别调用方具备访问权限(LW
)。因此,这个安全描述符允许低完整性级别调用方激活CoreShellCOMServerRegistrar
。这个点比较有趣,我们很自然想知道低完整性级别的进程(如沙箱化进程)在调用该对象时会出现什么情况。
0x02 在调试时转储安全描述符
在继续研究之前,我希望分享一个小知识点。在分析这个案例时,我正在调试RpcSs
(Remote Procedure Call)服务,其中正在执行前文提到的安全检查过程。如果大家想自己调试RpcSs
服务,我推荐使用dbgsrv.exe
,通过WinDBG的“File | Connect to Remote Stub”菜单从远程连接。
在调试过程中我找到了一个指针,该指针似乎指向LaunchPermission
安全描述符在内存中的副本。我希望转储该描述符,确保其中包含我需要的信息。为了完成该任务,我在WinDBG中写了一行代码,实时调用ConvertSecurityDescriptorToStringSecurityDescriptor
,然后恢复寄存器状态,以便目标进程继续运行。
图6
在这一行命令中,指向安全描述符的指针位于@r8
,可用的目的缓冲区位于40000000
。因此在运行该命令之前,我们应当使用.dvalloc /b 40000000 1000
分配该缓冲区。需要注意的是,该命令需要在函数开始时运行,否则我们还需要保存和恢复涉及到的其他寄存器。
我建议大家掌握这种技术,可以随时使用。通过这种方式,我们可以在调试会话中临时调用原生函数。
0x03 利用CoreShellCOMServerRegistrar
继续前面的工作,现在我们来研究下低完整性级别进程访问CoreShellCOMServerRegistrar
时会对系统安全性造成哪些影响。在符号都可用的情况下,反汇编CoreShellExtFramework.dll
能给我们提供很多信息。通过IDA我们可以看到该对象公开的方法:
图7
CoreShellCOMServerRegistrar
当然会导出与COM服务端注册有关的函数,然而,除了RegisterCOMServer
和UnregisterCOMServer
之外,我们还可以看到一些有趣的函数。比如说,其中就包含OpenProcess
以及DuplicateHandle
。经过反汇编后,我们确认这些函数的功能非常贴切其函数名:可以为调用方提供Windows的OpenProcess
及DuplicateHandle
API服务。由于运行在低完整性级别的进程可以调用这两个函数,并且服务端运行在中等完整性级别,因此我们可以通过这种方式将权限从低完整性级别提升至中等完整性级别。
在漏洞报告中,研究者提交了通过OpenProcess
实现的PoC。OpenProcess
方法可以用来打开指定PID的Windows进程,将句柄复制到调用方选择的目标进程。调用方应该以自己的进程为目标进程,这样就能使用该句柄。在利用过程中,攻击者可以暴力枚举sihost.exe
的PID,该进程以中等完整性权限级别运行。一旦拿到该进程的句柄,攻击者可以使用该句柄注入shellcode,从而在中等完整性级别实现代码执行。
此外,攻击者也可以使用DuplicateHandle
来开发利用代码,此时攻击者需要暴力枚举一个句柄值。
0x04 补丁分析
微软在2019年8月的补丁中修复了该问题。令人惊讶的是,官方并没有通过加强安全描述符来解决该问题,而是放宽了安全描述符,将特定功能的SID替换为ALL APPLICATION PACKAGES
(同时保留LW
标签)。与此同时,微软在这个COM
类的对象工厂中添加相应代码,手动检查所需的某个功能,如下所示:
图8
顺便提一下,通过代码调试,我成功解析出原始ACL中数字形式的SID。图4中的两个SID分别为shellExperienceComposer
以及coreShell
。在2019年8月份的补丁中,官方又新加了另一个功能:shellExperience
。
仅仅两个月之后,微软又在2019年10月份的补丁中更改了处理方式,撤回了8月份补丁中对CoreShellExtFramework.dll
做的所有代码改动,强化了LaunchPermission
安全描述符,将Low
标签更改为Medium
标签。我不清楚官方为什么不在第一次补丁中采用这种方法,但我猜测这种修复措施可能与涉及到图形界面渲染的某些进程的完整性级别改动有关。