【译】Kernel Pool Exploitation on Windows 7

https://dl.packetstormsecurity.net/papers/general/kernelpool-exploitation.pdf https://media.blackhat.com/bh-dc-11/Mandt/BlackHat_DC_2011_Mandt_kernelpool-wp.pdf?source=post_page https://www.sstic.org/media/SSTIC2020/SSTIC-actes/pool_overflow_exploitation_since_windows_10_19h1/SSTIC2020-Article-pool_overflow_exploitation_since_windows_10_19h1-bayet_fariello.pdf

摘要:在Windows 7中,微软引入了safe unlinking技术来解决影响Windows内核的安全公告日益增多的问题。在从双向链表中删除条目之前,safe unlinking旨在通过验证相邻列表条目的指针来检测内存损坏。因此,攻击者不能轻易地利用通用技术来利用泄漏池溢出或其他泄漏池损坏漏洞。本文中,我们展示了尽管引入了安全措施,Windows 7仍然容易受到通用内核池(kernel pool)攻击的影响。特别是,我们展示了在某些条件下,池分配器可能无法安全地unlink空闲列表条目,从而允许攻击者破坏任意内存。为了防止这些攻击,我们提出了进一步加固和增强内核池安全性的方法。

关键词:内核池,安全解链,利用

1.Introduction

由于现代计算的复杂性,软件漏洞很难完全消除,因此供应商正在尽最大努力隔离和防止安全漏洞的利用。现代操作系统引入了DEP和ASLR等缓解措施,以应对各种常用的利用技术。然而,由于缓解措施未解决安全漏洞的根本原因,总会存在一些边界情况,这些措施可能不足以防范。例如,仅仅使用DEP可以轻松绕过返回导向编程(ROP)[15]。此外,利用功能强大的应用内嵌脚本引擎(application-embedded scripting engines)的新技术可能完全绕过DEP和ASLR [4]。

一种补充性的缓解措施是特权隔离(privilege isolation)。通过使用操作系统内置的安全机制对用户和进程施加限制,攻击者无法轻易访问和操纵受损系统中的系统文件和注册表信息。自Vista引入用户账户控制(UAC)以来,用户不再默认以管理员权限运行常规应用程序。此外,现代浏览器[2]和文档阅读器[13] [12]使用"沙盒化(sandboxed)“的渲染进程(render processes)来减轻解析库和布局引擎中安全漏洞的影响。反过来,这激励攻击者(以及研究人员)将他们的努力集中在特权提升攻击上。通过在最高特权环中执行任意代码,操作系统的安全性将被破坏。

The kernel pool is analogous to the user-mode heap and was for many years susceptible to generic write-4 attacks abusing the unlink operation of doubly-linked lists [8][16]

特权提升漏洞在大多数情况下是由操作系统内核或第三方驱动程序中的错误引起的。许多漏洞源于对动态分配的内核池内存的处理。内核池类似于用户模式堆,多年来容易受到滥用双向链表的解链操作的通用写入-4攻击的影响[8][16]。为了应对日益增多的内核漏洞,微软在Windows 7中引入了安全解链(safe unlinking)[3]。safe unlinking确保在unlink之前,对双向链表的空闲列表中相邻内存块的指针进行验证。

This often starts with an arbitrary memory write or n-byte corruption at a chosen location We also show that safe unlinking, designed to remediate write-4 attacks, may under certain conditions fail to achieve its goals and allow an attacker to corrupt arbitrary memory

攻击者在利用内核池损坏漏洞时的目标是最终在 ring 0 中执行任意代码。这通常以在所选位置进行任意内存写入或n字节损坏开始。在本文中,我们展示了尽管引入了安全措施,Windows 7中的内核池仍然容易受到通用攻击的影响。反过来,这些攻击可能允许攻击者完全破坏操作系统内核。我们还展示了为了纠正写入-4攻击而设计的safe unlinking,在特定条件下可能无法达到其目标,并允许攻击者破坏任意内存。为了阻止所呈现的攻击,我们最终提出了进一步加固和增强内核池安全性的方法。

论文的其余部分按如下方式组织。第2节详细介绍了Windows 7(和Vista)内核池的内部结构和更改。第3节和第4节我们讨论并演示了影响Windows 7的实际内核池攻击。第5节我们讨论了对策并提出了加固内核池的方法。最后,在第6节中,我们对论文进行了总结。

2.Kernel Pool Internals

在本节中,我们详细介绍了内核池管理结构和算法,这些结构和算法涉及内核池内存的分配和释放。了解内核池的行为对于正确评估其安全性和健壮性至关重要。 为了简洁起见,我们假设使用x86架构(32位)。然而,大多数结构适用于AMD64/x64架构(64位)。有关x86和x64架构之间内核池的显着差异将在第2.9节中讨论。

2.1 Non-Uniform Memory Architecture

对于每个新版本的Windows,内存管理器都得到改进,以更好地支持非一致性内存体系结构(NUMA),这是一种用于现代多处理器系统的内存设计体系结构。NUMA将不同的内存模块分配给不同的处理器,使得本地内存可以更快地访问,而远程内存则访问速度较慢。处理器和内存被分组到较小的单元中,称为节点(nodes),由执行内核中的KNODE结构定义。

typedef struct _KNODE
{
	/*0x000*/ union _SLIST_HEADER PagedPoolSListHead;
	/*0x008*/ union _SLIST_HEADER NonPagedPoolSListHead[3];
	/*0x020*/ struct _GROUP_AFFINITY Affinity;
	/*0x02C*/ ULONG32 ProximityId;
	/*0x030*/ UINT16 NodeNumber;
	/*0x032*/ UINT16 PrimaryNodeNumber;
	/*0x034*/ UINT8 MaximumProcessors;
	/*0x035*/ UINT8 Color;//local memory address
	/*0x036*/ struct _flags Flags;
	/*0x037*/ UINT8 NodePad0;
	/*0x038*/ ULONG32 Seed;
	/*0x03C*/ ULONG32 MmShiftedColor;
	/*0x040*/ ULONG32 FreeCount[2];
	/*0x048*/ struct _CACHED_KSTACK_LIST CachedKernelStacks;
	/*0x060*/ LONG32 ParkLock;
	/*0x064*/ ULONG32 NodePad1;
	/*0x068*/ UINT8 _PADDING0_[0x18];
} KNODE, *PKNODE;

在多节点系统(nt!KeNumberNodes > 1)上,内存管理器将始终尝试从理想节点分配内存。因此,KNODE提供了有关本地内存位置的信息,存储在Color字段中。该值是一个数组索引,由分配和释放算法用于将节点与其首选池(preferred pool)相关联。此外,KNODE为每个节点定义了四个单向链表的预留列表(lookaside lists),用于空闲池页面(ree pool pages)(在第2.6节中讨论)。

