内核池初探:MS10-058漏洞分析与利用
引言
我正在研究基于池(pool)的内存破坏漏洞。因此我想为Tarjei Mandt在其首次演讲"Windows 7内核池利用"[3]中提到的漏洞编写一个PoC利用程序。我认为这是开始学习池溢出漏洞的好练习。
目录
- 引言
- 前言
- 触发漏洞
- 池喷射技术
- 非分页对象
- nt!PoolHitTag
- 利用技术
- 基本结构
- 覆盖PoolIndex
- 非分页池类型
- 伪造池描述符
- 注意事项
- 载荷与清理
- 致谢
- 结论
- 参考文献
前言
如果你想实验这个漏洞,应该阅读[1]并确保拥有易受攻击的系统。我在Windows 7 32位虚拟机(tcpip.sys 6.1.7600.16385)上测试了这个漏洞。微软关于此漏洞的公告是MS10-058,由Matthieu Suiche[2]发现,并被用作Tarjei Mandt论文[3]中的示例。
触发漏洞
tcpip!IppSortDestinationAddresses中的整数溢出导致分配了错误大小的非分页池内存块。漏洞版本与修补版本的差异如下:
|
|
可以通过WSAIoctl调用(SIO_ADDRESS_LIST_SORT)触发此代码:
|
|
池喷射技术
非分页对象
我们可以使用多种对象来操作非分页池,例如信号量对象或保留对象。由于驱动请求的是0x54字节(实际获得0x60字节块),这与I/O完成保留对象(IoCo)的大小完全匹配。
nt!PoolHitTag
要调试特定的ExFreePoolWithTag调用,可以使用池命中标签(pool hit tags)。nt!PoolHitTag会与当前释放块的池标签进行比较,如果匹配则触发调试断点。
利用技术
基本结构
池内存分为多种类型,其中两种是分页池和非分页池。池由_POOL_DESCRIPTOR结构描述,包含PoolType、ListHeads等字段。每个内存块在数据前都有一个_POOL_HEADER头,包含块大小、所属池等信息。
覆盖PoolIndex
这种攻击的基本思路是破坏池头中的PoolIndex字段。该字段用于在释放分页池块时确定它属于哪个池描述符。攻击者可以通过破坏此字段使池管理器认为特定块属于另一个池描述符。
伪造池描述符
我们需要在空地址处伪造一个池描述符。只需分配该页面并放置伪造的延迟空闲列表和ListHeads即可。当释放一个块时,如果延迟空闲列表包含至少0x20个条目,ExFreePoolWithTag将实际释放这些块并将它们放在ListHeads的适当条目上。
注意事项
值得注意的是,这种攻击在现代缓解措施下无法工作,主要原因包括:
- PoolIndex字段的验证
- 防止空页分配
- Windows 8引入了NonPagedPoolNX类型
- SMAP防止访问用户态数据
- SMEP防止执行用户态代码
载荷与清理
经典的"写任意位置"场景目标是HalDispatchTable。我们只需用指向我们载荷(setupPayload())的指针覆盖HalDispatchTable+4。完成后,我们需要将指针恢复为hal!HaliQuerySystemInformation。
致谢
特别感谢我的朋友@0vercl0k的审查和帮助!
结论
希望你喜欢这篇文章。如果你想了解更多关于这个主题的内容,可以查看Tarjei Mandt、Zhenhua Liu和Nikita Tarakanov的最新论文。你可以在我的新github[5]上找到我的代码。
参考文献
[1] itsecdb上的漏洞详情 [2] 微软公告 [3] Windows 7内核池利用 - Tarjei Mandt的论文(必读) [4] Windows 7中的保留对象 - j00ru的优秀文章 [5] 我的MS10-058漏洞利用代码