CVE-2019-12750:SEP本地提权漏洞分析(Part 1)

 

0x00 概述

Symantec Endpoint Protection(以下简称SEP)中存在一个漏洞,恶意应用可以利用该漏洞泄露高权限上下文中的信息以及/或者以较高权限执行代码,最终实现对目标主机的完全控制。

漏洞利用过程如下图所示:

图1. 漏洞利用示意图

受影响的版本如下:

Symantec Endpoint Protection v14.x < v14.2 (RU1)
Symantec Endpoint Protection v12.x < 12.1 (RU6 MP10)
Symantec Endpoint Protection Small Business Edition v12.x < 12.1 (RU6 MP10c)

 

0x01 前言

几个月之前,在研究最新版SEP软件(SEP v14.2 Build 2486)中是否存在本地提权漏洞时,我们找到了已存在几年之久的一个漏洞。

此外,Windows 10从v1809开始在内核池分配中引入了最新的安全更新,迫使我们找到利用该漏洞的另一种方法,并且新方法依然适用于当前可用的最新系统版本(v1909)。

由于这两种利用方法存在较大不同,因此我们决定分两篇来介绍相关内容。

在第一篇文章中,我们将讨论SEP中的bug,介绍如何在早期Windows版本(Windows 7到Windows 10 v1803)中利用该漏洞,利用过程不需要满足额外的内核模式执行控制条件

在第二篇文章中,我们将采用更为复杂的一种方法。在Windows 10 v1809及更高版本系统中,内核模式池分配中引入了新的Low Fragmentation Heap(LFH,低碎片堆)机制,此时无法使用第一种利用方法,因此我们需要进一步分析存在漏洞的产品。通过进一步研究,我们才能实现内核模式下的代码执行,绕过其他缓解机制(如SMEP及KASLR)。

 

0x02 SEP漏洞分析

当系统中创建新进程时,SEP会将名为sysfer.dll的一个DLL模块注入进程,该模块在加载时会发送一系列输入输出控制(IOCTL)请求。

这里我们比较感兴趣的是IOCTL 0x222014,该请求会发送给SysPlant.sys内核模块。

在处理该请求的过程中,产品代码逻辑上存在缺陷,允许攻击者泄露并破坏内核模式数据。

分析在处理IOCTL请求过程中调用的子例程时,我们发现SEP会执行如下操作:

1、调用ExAllocatePool,在分页池(Paged Pool)中分配大小为0x14字节的一个缓冲区;

2、调用IoAllocateMdl,为该缓冲区分配一个内存描述符列表;

3、调用MmProbeAndLockPages来探测并锁定内存中的关联页面;

4、调用MmMapLockedPagesSpecifyCache将这些页面映射到另一个虚拟地址范围(如图2所示)。

图2. 在用户模式下映射Paged Pool Chunk

上面最后一个步骤会将pool chunk映射到用户模式,位于调用进程的上下文中。然而当映射缓冲区时,该缓冲区涉及到的整个内存页面同样会被映射。

在这种情况下,缓冲区分配的是一个非常小的chunk,总共占用0x30个字节(缓冲区大小 + 池头部 + 对齐填充数据)。这意味着通过在用户模式下映射这个pool chunk,我们可以泄露出剩余的内存页面数据。我们能多得到0xFD04048)字节的内核内存,其中可能包含一些高权限下才能查看的信息。

不幸的是,漏洞影响范围并没有那么简单。默认情况下,内存页面会被映射为可写模式,因此用户模式进程可以修改对应的内容。对用户态映射内存页面的任何修改操作也会反映到原始的内核模式内存页面。

为了缓解内存泄露,驱动应当调整内核模式下分配的缓冲区大小,使其等于系统所用内存页面大小(通常为4KB)的整数倍。在映射操作之前,该缓冲区应当执行清零操作。

另外需要注意的是,当被映射到用户地址空间时,内核模式缓冲区不应当被释放。否则,向该缓冲区写入的任何新数据也会在用户态中泄露。

因此被映射的内核模式下的缓冲区应处于只读状态,从Windows 8开始,我们在调用MmMapLockedPagesSpecifyCache函数时可以使用MdlMappingNoWrite标志。

传递给MmMapLockedPagesSpecifyCache的第1个参数为指向MDL结构的一个指针,该结构在前文提到的步骤2中分配。

图3. MDL结构

该结构中我们重点关注3个成员:

  • StartVa:内存页面的起始地址。
  • ByteCount:MDL对应缓冲区的大小。
  • ByteOffset:内存页面中缓冲区的起始偏移量。

开发者本来是想将大小为0x14字节的缓冲区映射到用户模式下调用进程的地址空间中,然而系统会映射整个内存页面,而不单单是所需的pool chunk,如图4及图5所示:

图4. 内核模式缓冲区

图5. 用户模式映射的缓冲区

这个漏洞点不错,但我们还想进一步挖掘利用价值,而不单单是在用户态下获取分页池内存信息。

需要注意的是,只有进程在加载该模块时我们才能利用这一点。因此,我们无法在同一个进程中多次复用相同的IOCTL,在用户态中映射其他内核内存页面。