2.2 System Memory Pools

在系统初始化时,内存管理器根据系统节点的数量创建动态大小的内存池。每个池由一个池描述符定义(在第2.3节中讨论),这是一个管理结构,用于跟踪池的使用情况并定义池的属性,如内存类型。池内存有两种不同的类型:分页和非分页(paged and non-paged)。

分页池内存可以在任何进程上下文中分配和访问,但只能在IRQL < DPC/调度级别下进行。正在使用的分页池数量由nt!ExpNumberOfPagedPools给出。在单处理器系统中,定义了四个(4个)分页池描述符,分别用索引1到4表示在nt!ExpPagedPoolDescriptor数组中。在多处理器系统中,每个节点定义了一个(1个)分页池描述符。在这两种情况下,还为原型池(prototype pools)/整页分配定义了额外的分页池描述符,用索引0在nt!ExpPagedPoolDescriptor中表示。因此,在大多数桌面系统中,定义了五个(5个)分页池描述符。

非分页池内存始终保证驻留在物理内存中。这对于在IRQL >= DPC/调度级别下执行的线程(例如中断处理程序)是必需的,因为无法及时满足页面错误。当前正在使用的非分页池数量由nt!ExpNumberOfNonPagedPools给出。在单处理器系统中,nt!PoolVector数组的第一个索引指向非分页池描述符。在多处理器系统中,每个节点都有自己的非分页池描述符,由nt!ExpNonPagedPoolDescriptor数组索引。

此外,会话池内存(由win32k使用,session pool)用于会话空间的分配,对于每个用户 会话是唯一的。虽然非分页会话内存使用全局非分页池描述符,但分页会话池内存具有在nt!MM_SESSION_SPACE中定义的自己的池描述符。要获取会话池描述符,Windows 7会解析关联的nt!EPROCESS结构(当前执行线程的结构),以查找会话空间结构,然后找到嵌入的分页池描述符。

2.3 Pool Descriptor

就像用户模式堆一样,每个内核池都需要一个管理结构。池描述符负责跟踪正在运行的分配数量、正在使用的页面以及有关池使用情况的其他信息。它还帮助系统跟踪可重用的池块。池描述符由以下结构(nt!POOL_DESCRIPTOR)定义。

typedef struct _POOL_DESCRIPTOR
{
	/*0x000*/ enum _POOL_TYPE PoolType;
	union
	{
		/*0x004*/ struct _KGUARDED_MUTEX PagedLock;
		/*0x004*/ ULONG32 NonPagedLock;
	};
	/*0x040*/ LONG32 RunningAllocs;
	/*0x044*/ LONG32 RunningDeAllocs;
	/*0x048*/ LONG32 TotalBigPages;
	/*0x04C*/ LONG32 ThreadsProcessingDeferrals;
	/*0x050*/ ULONG32 TotalBytes;
	/*0x054*/ UINT8 _PADDING0_[0x2C];
	/*0x080*/ ULONG32 PoolIndex;
	/*0x084*/ UINT8 _PADDING1_[0x3C];
	/*0x0C0*/ LONG32 TotalPages;
	/*0x0C4*/ UINT8 _PADDING2_[0x3C];
	/*0x100*/ VOID **PendingFrees;
	/*0x104*/ LONG32 PendingFreeDepth;
	/*0x108*/ UINT8 _PADDING3_[0x38];
	/*0x140*/ struct _LIST_ENTRY ListHeads[512];
} POOL_DESCRIPTOR, *PPOOL_DESCRIPTOR;

池描述符包含内存管理器使用的几个重要列表。延迟释放列表由PendingFrees指针指向,它是一个单链表,用于存放等待释放的池块。该列表在第2.8节中有详细解释。ListHeads是一个双链表数组,用于存放相同大小的空闲池块。与延迟释放列表不同,ListHeads列表中的块已经被释放,内存管理器随时可以对其进行分配。我们在下一节中讨论ListHeads

2.4 ListHeads Lists (Free Lists)

ListHeads列表,或称为空闲列表,按照每个8字节的粒度(granularity)大小排序,并用于最多4080字节的分配。空闲块通过块大小索引到ListHeads数组中,块大小的计算方式是将请求的字节数向上舍入为8的倍数,然后除以8,即BlockSize = (NumberOfBytes+0xF) >> 3。这种舍入是为了为池头保留空间,池头是位于所有池块之前的结构。在x86 Windows上,池头的定义如下。

typedef struct _POOL_HEADER
{
	union
	{
		struct
		{
			/*0x000*/ UINT16 PreviousSize : 9;
			/*0x000*/ UINT16 PoolIndex : 7;
			/*0x002*/ UINT16 BlockSize : 9;
			/*0x002*/ UINT16 PoolType : 7;
		};
		/*0x000*/ ULONG32 Ulong1;
	};
	union
	{
		/*0x004*/ ULONG32 PoolTag;
		struct
		{
			/*0x004*/ UINT16 AllocatorBackTraceIndex;
			/*0x006*/ UINT16 PoolTagHash;
		};
	};
} POOL_HEADER, *PPOOL_HEADER;

池头包含了分配释放算法正常运行所需的信息。PreviousSize表示前一个池块的块大小。由于内存管理器始终尝试通过合并相邻的空闲块来减少碎片化,因此通常用于定位前一个块的池头。PreviousSize也可能为零,这意味着池块位于池页面的开头。

PoolIndex提供了对应的池描述符数组(例如nt!ExpPagedPoolDescriptor)的索引。它被释放算法使用,以确保池块被释放到正确的池描述符ListHeads中。在第3.4节中,我们展示了攻击者如何破坏这个值,以将池头损坏(如池溢出)扩展为任意内存损坏。

正如其名称所示,PoolType定义了块的池类型。然而,它也指示了一个块是忙碌还是空闲。如果一个块是空闲的,PoolType被设置为零。另一方面,如果一个块是忙碌的,PoolType被设置为其描述符的池类型(在下面显示的POOL_TYPE枚举中的一个值)与正在使用的池位掩码进行按位或运算。这个位掩码在Vista及更高版本上设置为2,而在XP/2003上设置为4。例如,在Vista和Windows 7上,对于一个忙碌的分页池块,PoolType = PagedPool | 2 = 3

