首页
社区
课程
招聘
[原创]Windows Kernel Exploit 内核漏洞学习(4)-池溢出
发表于: 2019-7-17 14:11 9118

[原创]Windows Kernel Exploit 内核漏洞学习(4)-池溢出

2019-7-17 14:11
9118

这是 Windows kernel exploit 系列的第四部分,前一篇我们讲了任意内存覆盖漏洞,这一篇我们讲内核池溢出漏洞,这一篇篇幅虽然可能不会很多,但是需要很多的前置知识,也就是说,我们需要对Windows内存分配机制有一个深入的理解,我的建议是先看《0day安全:软件漏洞分析技术第二版》中的第五章堆溢出利用,里面很详细的讲解了堆的一些机制,但是主要讨论的是 Windows 2000~Windows XP SP1 平台的堆管理策略,看完了之后,类比堆溢出利用你可以看 Tarjei Mandt 写的 Kernel Pool Exploitation on Windows 7 ,因为我们的实验平台是 Windows 7 的内核池,所以我们需要对内核池深入的理解,虽然是英文文档,但是不要惧怕,毕竟我花了一周的时间才稍微读懂了其中的一些内容(这也是这一篇更新比较慢的原因),总之这个过程是漫长的,并不是一两天就能搞定的,话不多说,进入正题,看此文章之前你需要有以下准备:

传送门:

[+] Windows Kernel Exploit 内核漏洞学习(0)-环境安装

[+] Windows Kernel Exploit 内核漏洞学习(1)-UAF

[+] Windows Kernel Exploit 内核漏洞学习(2)-内核栈溢出

[+] Windows Kernel Exploit 内核漏洞学习(3)-任意内存覆盖漏洞

我们暂时先不看源码,先用IDA分析HEVD.sys,我们找到TriggerPoolOverflow函数,先静态分析一下函数在干什么,可以看到,函数首先用ExAllocatePoolWithTag函数分配了一块非分页内存池,然后将一些信息打印出来,又验证缓冲区是否驻留在用户模式下,然后用memcpy函数将UserBuffer拷贝到KernelBuffer,这和内核栈溢出有点似曾相识的感觉,同样的拷贝,同样的没有控制Size的大小,只是一个是栈溢出一个是池溢出

漏洞的原理很简单,就是没有控制好传入Size的大小,为了更清楚的了解漏洞原理,我们分析一下源码文件BufferOverflowNonPagedPool.c,定位到关键点的位置,也就是说,安全的操作始终对分配的内存有严格的控制

漏洞的原理我们已经清楚了,但是关键点还是在利用上,内核池这个东西利用起来就不像栈一样那么简单了,我们还是一步一步的构造我们的exploit吧,首先根据上一篇的经验我们知道如何计算控制码从而调用TriggerPoolOverflow函数,首先找到HackSysExtremeVulnerableDriver.h中定义IOCTL的地方,找到我们对应的函数

然后我们用python计算一下控制码

我们验证一下我们的代码,我们先给buf一个比较小的值

运行一下如我们所愿调用了TriggerPoolOverflow函数,另外我们可以发现 Pool Size 有 0x1F8(504) 的大小(如果你细心的话其实在IDA中也能看到,另外你可以尝试着多传入几个字节的大小破坏下一块池头的内容,看看是否会蓝屏)

我们现在需要了解内核池分配的情况,所以我们需要在拷贝函数执行之前下断点观察,我们把 buf 设为 0x1F8 大小

我们可以用!pool address命令查看address周围地址处的池信息

我们查看我们申请到池的末尾,0x41414141之后就是下一个池的池首,我们待会主要的目的就是修改下一个池首的内容,从而运行我们shellcode

从上面的池分布信息可以看到周围的池分布是很杂乱无章的,我们希望是能够控制我们内核池的分布,从源码中我们已经知道,我们的漏洞点是产生在非分页池中的,所以我们需要一个函数像malloc一样申请在我们的内核非分页池中,我们这里使用的是CreateEventA,函数原型如下

该函数会生成一个Event事件对象,它的大小为 0x40 ,因为在刚才的调试中我们知道我们的池大小为 0x1f8 + 8 = 0x200,所以多次申请就刚好可以填满我们的池,如果把池铺满成我们的Event对象,我们再用CloseHandle函数释放一些对象,我们就可以在Event中间留出一些我们可以操控的空间,我们构造如下代码测试

