0x00 前言
这一篇,我们继续研究一种新的漏洞类型,内核池溢出。深入探讨池溢出这个主题前, 我们需要先了解下池的基本概念, 有一定的内核池基本知识,才知道如何根据需要操纵它。池溢出相较于之前的漏洞类型,比较难以理解,ok,准备开始。
相关储备文章:
- Kernel Pool Exploitation on Windows 7 [Tarjei Mandt]
- Kernel Pool Exploitation on Windows 7个人整理机翻版==
- Understanding Pool Corruption Part 1 – Buffer Overflows
- Understanding Pool Corruption Part 2 – Special Pool for Buffer Overruns
- Understanding Pool Corruption Part 3 – Special Pool for Double Frees
- Understanding Pool Corruption 个人整理机翻版
实验环境:Win10专业版+VMware Workstation 15 Pro+Win7 x86 sp1
实验工具:VS2015+Windbg+KmdManager+DbgViewer
0x01漏洞原理
池风水
内核池类似于Windows 中的堆, 因为它的作用也是用来动态分配内存。 像堆喷射修改正常应用程序的堆一样,我们需要在内核领域找到一种办法来修改内存池,以便在内存区域精确地调用我们的shellcode。 理解内存分配器的概念以及如何影响池分配和释放机制相当重要。
在我们的HEVD驱动中,有漏洞的用户缓冲区被分配在非分页池,我们也需要找到一种方法来修改非分页池。Windows提供了Event对象,该对象存储与非分页池中,可以使用CreateEvent API来进行创建:
``
1 | HANDLE WINAPI CreateEvent( |
这里我们需要用这个API创建两个足够大的Event对象数组,然后通过使用CloseHandle API 释放某些Event 对象,从而在分配的池块中造成空隙,经合并形成更大的空闲块:
1 | HRESULT CloseHandle( |
在这些空闲块中,我们需要将有漏洞的用户缓冲区插进去,以便每次准确地覆盖正确的内存位置。因为我们会破坏Event对象的相邻头部,以便跳转到包含shellcode的地址。
之后,我们会把指针指向shellcode,这样就可以通过操纵损坏的池头部来调用它。 我们伪造一个OBJECT_TYPE头,覆盖指向OBJECT_TYPE_INITIALIZER中的一个过程的指针。
分析
打开驱动源码,看到漏洞函数TriggerBufferOverflowNonPagedPool:
1 | NTSTATUS TriggerBufferOverflowNonPagedPool(_In_ PVOID UserBuffer,_In_ SIZE_T Size){ |
可以看到,原因和栈溢出如出一辙,直接使用用户传来的缓冲区大小而不是内核缓冲区大小进行拷贝。用户缓冲区数据过大,溢出到内核池毗邻的池块。IDA中我们可以看到:
申请标记为“Hack”的缓冲区,长度0x1f8即504B,后面我们将开始进行利用。
0x02漏洞利用
逆向得到IOCTL为0x22200f。我们写一个小测试代码如下:
1 |
|
内核输出信息:
进入触发漏洞函数,可以看到池标记“Hack”,大小为0x1F8。
尝试给UserBuffer分配0x1f8字节大小,
现在不应该破坏相邻的内存块,因为现在UserBuffer
的值为边界值,来分析一下池:
1 | kd> !pool 0x86D254F0 |
可以看到用KernelBuffer被完美分配,结束地址为下一池块起始地址:
溢出会是致命性的,将直接导致系统蓝屏崩溃,破坏了相邻的池块头部。
我们如何能够通过溢出控制相邻的头部。我们利用的这个漏洞可以以修改池的方式来使得池不再随机化。此前讨论的 CreateEvent
API 可以胜任这个工作,它的大小为0x40个字节,正好可以匹配池的大小0x200个字节。
喷射大量Event
对象,把它们的句柄存储在数组中,具体看一下过程:
1 | for (i = 0; i < 10000; i++) |
主函数代码
1 | __try { |
我们的Event
对象被喷射到非分页池中,现在我们需要在这些内存块创造一些空隙,然后把我们有漏洞的Hack缓冲区重新分配到这些空隙中。在重新分配有漏洞的缓冲区后,我们需要破坏相邻的池头部,以指向我们的shellcode地址。Event
对象的大小为0x40个字节(0x38+0x8),包括池头部。
来分析一下头部:
由于Event
对象被喷射到非分页池中,所以我们可以将这些值加到缓冲区末尾,来实现利用。但是,简单这样做是行不通的,我们来研究下头部的数据结构,再稍作修改:
我们感兴趣的部分是TypeIndex
,它实际上是指针数组中的偏移量大小,它定义了Windows所支持的每个对象的OBJECT_TYPE
,来分析一下:
这看起来可能有点复杂,但我已经标记出了重要的部分:
第一个指针是 00000000
,在Windows 7下非常重要(下面解释);下一个突出显示的指针是 85f05418
, 这是从0xc个数组元素开始的偏移量;分析到这,可以看出这是Event
对象类型;现在最有趣的是偏移量0x28 处的TypeInfo
成员:这个成员的最后部分有一些程序调用,我们可以从提供的程序中挑选以供己用,在这选择0x038处的 CloseProcedure
CloseProcedure 的偏移量为 0x28 + 0x38 = 0x60我们会覆盖0x60处的这个指针,让它指向我们的shellcode地址,然后调用CloseProcedure方法,从而最终执行我们的shellcode。
我们的目标是把TypeIndex
的偏移量从0xc改为0x0,因为第一个指针是空指针,在Windows 7 中有一个漏洞,可以调用 NtAllocateVirtualMemory来映射到Null页面:
1 | NTSTATUS ZwAllocateVirtualMemory( |
然后调用WriteProcessMemory 覆盖0x60处的指针,指向shellcode地址:
1 | BOOL WINAPI WriteProcessMemory( |
整合之后代码如下:
1 | #include <iostream> |
测试:
有漏洞的缓冲区现在位于我们创建的Event
对象之间的空隙中。
TypeIndex
由 0xc 修改为 0x0
shellcode地址布置完成!
现在,只需要调用 Closeprocedure
,在 虚拟内存中 加载shellcode, shellcode应该完美运行。Payload参见这里。
执行得到系统管理员权限:
0x03 漏洞反思
近些年池溢出方面的漏洞好像很少爆出来,不管怎么说,先大概了解一哈吧。
0x04 链接
这篇主要参考fyb波师傅的的文章:https://bbs.pediy.com/thread-223719.htm
rootkit原文链接:https://rootkits.xyz/blog/2017/11/kernel-pool-overflow/