typedef enum _POOL_TYPE
{
    NonPagedPool = 0,                          /*0x0*/
    PagedPool = 1,                             /*0x1*/
    NonPagedPoolMustSucceed = 2,               /*0x2*/
    DontUseThisType = 3,                       /*0x3*/
    NonPagedPoolCacheAligned = 4,              /*0x4*/
    PagedPoolCacheAligned = 5,                 /*0x5*/
    NonPagedPoolCacheAlignedMustS = 6,         /*0x6*/
    MaxPoolType = 7,                           /*0x7*/
    NonPagedPoolSession = 32,                  /*0x20*/
    NonPagedPoolMustSucceedSession = 34,       /*0x22*/
    DontUseThisTypeSession = 35,               /*0x23*/
    NonPagedPoolCacheAlignedSession = 36,      /*0x24*/
    PagedPoolCacheAlignedSession = 37,         /*0x25*/
    NonPagedPoolCacheAlignedMustSSession = 38, /*0x26*/
} POOL_TYPE, *PPOOL_TYPE;

如果一个池块是空闲的,并且在ListHeads列表中,它的池头紧跟着一个LIST_ENTRY结构。因此,单个块大小(8字节)的块不由ListHeads维护,因为它们不足以容纳该结构。

typedef struct _LIST_ENTRY
{
    /*0x000*/ struct _LIST_ENTRY *Flink;
    /*0x004*/ struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;

LIST_ENTRY 结构用于将池块连接到双向链表上。历史上,它一直是利用内存损坏漏洞的目标,无论是在用户模式堆[5]还是内核池[8][16]中,主要是由于众所周知的"write-4"利用技术(在unlink过程中中,覆盖LIST_ENTRY结构可能会导致任意值(指针)被写入内存中的任意位置)。微软在Windows XP SP2 [5]发布时解决了用户模式堆中的LIST_ENTRY攻击问题,并在Windows 7 [3]中类似地解决了内核池中的问题。

2.5 Lookaside Lists

内核使用单向链表(LIFO)的lookaside列表来更快地分配和释放小的池块。它们被设计用于高度并发的代码,并且在添加和删除条目时使用原子比较和交换指令。为了更好地利用CPU缓存,lookaside列表在处理器控制块(KPRCB)中针对每个处理器进行定义。KPRCB结构保存了分页(PPPagedLookasideList)和非分页(PPNPagedLookasideList)分配的lookaside列表,以及用于频繁请求的固定大小分配(例如I/O请求包和内存描述符列表)的特殊专用lookaside列表(PPLookasideList)。

typedef struct _KPRCB
{
    ...
    /*0x5A0*/ struct _PP_LOOKASIDE_LIST PPLookasideList[16];
    /*0x620*/ struct _GENERAL_LOOKASIDE_POOL PPNPagedLookasideList[32];
    /*0xF20*/ struct _GENERAL_LOOKASIDE_POOL PPPagedLookasideList[32];
    ...
} KPRCB, *PKPRCB;

对于分页和非分页的lookaside列表,最大块大小为0x20(32字节)。因此,每种类型有32个独特的lookaside列表。每个lookaside列表由下面显示的GENERAL_LOOKASIDE_POOL结构定义。

typedef struct _GENERAL_LOOKASIDE_POOL
{
    union
    {
        /*0x000*/ union _SLIST_HEADER ListHead;
        /*0x000*/ struct _SINGLE_LIST_ENTRY SingleListHead;
    };
    /*0x008*/ UINT16 Depth;
    /*0x00A*/ UINT16 MaximumDepth;
    /*0x00C*/ ULONG32 TotalAllocates;
    union
    {
        /*0x010*/ ULONG32 AllocateMisses;
        /*0x010*/ ULONG32 AllocateHits;
    };
    /*0x014*/ ULONG32 TotalFrees;
    union
    {
        /*0x018*/ ULONG32 FreeMisses;
        /*0x018*/ ULONG32 FreeHits;
    };
    /*0x01C*/ enum _POOL_TYPE Type;
    /*0x020*/ ULONG32 Tag;
    /*0x024*/ ULONG32 Size;
    union
    {
        /*0x028*/ PVOID AllocateEx;
        /*0x028*/ PVOID Allocate;
    };
    union
    {
        /*0x02C*/ PVOID FreeEx;
        /*0x02C*/ PVOID Free;
    };
    /*0x030*/ struct _LIST_ENTRY ListEntry;
    /*0x038*/ ULONG32 LastTotalAllocates;
    union
    {
        /*0x03C*/ ULONG32 LastAllocateMisses;
        /*0x03C*/ ULONG32 LastAllocateHits;
    };
    /*0x040*/ ULONG32 Future[2];
} GENERAL_LOOKASIDE_POOL, *PGENERAL_LOOKASIDE_POOL;

在这个结构中,SingleListHead.Next指向单向链表上第一个空闲的池块。lookaside列表的大小由Depth的值限制,该值由平衡集管理器(balance set manager,平衡集管理器是一个系统线程,执行nt!KeBalanceSetManager函数,定期处理工作项并调整lookaside列表的大小。)根据对lookaside列表的命中和未命中次数进行定期调整。因此,一个经常使用的lookaside列表的Depth值会比一个不经常使用的列表的Depth值大。初始的Depth值为4(nt!ExMinimumLookasideDepth),最大值为MaximumDepth(256)。如果一个lookaside列表已满,池块将被释放到相应的ListHeads列表中。

还为会话池定义了lookaside列表。分页会话池分配使用在会话空间中定义的单独的lookaside列表(nt!ExpSessionPoolLookaside)。每个会话的lookaside列表的最大块大小由nt!ExpSessionPoolSmallLists设置为0x19。会话池的lookaside列表使用与GENERAL_LOOKASIDE_POOL相同的GENERAL_LOOKASIDE结构,但具有额外的填充。对于非分页会话池分配,则使用先前讨论的非分页每个处理器的lookaside列表。

如果设置了热/冷页面分离的池标志(nt!ExpPoolFlags & 0x100,hot/cold page separation pool),则禁用池块的lookaside列表。该标志在系统启动期间设置,以提高速度并减少内存占用。一个定时器(在nt!ExpBootFinishedTimer中设置)在启动后的2分钟内关闭热/冷页面分离。

2.6 Large Pool Allocations

池描述符ListHeads维护小于一页的块。大于4080字节(需要一页或更多)的池分配由nt!ExpAllocateBigPool处理。该函数调用nt!MiAllocatePoolPages,即池页分配器(pool page allocator),将请求的大小舍入到最近的页大小。在大型池分配之后,会立即放置一个块大小为1且前一个大小为0的“碎片”(frag)块,以便池分配器可以利用剩余的页面片段。多余的字节随后放回到相应池描述符的ListHeads列表的末尾。

回顾第2.1节,每个节点(由KNODE定义)与4个单向链表相关联。这些链表由池页分配器用于快速处理小页面计数的请求。对于分页内存,KNODE定义了一个lookaside列表(PagedPoolSListHead)用于单页分配。对于非分页分配,定义了lookaside列表(NonPagedPoolSListHead[3])用于页面计数为1、2和3。池页lookaside列表的大小由系统中存在的物理页面数量决定。

如果无法使用lookaside列表,将使用分配位图(allocation bitmap)来获取所请求的池页。位图(在RTL_BITMAP中定义)是一个位数组,指示哪些内存页面正在使用,并且为每个主要池类型创建一个位图。它会搜索第一个包含所请求数量的未使用页面的索引。对于分页池,位图在MM_PAGED_POOL_INFO结构中定义,由nt!MmPagedPoolInfo指向。对于非分页池,位图由nt!MiNonPagedPoolBitMap指向。对于会话池,位图在MM_SESSION_SPACE结构中定义。

对于大多数大型池分配,nt!ExAllocatePoolWithTag将请求额外的4字节(在x64上为8字节)来存储分配大小,存放在池体的末尾。在释放分配时(在ExFreePoolWithTag中),将检查该值,以捕获可能的池溢出情况。

2.7 Allocation Algorithm

为了分配池内存,内核模块和第三方驱动程序调用由执行内核导出的ExAllocatePoolWithTag(或其任何包装函数)。该函数首先尝试使用lookaside列表,然后使用ListHeads列表,如果无法返回池块,则从池页分配器请求一个页面。以下伪代码大致概述了其实现过程。

PVOID ExAllocatePoolWithTag(POOL_TYPE PoolType, SIZE_T NumberOfBytes, ULONG Tag)
{
    // 如果大小超过4080字节,则调用池页分配器
    if (NumberOfBytes > 0xff0)
    {
        // 调用 nt!ExpAllocateBigPool
    }
    // 尝试使用 lookaside 列表
    if (PoolType & PagedPool)
    {
        if (PoolType & SessionPool && NumberOfBytes <= 0x19)
        {
            // 尝试会话分页 lookaside 列表
            // 成功时返回
        }
        else if (NumberOfBytes <= 0x20)
        {
            // 尝试每个处理器的分页 lookaside 列表
            // 成功时返回
        }
        // 锁定分页池描述符(循环或本地节点)
    }
    else
    { // NonPagedPool
        if (NumberOfBytes <= 0x20)
        {
            // 尝试每个处理器的非分页 lookaside 列表
            // 成功时返回
        }
        // 锁定非分页池描述符(本地节点)
    }
    // 尝试使用 listheads 列表
    for (n = NumberOfBytes - 1; n < 512; n++)
    {
        if (ListHeads[n].Flink == &ListHeads[n])
        {             // 空的
            continue; // 尝试下一个块大小
        }
        // 安全地取消链接 ListHeads[n].Flink
        // 如果大于所需大小,则拆分
        // 返回块
    }
    // 未找到块,调用 nt!MiAllocatePoolPages
    // 拆分页面并返回块
}

如果从ListHeads[n]列表返回的块大于请求的大小,则会拆分该块。为了减少碎片化,分配器返回的超大块的部分取决于其相对于页面的位置。如果块与页面对齐,请求的大小将从块的前部分分配。如果块不与页面对齐,请求的大小将从块的后部分分配。无论哪种方式,拆分块的剩余(未使用)片段将放在适当的ListHeads列表的尾部。

2.8 Free Algorithm

ExFreePoolWithTag函数实现的释放算法会检查要释放的块的池头,并将其释放到相应的列表中。为了减少碎片化,它还尝试合并相邻的空闲块。以下伪代码展示了算法的工作原理。

VOID ExFreePoolWithTag(PVOID Entry, ULONG Tag)
{
    if (PAGE_ALIGNED(Entry))
    {
        // 调用 nt!MiFreePoolPages
        // 成功时返回
    }

    if (Entry->BlockSize != NextEntry->PreviousSize)
        BugCheckEx(BAD_POOL_HEADER);
    if (Entry->PoolType & SessionPagedPool && Entry->BlockSize <= 0x19)
    {
        // 放入会话池的 lookaside 列表
        // 成功时返回
    }
    else if (Entry->BlockSize <= 0x20)
    {
        if (Entry->PoolType & PagedPool)
        {
            // 放入每个处理器的分页 lookaside 列表
            // 成功时返回
        }
        else
        { // NonPagedPool
            // 放入每个处理器的非分页 lookaside 列表
            // 成功时返回
        }
    }
    if (ExpPoolFlags & DELAY_FREE)
    { // 0x200
        if (PendingFreeDepth >= 0x20)
        {
            // 调用 nt!ExDeferredFreePool
        }
        // 将 Entry 添加到 PendingFrees 列表
    }
    else
    {
        if (IS_FREE(NextEntry) && !PAGE_ALIGNED(NextEntry))
        {
            // 安全地取消链接下一个条目
            // 将下一个条目与当前块合并
        }
        if (IS_FREE(PreviousEntry))
        {
            // 安全地取消链接上一个条目
            // 将上一个条目与当前块合并
        }
        if (IS_FULL_PAGE(Entry))
        {
            // 调用 nt!MiFreePoolPages
        }
        else
        {
            // 将 Entry 插入到 ListHeads[BlockSize - 1]
        }
    }
}

DELAY_FREE池标志(nt!ExpPoolFlags & 0x200)启用了一种性能优化,可以一次释放多个池分配,以分摊池获取和释放的开销。这个机制在[11]中简要提到,并且在Windows XP SP2或更高版本中启用,如果可用物理页面的数量(nt!MmNumberOfPhysicalPages)大于或等于0x1fc00(大致翻译为,在IA-32和AMD64架构上,这大约相当于508兆字节的RAM。)。当使用时,每次调用ExFreePoolWithTag时,要释放的块都会附加到每个池描述符特定的PendingFrees列表中。如果该列表包含32个或更多的块(由PendingFreeDepth确定),则会在调用ExDeferredFreePool时对其进行处理。该函数遍历每个条目,并将其释放到相应的ListHeads列表中,如下面的伪代码所示:

VOID ExDeferredFreePool(PPOOL_DESCRIPTOR PoolDesc, BOOLEAN bMultipleThreads)
{
    for each (Entry in PendingFrees)
    {
        if (IS_FREE(NextEntry) && !PAGE_ALIGNED(NextEntry))
        {
            // safe unlink next entry
            // merge next with current chunk
        }
        if (IS_FREE(PreviousEntry))
        {
            // safe unlink previous entry
            // merge previous with current chunk
        }
        if (IS_FULL_PAGE(Entry))
        {
            // add to full page list
        }
        else
        {
            // insert Entry to ListHeads[BlockSize - 1]
        }
    }
    for each (page in full page list)
    {
        // call nt!MiFreePoolPages
    }
}

释放到lookaside和池描述符ListHeads的块总是放在相应列表的前面。这个规则的例外是被拆分块的剩余片段,它们被放在列表的尾部。当内存管理器返回大于请求大小的块(如第2.7节所述)时,块会被拆分,比如在ExpBigPoolAllocation中拆分的整页和在ExAllocatePoolWithTag中拆分的ListHeads条目。为了尽可能频繁地使用CPU缓存,分配总是从最近使用的块中进行,从相应列表的前面开始。

2.9 AMD64/x64 Kernel Pool Changes

尽管x64 Windows支持更大的物理地址空间,但对内核池没有引入任何重大更改。然而,为了适应指针宽度的变化,块大小的粒度增加到16字节,计算公式为BlockSize = (NumberOfBytes+0x1F) >> 4。为了反映这个变化,池头也相应地进行了更新。

typedef struct _POOL_HEADER
{
    union
    {
        struct
        {
            /*0x000*/ ULONG32 PreviousSize : 8;
            /*0x000*/ ULONG32 PoolIndex : 8;
            /*0x000*/ ULONG32 BlockSize : 8;
            /*0x000*/ ULONG32 PoolType : 8;
        };
        /*0x000*/ ULONG32 Ulong1;
    };
    /*0x004*/ ULONG32 PoolTag;
    union
    {
        /*0x008*/ struct _EPROCESS *ProcessBilled;
        struct
        {
            /*0x008*/ UINT16 AllocatorBackTraceIndex;
            /*0x00A*/ UINT16 PoolTagHash;
            /*0x00C*/ UINT8 _PADDING0_[0x4];
        };
    };
} POOL_HEADER, *PPOOL_HEADER;

由于块大小粒度的变化,PreviousSizeBlockSize都被减小为8位。因此,池描述符ListHeads保存了256个双向链表,而不是x86上的512个。这也允许将一个额外的位分配给PoolIndex,因此在x64上可以支持256个节点(池描述符),而在x86上只能支持128个。此外,池头扩展为16字节,并包括用于配额管理的ProcessBilled指针,用于标识为分配而计费的进程。在x86上,这个指针存储在池体的最后四个字节中。我们在第3.5节中讨论了利用配额进程指针的攻击。

3.Kernel Pool Attacks

在本节中,我们讨论了对Windows 7内核池的几种实际攻击。首先,在第3.1节中,我们展示了对LIST_ENTRY结构的攻击,该攻击涉及对ListHeads池块进行(un)safe unlinking操作。在第3.2节和第3.3节中,我们分别展示了对单向链接的lookaside列表和延迟释放列表的攻击。在第3.4节中,我们介绍了对被释放的分配块的池头的攻击,最后,在第3.5节中,我们展示了对配额计费池分配(quota charged pool alloca)的攻击。

为了解决对内核池溢出的通用利用,Windows 7执行安全解链操作,以验证ListHeads列表中池块的LIST_ENTRY指针。然而,在从ListHeads[n](给定块大小)分配池块时,算法验证的是ListHeads[n]LIST_ENTRY结构,而不是正在unlink的实际块的结构。因此,覆盖一个空闲块中的前向链接可能会导致ListHeads[n]的地址被写入到攻击者控制的地址中(见图1)。

image-20230816133051141.png

这种攻击需要目标ListHeads[n]列表上至少有两个空闲块。否则,ListHeads[n].Blink会验证被解链块的前向链接。在示例1中,ListHeads列表上的一个池块的前向链接已被攻击者选择的地址破坏。接着,当这个块在ExAllocatePoolWithTag中被分配时,算法会尝试将ListHeads[n]的地址(esi)写入到攻击者控制的地址(eax)处LIST_ENTRY结构的后向链接。

image-20230816134801644.png

尽管从用户模式上很难确定esi的值,但有时可以推断出它的值。例如,如果只定义了一个非分页池(如2.2节所讨论的),esi将指向ntoskrnl数据段中的一个固定位置(nt!NonPagedPoolDescriptor)。如果池描述符是从内存中分配的,可以根据定义的池内存范围推断出它的位置。因此,攻击者可以通过覆盖重要的全局变量[14]或内核对象指针[6](例如通过部分指针覆盖)来实现任意代码执行。

攻击者还可以利用覆写中的用户模式指针将任意写扩展为完全受控的内核分配。这是因为在解链受损的块后,ListHeads[n].Flink会被更新为指向下一个空闲块(攻击者控制的指针)。由于攻击者提供的地址的后向链接已被更新为指向ListHeads[n],池分配器可以安全地从空闲列表中unlink用户模式指针,因此不会出现问题。

3.2 Lookaside Next Pointer Overwrite

Lookaside列表被设计为快速和轻量级,因此不像双向链表的ListHeads列表那样引入相同的一致性检查。作为单链表,lookaside列表上的每个条目都保存着指向下一个条目的指针。由于没有对这些指针的有效性进行检查,攻击者可以利用池损坏漏洞来迫使池分配器返回一个任意地址以检索下一个空闲的lookaside块。这反过来可能允许攻击者破坏任意的内核内存。

image-20230816135310282.png

正如在第2.5节中讨论的那样,内存管理器在池块和池页面上都使用了lookaside列表。对于lookaside池块,Next指针直接跟随8字节的池头(POOL_HEADER)。因此,覆写Next指针最多需要在x86上进行12字节的溢出。为了将池块释放到lookaside列表中,必须满足以下条件:

  • 对于(分页/非分页)池块,BlockSize <= 0x20
  • 对于分页会话池块,BlockSize <= 0x19
  • 目标BlockSize的lookaside列表未满
  • 未使用热/冷页面分离(ExpPoolFlags & 0x100

为了将lookaside Next指针的损坏扩展为n字节的任意内存覆写,必须进行目标块大小的分配,直到返回被损坏的指针(图2)。此外,必须在一定程度上控制分配块的内容,以影响用于覆写的数据。对于分页池的分配,像NtCreateSymbolicLinkObject这样的本机API提供了一种方便的方式,可以用几乎任意组合的字节来填充任意大小的块。这样的API还可以用于对池内存布局进行碎片整理和操作,以控制可利用的原语(primitives),如未初始化的指针和双重释放。

image-20230816135451293.png

lookaside池块不同,lookaside池页面(图3)在偏移null处存储Next指针,因为它们没有与之关联的池头。如果满足以下条件,分配的池页面将被释放到lookaside列表中:

  • 对于分页池页面,NumberOfPages = 1
  • 对于非分页池页面,NumberOfPages <= 3
  • 目标页面计数的lookaside列表未满

当内存管理器需要请求额外的池内存时(无法从ListHeadslookaside列表中获得),nt!MiAllocatePoolPages会返回池页面。由于这通常由许多并发的系统线程执行,为了将溢出位置放置在lookaside列表上的空闲池页面旁边,需要操作内核池布局,这显然不容易实现。另一方面,当使用lookaside池块时,可以使用不经常请求的块大小值,以更精细地控制内存布局。这可以通过检查lookaside管理结构中的TotalAllocates值来实现。

3.3 PendingFrees Next Pointer Overwrite

回顾第2.8节,等待释放的池条目存储在单向链接的PendingFrees列表中。在遍历这些列表时,不进行任何检查,因此攻击者可以利用池损坏漏洞来破坏PendingFrees列表条目的Next指针。这将使攻击者能够将任意地址释放到所选的池描述符ListHeads列表中,并可能控制后续池分配的内存(图4)。

image-20230816135812648.png

攻击延迟释放列表的一个显著限制是内核池非常频繁地处理该列表(每32次释放一次)。实际上,数百个线程可以被调度到同一个内核池,并且在多核机器上可以并行处理(每个池描述符都实现了一个锁,因此两个线程不会同时对同一个空闲列表进行操作。)。因此,一个被池溢出攻击目标的块很可能已经从延迟释放列表中移除,并放置在ListHeads列表中。出于这个原因,我们几乎无法认为这种攻击是实际可行的。然而,由于某些池描述符的使用频率较低(如会话池描述符),在某些情况下,对延迟释放列表的攻击可能是可行的。

3.4 PoolIndex Overwrite

如果为给定的池类型定义了多个池描述符,池块的PoolIndex表示与关联的池描述符数组的索引。因此,在处理ListHeads条目时,池块总是被释放到其正确的池描述符中。然而,由于验证不足,一个格式错误的PoolIndex可能会触发越界的数组解引用(间接引用,dereference),并随后允许攻击者覆盖任意内核内存。

image-20230816140058293.png

对于分页池,PoolIndex始终表示分页池描述符数组(nt!ExpPagedPoolDescriptor)中的索引。在调试版本中(On checked builds),通过与nt!ExpNumberOfPagedPools进行比较,验证索引值以防止越界数组访问。然而,在免费版本中(on free (retail) builds),索引没有进行验证。对于非分页池(non-paged pools),只有在NUMA感知系统中存在多个节点时,PoolIndex才表示对nt!ExpNonPagedPoolDescriptor的索引。同样,在免费版本中,PoolIndex没有进行验证。

一个格式错误的PoolIndex(只需要一个2字节的池溢出)可能会导致一个已分配的池块被释放到一个空指针池描述符(图5)。通过映射虚拟的空页,攻击者可以完全控制池描述符及其ListHeads条目。反过来,这可能允许攻击者在链接到列表时将池块的地址写入任意地址。这是因为当前位于前面的块的Blink会被更新为释放的块的地址,即ListHeads[n].Flink->Blink = FreedChunk。值得注意的是,由于释放的块没有返回到任何真实的池描述符,所以没有必要清理(删除陈旧的条目等)内核池。

image-20230816140623402.png

如果启用了延迟池释放(如2.8节所述),可以通过创建一个虚假的PendingFrees列表(图6)来实现类似的效果。在这种情况下,列表上的第一个条目将指向一个由攻击者控制的地址。此外,池描述符中的PendingFreeDepth的值将大于或等于0x20,以触发对PendingFrees列表的处理。

示例2演示了如何通过PoolIndex覆写可能导致将用户控制的页面地址(eax)写入任意目标地址(esi)。为了执行任意代码,攻击者可以利用这种方法,将一个不经常使用的内核函数指针用用户模式页面地址覆写,并从相同的进程上下文触发其执行。

image-20230816140803498.png

如果还覆写了块的PoolType(例如将其设置为PagedPool),PoolIndex覆写攻击可以应用于任何池类型。由于这还需要覆写BlockSize,因此攻击者必须要么知道溢出块的大小,要么在其中创建一个嵌入的虚假边界块。这是必需的,因为释放算法会检查FreedBlock->BlockSize = NextBlock->PreviousSize是否成立。此外,块的大小应大于0x20,以避免lookaside列表(它们忽略PoolIndex)。但请注意,嵌入的池块可能会破坏块数据中的重要字段或指针。

3.5 Quota Process Pointer Overwrite

配额进程指针覆写攻击(Quota Process Pointer Overwrite)

由于进程可能需要为分配的池内存填充(charged),池分配必须提供足够的信息,以便池算法将收费配额返回给正确的进程。出于这个原因,池块可以选择性地存储与之关联的进程对象的指针。在x64上,进程对象指针存储在池头的最后八个字节中,如第2.9节所述;而在x86上,该指针附加到池体中。在池损坏漏洞中覆写这个指针(图7)可能允许攻击者释放正在使用的进程对象或在返回收费配额时破坏任意内存。

image-20230816143057410.png

每当释放一个池分配时,释放算法会在实际将内存返回给适当的空闲列表或lookaside之前,检查池类型的配额位(quota,0x8)。如果该位被设置,它将尝试通过调用nt!PspReturnQuota来返回收取的配额,然后对关联的进程对象进行解引用。因此,覆写进程对象指针可能允许攻击者减少任意进程对象的引用(指针)计数。如果满足正确的条件(例如当引用计数降低到零时句柄计数为零),引用计数不一致可能随后导致use-after-frees问题。

image-20230816143126611.png

如果将进程对象指针替换为指向用户模式内存的指针,攻击者可以创建一个伪造的EPROCESS对象来控制指向EPROCESS_QUOTA_BLOCK结构(图8)的指针,该结构存储了配额信息。在释放时,通过减去分配的大小,更新了该结构中表示配额使用的值。因此,攻击者可以在返回收费配额时减少任意地址的值。只要配额位和配额进程对象指针都被设置,攻击者可以对任何池分配进行这两种攻击。

4.Case Study: CVE-2010-1893

在本节中,我们将在Windows TCP/IP内核模块中利用池溢出(CVE-2010-1893)中描述的PoolIndex覆写技术,该漏洞已在MS10-058 [10]中得到修复。所描述的攻击仅操作池管理结构,因此不依赖于涉及的任何池块中保存的数据。

4.1 About the Vulnerability

Windows TCP/IP内核模块(tcpip.sys)实现了多个用于控制套接字模式的函数。大部分情况下,用户可以通过调用WSAIoctl并提供所需操作的I/O控制代码来访问这些函数。在指定SIO_ADDRESS_LIST_SORT ioctl时,tcpip.sys调用IppSortDestinationAddresses()函数对IPv6和IPv4目标地址列表进行排序,以确定用于建立连接的最佳可用地址。然而,这个函数在Windows 7/Windows 2008 R2和Windows Vista/Windows 2008上存在整数溢出漏洞 [17],因为它没有始终使用安全的整数函数。因此,为地址列表指定大量地址可能导致分配的缓冲区过小,从而在IppFlattenAddressList()中导致池溢出。

该漏洞实质上允许攻击者使用任意字节的组合来破坏相邻的池内存,池内存的大小为SOCKADDR_IN6记录(0x1c字节)。内存复制在sin6_family结构体成员不再等于0x17(AF_INET6)时停止。然而,由于此检查是在复制完成后进行的,因此当仅溢出单个地址记录时,攻击者无需设置该字段。换句话说,攻击者可以通过溢出单个地址记录来破坏相邻的池内存,而无需设置sin6_family字段。

4.2 Preparing Pool Memory

内核池利用的一个重要方面是能够始终覆写所需的内存。由于内核池的碎片化状态使得分配的局部性不可预测,攻击者必须首先使用内核对象或其他可控内存分配来对内核池进行碎片整理。在这方面的目标是分配所有的空闲块,以便池分配器返回一个新的页面。将新分配的页面填充相同大小的分配并释放每个第二个分配,允许攻击者为易受攻击的缓冲区创建空洞。这反过来将使攻击者能够溢出用于填充内核池的对象或内存分配。

image-20230816144524537.png

在示例3中,内核池已被填充了IoCompletionReserve对象(使用NtAllocateReserveObject [7]),其中每隔一个分配后来被释放。因此,当在IppSortDestinationAddresses()函数中分配一个与已释放块的大小(三个SOCKADDR_IN6条目)相匹配的地址排序缓冲区时,很有可能它会掉入其中一个创建的空洞(holes)中。

4.3 Using PoolIndex Overwrite

为了利用PoolIndex攻击,攻击者必须溢出以下池块的池头,并将其PoolType设置为PagedPool|InUse(3),将其PoolIndex设置为越界索引(例如,在大多数单处理器系统上为5),如示例4所示。这将导致在释放被破坏的池块时引用空指针池描述符。

image-20230816144835052.png

在代码清单1(下面)的函数中,我们初始化了进行攻击所需的池描述符值。在这个函数中,PoolAddress指向一个用户可控的池块(例如,在用户模式页面上分配的),而WriteAddress则设置了PoolAddress指针被写入的地址。

VOID
InitPoolDescriptor(PPOOL_DESCRIPTOR PoolDescriptor, PPOOL_HEADER PoolAddress, PVOID WriteAddress)
{
    ULONG i;
    RtlZeroMemory(PoolDescriptor, sizeof(POOL_DESCRIPTOR));
    PoolDescriptor->PoolType = PagedPool;
    PoolDescriptor->PagedLock.Count = 1;
    // create pending frees list
    PoolDescriptor->PendingFreeDepth = 0x20;
    PoolDescriptor->PendingFrees = (VOID **)(PoolAddress + 1);
    // create ListHeads entries with target address
    for (i = 0; i < 512; i++)
    {
        PoolDescriptor->ListHeads[i].Flink = (PCHAR)WriteAddress - sizeof(PVOID);
        PoolDescriptor->ListHeads[i].Blink = (PCHAR)WriteAddress - sizeof(PVOID);
    }
}

我们假设使用待释放列表(pending frees list),因为大多数系统具有512MB或更多的RAM。因此,在链接过程中,用户可控池块的地址最终将被写入到WriteAddress指示的地址中。这可以用来覆写内核函数指针,使得利用变得简单。如果不使用待释放列表,已释放的内核池块的地址(内核地址)将被写入到指定的地址中,这种情况下需要使用其他手段,如部分指针覆写来执行任意代码。

在触发溢出之前的最后一个任务是初始化由PoolAddress指向的内存,以便将伪造的池块(在待释放列表中)正确返回到精心构造的ListHeads列表中(触发任意写入)。在代码清单2(下面)的函数中,我们创建了两个相邻的池块的布局,其中PoolIndex再次引用了与相关池描述符数组的越界索引。此外,BlockSize必须足够大,以避免使用lookaside列表。

##define BASE_POOL_TYPE_MASK 1
##define POOL_IN_USE_MASK 2
##define BLOCK_SHIFT 3 // 4 on x64
VOID InitPoolChunks(PVOID PoolAddress, USHORT BlockSize)
{
    POOL_HEADER *pool;
    SLIST_ENTRY *entry;
    // chunk to be freed
    pool = (POOL_HEADER *)PoolAddress;
    pool->PreviousSize = 0;
    pool->PoolIndex = 5; // out - of - bounds pool index
    pool->BlockSize = BlockSize;
    pool->PoolType = POOL_IN_USE_MASK | (PagedPool & BASE_POOL_TYPE_MASK);
    // last chunk on the pending frees list
    entry = (SLIST_ENTRY *)((PCHAR)PoolAddress + sizeof(POOL_HEADER));
    entry->Next = NULL;
    // bordering chunk ( busy to avoid coalescing )
    pool = (POOL_HEADER *)((PCHAR)PoolAddress + (BlockSize << BLOCK_SHIFT));
    pool->PreviousSize = BlockSize;
    pool->PoolIndex = 0;
    pool->BlockSize = BlockSize;
    pool->PoolType = POOL_IN_USE_MASK | (PagedPool & BASE_POOL_TYPE_MASK);
}

5.Kernel Pool Hardening

尽管引入了safe unlinking是朝着正确方向迈出的一步,但就与用户空间堆的健壮性相匹配而言,内核池利用的预防仍有很长的路要走。在本节中,我们提出了解决第3节中讨论的攻击的方法,以及关于如何进一步改进内核池的建议。

safe unlinking被引入到内核池中,以防止池溢出的通用利用。然而,正如第3.1节所示,不充分的验证可能会在从空闲列表(ListHeads)分配条目时允许攻击者破坏任意内存。正如之前指出的那样,这是由于安全解链未在实际解链的块上执行,而是在目标ListHeads数组条目的LIST_ENTRY结构上执行。因此,一个简单的修复方法是正确验证被解链的块的前向和后向链接。

在对已经高度优化的池管理算法引入额外的缓解措施时,一个主要关注的问题是这些改变是否会对性能产生显著影响[3]。最大的关注点不是引入的额外指令数量,而是改变是否需要额外的分页操作,这在性能方面非常昂贵。解决第3.1节中的攻击可能会对性能产生影响,因为解链的块的前向链接的地址不能保证被分页到内存中,因此在安全解链时可能会触发页面错误(page-fault)。

5.2 Lookaside Next Pointer Overwrite

由于lookaside列表本质上是不安全的,要解决它们的缺点而又不对内核池进行重大改变显然是一项具有挑战性的任务。在Vista和Windows 7堆中,为了取而代之,lookaside列表已被移除,采用了低碎片堆(low fragmentation heap)[9]。低碎片堆避免了使用嵌入指针,并大大降低了攻击者准确操纵堆的能力。因此,类似的方法可以在内核中使用。然而,移除高度优化的看aside列表可能会在一定程度上影响性能。

image-20230816145649040.png

或者,可以添加池块完整性检查来帮助防止对lookaside列表指针的利用。由于所有池块都必须为LIST_ENTRY结构保留空间,而lookaside指针只需要一半的大小(SLIST_ENTRY),所以在lookaside列表上的池块可以在Next指针之前存储一个4字节(或x64上的8字节)的cookie(图9)。这个cookie应该对用户模式来说是非平凡的,并且可以是一个随机值(例如由lookaside列表结构或处理器控制块定义)与块的地址进行异或运算得到的。然而,请注意,这并不一定能够防止在攻击者可以写入已分配块的选择偏移量的情况下进行利用(例如数组索引漏洞)。

5.3 PendingFrees Next Pointer Overwrite

由于PendingFrees列表是单链表,它们显然存在与前面提到的lookaside列表相同的问题。因此,为了防止池溢出的利用,PendingFrees列表也可以受益于嵌入式池块cookie。虽然可以使用双链表,但这将需要在ExFreePoolWithTag中进行额外的锁定(在插入条目到列表时),这将带来计算开销,并且会破坏延迟释放列表的目的。

5.4 PoolIndex Overwrite

由于PoolIndex被用作池描述符数组的索引,正确的方式是在释放块之前根据数组的总条目数验证其值。这样可以防止攻击者引用越界的数组索引并控制池描述符。如第4节所示,可以通过在链接之前对相邻的块进行验证来防止PoolIndex的覆写。

请注意,这种技术也是对空指针的滥用的另一个明显案例。因此,不仅可以解决这种特定的攻击,还可以解决许多其他可利用的空指针内核漏洞,将虚拟地址空(0)在非系统进程中禁止映射可能是一个解决方案。目前,空页面主要用于向后兼容,例如Virtual Dos Machine(VDM)用于在WOW应用程序中寻址16位内存。因此,攻击者可以通过注入到WOW进程中来绕过空页面映射限制。

5.5 Quota Process Pointer Overwrite

在第3.5节中,我们展示了攻击者如何利用池破坏漏洞来取消引用任意进程对象指针。这在x64系统上特别容易实现,因为该指针存储在池头中,而不是像x86系统那样存储在池块的末尾。为了防止利用使用该指针的漏洞,可以使用简单的编码(使用攻击者不知道的常量)来混淆其实际值。然而,这种方法的一个明显问题是,池破坏可能会更难调试,因为解码不正确的指针可能会引用与崩溃无关的数据。尽管如此,仍然可以进行某些检查来验证解码后的指针,例如确保它具有正确的对齐方式并且在预期范围内。

6.Conclusion

在本文中,我们展示了尽管进行了安全的解链操作,但Windows 7内核池仍然容易受到通用攻击。然而,大多数已识别的攻击向量可以通过添加简单的检查或采用用户空间堆的防攻击功能来解决。因此,在未来的Windows版本和服务包中,我们很可能会看到内核池的额外加固措施。特别是,内核池将受益于池头部的校验和或Cookie,以防止涉及池头部破坏或恶意池构造的利用。

References

[1] Alexander Anisimov: Defeating Microsoft Windows XP SP2 Heap Protection and DEP Bypass. http://www.ptsecurity.com/download/defeating-xpsp2-heap-protection.pdf [2] Adam Barth, Collin Jackson, Charles Reis: The Security Architecture of the Chromium Browser. http://crypto.stanford.edu/websec/chromium/chromium-security-architecture.pdf

[3] Pete Beck: Safe Unlinking in the Kernel Pool. Microsoft Security Research and Defense. http://blogs.technet.com/srd/archive/2009/05/26/safe-unlinking-in-the-kernel-pool.aspx

[4] Dion Blazakis: Interpreter Exploitation: Pointer Inference and JIT Spraying. Black Hat DC 2010. http://www.semantiscope.com/research/BHDC2010

[5] Matt Conover & Oded Horovitz: Windows Heap Exploitation. CanSecWest 2004.

[6] Matthew Jurczyk: Windows Objects in Kernel Vulnerability Exploitation. Hack-in-the-Box Magazine 002. http://www.hackinthebox.org/misc/HITB-Ezine-Issue-002.pdf

[7] Matthew Jurczyk: Reserve Objects in Windows 7. Hack-in-the-Box Magazine 003. http://www.hackinthebox.org/misc/HITB-Ezine-Issue-003.pdf

[8] Kostya Kortchinsky: Real World Kernel Pool Exploitation. SyScan 2008. http://www.immunitysec.com/downloads/KernelPool.odp

[9] Adrian Marinescu: Windows Vista Heap Management Enhancements. Black Hat USA 2006. http://www.blackhat.com/presentations/bh-usa-06/BH-US-06-Marinescu.pdf

[10] Microsoft Security Bulletin MS10-058: Vulnerabilities in TCP/IP Could Allow Elevation of Privilege. http://www.microsoft.com/technet/security/Bulletin/MS10-058.mspx

[11] mxatone: Analyzing Local Privilege Escalation in win32k. Uninformed Journal, vol. 10, article 2. http://www.uninformed.org/?v=10&a=2

[12] Office Team: Protected View in Office 2010. Microsoft Office 2010 Engineering. http://blogs.technet.com/b/office2010/archive/2009/08/13/protected-view-in-office-2010.aspx

[13] Kyle Randolph: Inside Adobe Reader Protected Mode - Part 1 - Design. Adobe Secure Software Engineering Team (ASSET) Blog. http://blogs.adobe.com/asset/2010/10/inside-adobe-reader-protected-mode-part-1-design.html

[14] Ruben Santamarta: Exploiting Common Flaws in Drivers. http://reversemode.com/index.php?option=com_remository&Itemid=2&func=fileinfo&id=51

[15] Hovav Shacham: The Geometry of Innocent Flesh on the Bone: Return-into-libc without Function Calls (on the x86). In Proceedings of CCS 2007, pages 552561.ACM Press, Oct. 2007.

[16] SoBeIt: How To Exploit Windows Kernel Memory Pool. Xcon 2005. http://packetstormsecurity.nl/Xcon2005/Xcon2005_SoBeIt.pdf

[17] Matthieu Suiche: Microsoft Security Bulletin (August). http://moonsols.com/blog/14-august-security-bulletin

0%