可以发现,我们已经把内核池铺成了我们希望的样子

接下来我们加上CloseHandle函数就可以制造一些空洞了

重新运行结果如下,我们已经制造了许多空洞

首先我们复习一下x86 Kernel Pool的池头结构_POOL_HEADER_POOL_HEADER是用来管理pool thunk的,里面存放一些释放和分配所需要的信息

我们在调试中查看下一个池的一些结构

你可能会疑惑_OBJECT_HEADER_OBJECT_HEADER_QUOTA_INFO是怎么分析出来的,这里你需要了解 Windows 7 的对象结构不然可能听不懂图片下面的那几行字,最好是在NT4源码(private\ntos\inc\ob.h)中搜索查看这些结构,这里我放一张图片吧

这里我简单说一下如何识别这两个结构的,根据下一块池的大小是 0x40 ,在_OBJECT_HEADER_QUOTA_INFO结构中NonPagedPoolCharge的偏移为0x004刚好为池的大小,所以这里确定为_OBJECT_HEADER_QUOTA_INFO结构,又根据InfoMask字段在_OBJECT_HEADER中的偏移,结合我们确定的_OBJECT_HEADER_QUOTA_INFO结构掩码为0x8可以确定这里就是我们的InfoMask,这样推出_OBJECT_HEADER的位置在+0x18处,其实我们需要修改的也就是_OBJECT_HEADER中的TypeIndex字段,这里是0xc,我们需要将它修改为0,我们看一下_OBJECT_HEADER的结构

Windows 7 之后 _OBJECT_HEADER 及其之前的一些结构发生了变化,Windows 7之前0×008处的指向_OBJECT_TYPE的指针已经没有了, 取而代之的是在 0x00c 处的类型索引值。但Windows7中添加了一个函数ObGetObjectType,返回Object_type对象指针,也就是说根据索引值在ObTypeIndexTable数组中找到对应的ObjectType

我们查看一下ObTypeIndexTable数组,根据TypeIndex的大小我们可以确定偏移 0xc 处的 0x865f0598 即是我们 Event 对象的OBJECT_TYPE,我们这里主要关注的是TypeInfo中的CloseProcedure字段

我们的最后目的是把CloseProcedure字段覆盖为指向shellcode的指针,因为在最后会调用这些函数,把这里覆盖自然也就可以执行我们的shellcode,我们希望这里能够将Event这个结构放在我们能够操控的位置,在 Windows 7 中我们知道是可以在用户模式下控制0页内存的,所以我们希望这里能够指到0页内存,所以我们想把TypeIndex从0xc修改为0x0,在 Windows 7 下ObTypeIndexTable的前八个字节始终为0,所以可以在这里进行构造,需要注意的是,这里我们需要申请0页内存,我们传入的第二个参数不能是0,如果是0系统就会随机给我们分配一块内存,我们希望的是分配0页,如果传入1的话由于内存对齐就可以申请到0页内存,然后就可以放入我们shellcode的位置了

最后我们整合一下代码就可以提权了,总结一下步骤

最后提权效果如下,详细代码参考这里

这里放一些调试的小技巧,以判断每一步是否正确,在memcpy处下断点,p单步运行可观察下一个池是否构造完成,dd 0x0观察零页内存查看0x60处的指针是否指向shellcode,然后在该处下断点运行可以观察到是否运行了我们的shellcode,源码中的调试就是用__debugbreak()下断点观察即可,调试很重要,如果你会调试的话写出exp也只是时间问题

参考资料:
https://media.blackhat.com/bh-dc-11/Mandt/BlackHat_DC_2011_Mandt_kernelpool-wp.pdf
https://www.cnblogs.com/kuangke/p/5818839.html
https://www.cnblogs.com/flycat-2016/p/5449738.html
https://rootkits.xyz/blog/2017/11/kernel-pool-overflow/

 
 
 
 
 
 

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2019-7-17 15:36 被Thunder J编辑 ,原因:
收藏
免费 7
支持
分享
最新回复 (1)
雪    币: 11695
活跃值: (7179)
能力值: ( LV13,RANK:550 )
在线值:
发帖
回帖
粉丝
2
2019-7-17 14:47
0
游客
登录 | 注册 方可回帖
返回
//