背景

攻击者们在利用内核漏洞时的主要目的是从较低的权限提升到较的高权限或是系统级别的权限。这种攻击类型的通常被称为LPE(本地权限升级),无论是在NTOSKRNL本身还是在第三方驱动程序中都可以利用到内核代码中的各种类型的漏洞。

虽然微软在防御这些漏洞方面做得很好,但仍有很大的改进空间。我们启动了一个新的开源项目,名为SKREAM
(SentinelOne’s KeRnel Exploits Advanced Mitigations)。这个项目将会有很多自己的特点,用于检测或缓解内核开发生命周期的不同类型/阶段的风险。目前它只有一种缓解措施,后续我们会添加更多的,敬请关注。

在这篇文章中,我们将探讨SKREAM首次引入的缓解措施。这种缓解措施有效的解决了一种特定的攻击技术,也就是pool overflow vulnerabilities 中的溢出漏洞,并使其在Windows 7和8系统上的利用失效。


内核溢出简介

内核溢出是众所周知的一类漏洞,过去几年LPE漏洞也被广泛使用。内核模式下驱动程序将用户输入的数据复制到内存池分配时,无需验证其大小就可以被利用。这允许攻击者输入比内核驱动程序分配的数据还要长的数据,从而覆盖下一个相邻分配的内存池。

图1:漏洞示意

结合内核碰撞技术,可以预测以下池分配的内容,从而允许攻击者覆盖内存池。


TypeIndex覆盖

实际上有多种方法可以利用内存溢出漏洞。本文中主要研究通过覆盖每个OBJECT_HEADER结构中的类型索引成员来实现攻击。

正如numerous sources(源记录)里的那样,Windows对象管理器分配的每个对象都有一个描述它的对象标头,它紧跟在内存中相应的池标头之后。这个对象头包含一个名为“TypeIndex”的成员,其为nt!ObTypeIndexTable索引的数组。

图2:OBJECT_HEADER结构的内存内布局。圈起来的是TypeIndex成员。

nt!ObTypeIndexTable是一个OBJECT_TYPE的结构数组,每个结构都描述了Windows上可用的众多对象类型之一(进程、事件、桌面等)。OBJECT_TYPE结构向Windows提供了对对象执行各种操作所需的信息,比如当对象打开、关闭、删除等时应该调用哪些方法。

图3:每个OBJECT_TYPE都实现了一些方法

结果是,nt!ObTypeIndexTable数组的前两条目实际上是伪条目,它似乎没有指向实际的OBJECT_TYPE结构。第一个条目包含一个空指针,第二个条目包含神奇的常量0xbad0b0b0:

图4:nt!ObTypeIndexTable数组前的两个伪条目

在x64架构上这两个值都是由0扩展到普通用户模式地址的,这无疑为攻击者提供了一种使用内核级权限实现代码执行的可乘之机。即挖掘溢出漏洞,攻击者可以:

  1. 分配0xbad0b0b0页面并为其伪造一个OBJECT_TYPE结构。这个伪对象类型将包含指向攻击者的用以提升权限的代码的函数指针(在Windows 7 x86上,我们也可以为此分配空页面,在新版本Windows上无效)。
  2. 在内存池中碰撞已知类型和大小的对象。以确保攻击者知道溢出时所分配的内容。
  3. 释放部分对象,在内存池中挖掘“漏洞”。对于攻击者来说,最理想的情况是,溢出分配将出现在这些“漏洞”中。
  4. 触发此漏洞,并且溢出到下一个对象,销毁其OBJECT_HEADER并将其TypeIndex属性设置为1。
  5. 对溢出对象触发一些操作(例如关闭句柄)。会导致系统从0xbad0b0b0获取OBJECT_TYPE并调用它的一个方法(示例中是CloseProcedure)。由于这个函数指针是由攻击者提供的,所以它实际上使用内核权限运行攻击代码从而导致权限的提升的。

图5 内存池溢出可视化,来源Nikita Tarakanov