另一方面,由于内核对象一直都在被分配和释放,因此关联的映射页面的内容也会不断变化。

然而,如果在我们自己的函数中复用相同的IOCTL,那么一旦我们的进程被初始化,我们就能找到已映射chunk在用户模式下的地址。

假如页面分配单元为4KB大小,我们还可以在用户空间中找到该页面的基址(VA & 0xFFFFF000)。

掌握这些信息后,我们可以解析映射的页面,寻找特定的信息及有趣的对象,以便进一步利用该漏洞。

 

0x03 漏洞利用(Win 7 – Win 10 v1803)

在这篇文章中,我们将重点关注Windows 10 v1809之前的系统。澄清相关内容后,也能帮助我们理解第二篇文章内容(其中介绍了最新版Windows 10系统在内核池分配中部署的LFH如何影响本文的利用技术)。这里我们以Windows 10 v1803为研究对象。

由于每个进程我们只能提取1个分页池内存页面,并且不同进程可能提取到的是同一个页面(如果有多个大于0x30字节的空闲chunk),因此我们首先可以启动多个进程进行测试。

每个进程都会使用前面提到的IOCTL来获取用户空间中映射页面的基地址,然后将得到的数据保存到文件中,以便进一步分析。

创建一些进程后,我们可以观察到一些“有趣的”情况。

图6. 提取到的令牌对象

测试我们发现,有时候我们能获取到令牌对象(Token Object),这是内核本地权限提升(LPE)攻击中最常用的目标。这些为分页池对象,现在已经被我们牢牢掌握。

需要注意的是,提取到的内存页面并不一定会包含调用进程所关联的对象,实际上通常不满足该条件。

当发送IOCTL请求时,我们获取到的主要是随机的页面,并且当分页内核池中存在空闲空间时,就会填入0x30字节chunk。

然而幸运的是,我们获取到的不一定是调用进程对应的令牌对象(并且实际上通常不会得到调用进程的令牌对象)。

但这里依然存在随机性,因此我们需要找到正确的方法,以便高效处理这种场景。

令牌对象处理(方法1)

由于每个进程都有自己的令牌对象,因此我们可以创建许多进程,并且将这些进程设置为“等待”状态。我们需要执行该操作,避免关联的令牌对象被释放。

简而言之,我们会在每个子进程中检查映射到当前地址空间中的内核内存页面,查找令牌对象。不论获取到什么数据,进程将无限期处于等待状态。

创建的进程越多,我们能分配到更多的令牌对象,因此最终我们会在映射到用户模式下可写的内核页面中找到所需的对象。

此时我们并不知道每个对象对应的是哪个进程,因此我们必须维护关于进程句柄的一个列表。当子进程创建操作结束后,我们可以检查这些进程对应的令牌,找到已被我们修改权限的进程,然后在其上下文中执行代码。

这个方法的确可行,但可以进一步改进,我们来看一下方法2.

令牌对象处理(方法2)

我们的目标是尽可能多获取令牌对象,因此分配的对象越多,我们就有更大的机率通过0x30字节大小的chunk找到适用的内存页面。

我们可以使用DuplicateTokenEx函数,创建漏洞利用进程Primary令牌的副本。该函数允许我们将克隆出来的令牌作为Primary令牌,以便后续在CreateProcessAsUser函数中使用。

此外,我们还可以使用ImpersonateLoggedOnUser函数,使我们漏洞利用程序的调用线程能直接使用提升的令牌特权。

成功创建数千个Primary令牌副本后,我们就可以使用前面提到的方法1。

沿用之前的方法,我们开始创建一些子进程,每个子进程查看已映射到自己地址空间的内核页面,搜索待修改的令牌对象(如图6所示)。

接下来我们可以遍历令牌副本列表,使用TokenPrivilegesTOKEN_INFORMATION_CLASS枚举类型)来调用GetTokenInformation,搜索其中包含高权限进程特有的特权(比如SeDebugPrivilege)。

最后,我们可以使用修改的令牌,通过CreateProcessAsUser函数来启动具备高权限的新进程,或者通过ImpersonateLoggedOnUser函数来提升调用线程本身的权限。

利用过程总结

1、创建Primary令牌的数千个副本;

2、启动子进程,搜索并修改获取到的令牌对象;

3、解析Primary令牌副本列表,查找被修改的令牌;

4、使用CreateProcessAsUser或者ImpersonateLoggedOnUser函数,在高权限下执行代码;

5、在以SYSTEM权限运行的进程中注入并执行代码。

 

0x04 总结

有时候简单的编程错误会造成非常严重的漏洞,本文就是一个典型的案例。开发者需要在调用进程中映射一小段chunk数据,但却没有详细阅读官方文档,考虑可能带来的安全风险。

在第二篇文章中,我们将重点关注最新版Windows 10 v1909上的利用技术,了解新增的内核池LFH机制如何给我们带来挑战,使我们不得不寻找利用该漏洞的其他方法。

 

0x05 时间线

2019年4月:发现漏洞

2019年4月18日:通知厂商

2019年4月19日:厂商确认漏洞

2019年4月19日:厂商请求延长漏洞处理时间

2019年7月31日:厂商发布安全公告

2019年12月5日:本文发布

(完)