-
-
[翻译]Windows exploit开发系列教程第十七部分:内核利用程序之滥用GDI Bitmap(Win7-10 32/64位)
-
发表于: 2018-3-15 20:22 6382
-
点击查看原文。
欢迎回来!我们将再次通过 @HackSysTeam's 驱动深入到ring0层。本文中我们将重温任意地址任意写漏洞。通过实现一个强大的ring0层读写功能我们可以在32位和64位的Windows 7/8/8.1/10(pre v1607)系统上创造一个可以工作的exploit!如我们即将所见,该技术本质上是一个数据攻击,因此我们将不费力气的绕过SMEP/SMAP/CFG/RFG缓解措施,直达胜利!
本技术有一点“复杂”,需要一些预备知识。这一部分我强烈推荐读者在开始阅读本文之前,先阅读下面的资源。最后,为了尝鲜,我们将在64位的Windows 10系统上开发exp。那就不跟你多BB,让我们开始吧!
我们将直接对第十一部分的任意地址任意写漏洞重新梳理,也就无需再次做完整的
解读。我们只想确保我们的任意写在Win10上依然可以如期工作。
看起来我们得到了想要的结果。
如果你记着第十部分的话就会发现这并不完整。我们写进去的值实际上并不是0x4141414141414141,而是该地址上存储的指针。与此同时,我们的POC仅仅在64位机上成功运行。我们需要对exp的架构独立性进行调整!
我们可以修改buffer结构成下面的内容,以成功在32/64位机实现任意写。
尽管传递了合适的值,它也并不能工作。
简单的工作完成了。我们现在想要的是将一个单一任意写漏洞转换成一个完全的ring0读写。在高级别下我们可以:
使用Gdi32的GetBitmapBits/SetBitmapBits API调用来读写内核地址空间
本技术重要的部分在于,当创建一个bitmap时,我们可以泄露出其在内核中的地址。这一泄露在Windows10的v1607版本之后被打上了补丁,哭哭。
如其所呈现般,当创建一个bitmap时,一个结构被附加到了父进程PEB的GdiSharedHandleTable中。给定进程PEB的基地址,GdiSharedHandleTable就在如下的偏移处(分别于32/64位)。
PEB中的该条目是一个指向GDICELL结构体数组的指针,它定义了多种不同的image类型。该结构体的定义如下:
让我们用下面的POC来看看是否可以在KD中手动找到_GDI_CELL
结构体。
运行POC,立即得到了一个bitmap句柄,可以看到它不是一个标准的句柄值(看起来太大了)。
实际上,bitmap句柄的最后两个字节是该结构在GdiSharedHandleTable数组中的索引(=>handle & 0xffff
)。于是,让我们跳转到KD来看看是否可以找到我们新创建的bitmap的_GDI_CELL
结构。
有了指向GdiSharedHandleTable数组的指针,我们所需要做的就是按结构体尺寸的倍数(64位为0x18)增加结构体的索引。
Process hacker有一个非常有用的特性来列出GDI对象句柄。我们可以用它来证实在KD中找到的值。
很棒!我们需要通过重新来采集两个bitmap的信息(manager+worker)。如我们可以看到的,在bitmap句柄上仅仅是做一些简单的数学运算。唯一的问题在于我们要如何拿到进程PEB的基地址。幸运的是,未文档化的NtQueryInformationProcess函数可以解决这个问题。当使用ProcessBasicInformation类(0x0)来调用该函数时,它会返回一个包含PEB基地址的结构体。在这里,我不会进一步描述更多的细节,毕竟这是个妇孺皆知的技巧。下面的POC会清理掉任何疑点!
注意到我们的脚本是架构独立的!其中的一个疑题解决掉了,但是我们为什么要关心这些bitmap对象呢?
泄露出来的bitmap内核地址在内核空间中指向了下面的GDI baseobject结构。
我们对此并无多大兴趣,如果你想要了解更多关于baseobject的信息,请参考ReactOS wiki here. 在这一头部之后,有一个特定的结构体,它的类型取决于该对象的类型。对于bitmaps来说,这是个 surface object 结构体。
pvScan0成员就是我们想要的!这一成员是一个指针,指向bitmap的首个扫描行。从泄露出来的内核地址我们可以计算出该成员的偏移。
说了这么多,还是那个问题,为什么要关心这货?好吧,有这样两个GDI32 API调用,GetBitmapBits 和 SetBitmapBits ,它们直接操纵该成员。GetBitmaps允许我们在pvScan0地址上读任意字节,SetBitmapBits允许我们在pvScan0地址上写任意字节。闻到pwn的味道了吗?
我们可以简要的更新POC来包含manager和worker bitmap的pvScan0偏移。
从现在开始,在ring0层的工作就很可以很简单的展开了。最基本的,使用我们的任意写来设置manager bitmap的pvScan0地址去指向worker bitmap的pvScan0地址。整合所有的内容,我们写出下面的POC。
运行新的POC,在KD中看一下pvScan0。
如上图所见,我们正确修改了manager指针,本质上也就达成了可重用的内核任意读写。使用这些bitmap来读写数据的进程可以在下面看到。
花些时间来消化一下,我承认这一开始有些令人困惑。为了方便,我创建了下面的助手函数来做透明化的IntPtr大小的读写。
在内核中可以任意读写后,我们仍需要找出如何获取SYSTEM。记住我们在64位Windows 10上,这里有着大量的增强版缓解措施诸如SMEP(阻止我们在用户空间运行shellcode)。既然我们已经可以自由的拷贝数据了,看起来在执行体进程块(EPROCESS)上可以做一个数据攻击。EPROCESS结构体包含了进程token,这个token描述了该进程的安全上下文,同时包含了进程账户相关的身份以及权限。当进程或线程尝试去与一个安全对象交互或尝试去执行一个需要特定权限的功能时,OS通过查询这个token来鉴权。
既然进程的token仅仅是一个EPROCESS结构体中的IntPtr尺寸的值。如果我们可以找到SYSTEM进程,拷贝它的token并覆盖PowerShell进程的token,那我们就提权到了SYSTEM。
第一步就是拿到一个指向SYSTEM EPROCESS结构体的指针。实际上,为了避免蓝屏,我们仅可以安全的利用目标PID 4。有一个非常方便的全局变量PsInitialSystemProcess 指向了system EPROCESS(->PID 4)。我们可以在NT内核中通过NtQueryInformation API拿到这一基地址。我写了个脚本Get-LoadedModules,用于完成这项任务。我们做下面的计算来获取这一全局变量。
该地址理应持有一个指向system EPROCESS结构的指针,让我们手动验证它。
如果我们把这一逻辑增添到exp中,我们就可以利用Bitmap-Read函数来拷贝system token。还有另外一个需要关心的事,EPROCESS结构体是未文档化的,它变化的相当频繁因此我们需要写一个switch语句来控制不同系统(32/64位)上的不同偏移。我推荐你看一看@rwfpl最精彩的 Terminus项目,它不止一次的帮助过我。switch语句应该像下面这样,注意到它包含了到ActiveProcessLinks的偏移,我们即将用到。
有了正确的偏移,就可以添加到exp上了。
注意到token减了1,这是因为token成员是一个_EX_FAST_REF
结构,它的最后一位用于引用计数,尽管我们无需担忧这一行为。一个完整的提权过程要先找到PowerShell的EPROCESS结构体,然后用泄露出来的SYSTEM的token来覆盖它。这就是ActiveProcessLinks成员的作用了。EPROCESS结构组成了一个链表,它通过ActiveProcessLinks成员来链入,该成员内部包含了一个_LIST_ENTRY([IntPtr]Flink, [IntPtr]Blink)
成员,存储了前后两个EPROCESS结构体的地址。我们所需要做的就是遍历该链表,直到我们找到PowerShell进程对应的EPROCESS结构,此后进行token覆盖。下面的代码展示了这一技巧。
下面的exploit可以在32位和64位的Windows 7-10上运行。如果遇到了问题,请留下评论。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
赞赏
- [翻译]Windows 10 Segment Heap内部机理 19880
- [翻译]Windows 8堆内部机理 7159
- [翻译]深入理解LFH 7787
- [翻译]Bitmap轶事:Windows 10纪念版后的GDI对象泄露 9296
- [翻译]理解池污染三部曲 6821