这项技术是由Nikita Tarakanov 首发于这儿


缓解措施

这里我们提出的缓解措施主要是通过预先分配包含0xbad0b0b0的内存区域,以便在任何攻击代码想利用它之前在每个进程的基础上阻止这种攻击。为了让措施尽可能的有效,我们不能只分配这个页面,也必须保护它不受恶意代码取消映射、释放或以任何方式修改它的攻击。

早在2011年,Tarjei Mandt就进行了一个类似的项目,他演示了在Windows 7系统上保护空页不受空页反引用攻击的手法。为此他还编写了一个内核驱动程序,该驱动程序使用了一组VAD(Virtual Address Descriptors 虚拟地址描述符)操作技术来手动为空页面构建VAD条目,然后将其作为“合成”叶状条目插入到VAD树中。

我们要做的事情同理,但希望以一种更“有机”的方式来做,即尽可能的将卸载工资交给Windows虚拟内存管理器来做,从而避免问题复杂化。通过分配包含0xbad0b0b0的区域(因为我们知道每个虚拟内存分配最终都转换为VAD条目),每个VAD条目的创建以及插入在VAD树中都可以轻松地转移到系统。

紧接着我们继续检索刚才创建的VAD条目,以便进一步编辑它来满足保护内存的范围。我们借鉴了Blackbone library库中的BBFindVad函数,在不同的Windows版本中都可以轻松实现了这个功能。

不幸的是从VAD树中检索到的VAD条目是MMVAD_SHORT类型的,我们需要的VAD是MMVAD_LONG类型。我们感兴趣的内容似乎在MMVAD_LONG结构中,而不是在MMVAD_SHORT结构中。

图6 VAD描述了在被保护之前包含0cbad0b0b0的内存区域

为了解决这个问题,我们分配了自己的MMVAD_LONG结构并初始化。事实证明,每个MMVAD_LONG结构中都有一个MMVAD_SHORT子结构嵌在其中,因此我们可以复制前面检索到的MMVAD_SHORT来设置这一属性。

下一步就是编辑VAD标志使其安全一些。根据Tarjei的论文所叙述,我们所做的修改最终归结为:

这些变化使VAD变成这样:

图7 VAD描述了被保护后包含0cbad0b0b0的内存区域

最后,我们需要用新的MMVAD_LONG替换VAD树中的MMVAD_SHORT。简而言之,这涉及到三个阶段的操作:

  1. 设置短VAD子节点的父节点指向我们的MMVAD_LONG条目。
  2. 设置短VAD的父节点中适当是我子节点(左或右)以指向我们的MMVAD_LONG条目。
  3. 释放不再被VAD树引用的短的VAD条目。

图8 用MMVAD_LONG替换VAD树中的MMVAD_SHORT

效果展示

KdExploitMe中SKREAM缓解池溢出漏洞利用
https://www.youtube.com/embed/NHG4v_g3Fi8


Windows 8

在Windows 8上,微软对各种VAD结构做了一些细微的修改,我们也需要对代码基进行了一些调整:

  1. 修改中包括不再有MMVAD_LONG结构,只有MMVAD_SHORTMMVAD
  2. 我们在Windows 7上设置的标志要么改变了它们在MMVAD结构中的位置(NoChange, StartVA, EndVA),要么根本不存在(OneSecured)。

    在考虑了这些因素后,针对Windows 8的代码调整如下:


Windows 8.1或者其他版本

本文中提到的技术仅适用于Windows 7和Windows 8系统。从win8.1开始nt!ObTypeIndexTable[1]不再指向0xbad0b0b0,而是nt!MmBadPointer的值,释放引用时会冲突。另外,在Win10中,存储在OBJECT_HEADER结构中的类型索引值是使用安全cookie编码的,所以如果没有搞定这个,这项技术显然也是无效的。

原文:https://www.sentinelone.com/blog/skream-kernel-mode-exploits-mitigations-rest-us/

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