原文链接

池喷射介绍

当你正尝试去攻击一个内核池(Pool)漏洞时,你将不得不处理许多数据块(chunk)和池数据(pool metadata)。数据块头部存在着不少检查,如果你想避免BSOD(蓝屏)的话,你必须去控制所有东西。池喷射(Pool Spraing)在池中是一门管理分配内存的艺术。这意味着你将可以知道什么时候一个数据块会被分配,还有周围有什么数据块。如果你想暴露一些精确的信息或者覆写特定的数据的话,这是必须的。本篇文章的意图不是深度阐述池的内部结构(internal),只是为了理解喷射技巧的你需要了解的基础知识。如果你需要关于池的本质的详尽信息,你应该阅读Tarjei Mandt的文章。基于同样的原因,我们将只讲述x64结构。所有在本文提及的东西基于Windows并且可以应用在Windows7到Windows10的每个版本


池内部结构

对于在Windows的每次分配来说,池是普通的。尽管池被经常使用,但它比简单的堆要复杂得多。池管理着每种类型的数据,从最简单的字符串到最大的结构体。即使池跟堆没有太大的不同,池也有自己的分配器和结构。运行中的Windows系统内核预留两个内存池,一个保留在物理内存(非分页池 | NonPaged Pool),一个可以跟物理内存进行交换(分页池 | Paged Pool)。请注意Windows8引入了NonPagedPoolNx,NonPagedPoolNx基本上是开启了DEP的非分页池(NonPagedPool)。内核中有许多种类型的池,但它们主要结构是相同的。池描述符保留了池的当前状态信息,当前状态信息包含以下5点:

  • (A Deferred Free list)一个延迟的释放列表(默认状态下开启):当列表满的时候就会被释放的数据块列表
  • (A ListHeads)一个列表头:根据大小排序的空闲数据块的后进先出列表
  • (A Lookaside List)一个页表列表:据大小排序的空闲数据块的后进先出列表。跟ListHeads List十分相似,但会有一些限制。
  • 当前内存分配的多方面信息
  • 页表列表是一个小的空闲数据块的后进先出列表,也是根据大小排序。它被当作数据块的ListHeads的替代品,拥有小于或等于0x200(512)字节的大小,来改善结构性能。

简单来说,池只是已分配页的一个列表。一个页大小为0x1000字节,在数据块中页是碎片化的。有许多数据块的大小大于0x1000字节,这些代表了一种特殊情况,但我们不会学习。所以我们将专注于那些小于0xFF1字节的数据块,这是内核池块的结构:

image

  • PreviousSize(之前的块大小):之前数据块的大小。块的大小存储为实际大小右移4位(即实际大小/16)

  • PoolIndex(池索引):一个用于从对应池类型的的池描述符数组中获取池描述符的索引

  • BlockSize(当前的块大小):当前数据块的大小。块的大小存储为实际大小右移4位(即实际大小/16)

  • PoolType(池类型):一个位掩码包含块的一些细节

    • 块类型(非分页、分页)
    • 分配到或非分配的
    • Quota(配额)位:如果块被用在进程配额的管理上。如果引发此标志,对应的进程快对象的指针将会被存储到ProcessBilled
    • 一些其他信息
  • PoolTag:4个特征用于识别块是否正在被调试

  • ProcessBilled:如果Quota位被设置,这就是一个指向进程块对象的指针

内核池分配与释放

池有三种不同方式分配数据块

image

  • 如果要分配的数据块小于0x200字节,分配器将首次尝试使用页表列表。它将寻找与请求的大小完全相同的数据块。如果没有这样的数据块,分配器将会使用下一种方式
  • 它将使用列表头,也会寻找与请求的大小完全相同的数据块。如果没有这样的数据块,分配器将选择一个更大的数据块并将其分成两部分;一部分将会被分配,另外一部分被存储在适合的列表头中
  • 如果任何对应的数据块都没有,它将会分配一个新页,并且它的一个数据块被分配在该页的顶部。接下来的每个数据块都会被分配到该页的底端,示意图如下:

同时,存在几种机制去释放数据块

  • 如果数据块是一个小的数据块(小于等于0x200字节),分配器将首先尝试将这个数据块存储到对应其类型的页表列表中。然而,一个页表列表最多存储0xff(255)个同样大小的数据块,所以它可能满的
  • 如果延迟释放标志出现(默认的),这个数据块将被存储到延迟释放列表,直到这个列表是满的(最大容纳0x20个数据块)。一旦延迟释放列表满了,这个列表将用于同时释放每个数据块,进而改善结构性能
  • 最后,这个数据块实际上释放了;分配器检查周围的数据块是否是释放了的,如果是,就将它们合并起来,然后存储新的数据块到合适的列表头。如果实际上释放了整个页面,那么它将会被简单释放

池喷射基础

现在,让我们直奔主题。喷射的基础就是分配足够的对象以确保你可以控制未来的分配。Windows向我们提供了许多允许对象分配在不同类型的池的工具。例如,我们可以在非分页池(NonPagedPoolNx在Windows8及往后的版本)中分配ReservedObjects或者Semaphores。你可以根据自己的意愿查找那个匹配你想要控制的池的对象。你所选择的对象大小也很重要,它与你要创建的空隙大小直接相关。一旦你选择你的对象,首先通过大量分配这个对象来取消池的随机化,创建池页面的想法如下: