Windows 10 x86/wow64用户态堆
引言
在过去的几周里,我接到了一些亲戚的"紧急"电话,要求我检查他们的电脑,因为"东西坏了"、“看起来不一样了"和"我觉得我的电脑被黑了”。我很快意识到他们的电脑已升级到Windows 10。
我们可以就强制升级策略进行长篇讨论,但无论如何,Windows 10正在获得市场份额。这也意味着它已成为一个值得研究的Windows版本。
在这篇文章中,我收集了关于Windows 10中32位进程的用户态堆管理器行为的一些笔记。我调查的主要重点是记录与Windows 7的相似之处和差异,并希望提出一些如何操作堆以增加其行为可预测性的想法。
测试环境
我的测试环境包含以下组件:
- Windows 10 Enterprise x64,完全打补丁(在VirtualBox内作为虚拟机运行),具有2个CPU和1.8GB RAM
- Visual Studio Express 2015 for desktop
- WinDBG(适用于Windows 10)
我已通过创建系统环境变量设置WinDBG的符号支持:
- 变量:_NT_SYMBOL_PATH
- 值:srvc:\symbolshttp://msdl.microsoft.com/download/symbols
本文中使用的所有测试用例的源代码(Visual Studio C++项目)可以在这里找到:https://github.com/corelan/win10_heap
堆结构
与以前的Windows版本类似,Windows 10堆位于受ASLR影响(“随机”)的地址,并以头开始。由于堆管理单元的基地址位于非静态地址,您将在本文中看到不同的堆基地址。
转储堆的头内容,我们可以观察到以下字段:
|
|
偏移量0x4c(EncodeFlagMask)和0x50(Encoding)用于存储堆中块头编码的信息。幸运的是,WinDBG !heap扩展将为我们执行所有解码。
此外,与Windows 7类似,VirtualMemoryThreshold字段(偏移量0x5c)包含值0xfe00。由于此值表示块数,我们需要将该值乘以8以获得实际字节数(0x7F000字节)。
偏移量0x0d6(FrontEndHeapType)指示正在使用哪个前端分配器。值0x2指LFH。FrontEndHeap"主"头结构的地址可以在偏移量0xd0(FrontEndHeap)引用的地址找到。
后端分配器
在Windows 7上,后端分配器(BEA)是用于管理空闲块的默认/活动机制。根据我的观察,这在Windows 10上仍然如此。
BEA行为测试
为了评估其在Windows 10上的行为,我们将使用一些示例应用程序运行一些基本测试。
BEA_Alloc1测试 此第一个测试应用程序首先分配2个0x300字节的块。这在我的简单测试应用程序中不应该是常见的对象大小,并且应该足够大以避免后端分配器在其空闲列表中已经有该大小的块。
测试显示,当没有确切大小的空闲块时,后端分配器会从较大的空闲块中分割出所需大小的块。
BEA_Alloc2测试 在这个第二个例子中,我们将创建一系列0x300字节的分配。我们将释放最后一个,然后导致2个0x100字节的分配。
测试显示,后端分配器优先重用恰好符合分配请求大小的空闲块,然后才分割较大的空闲块。
BEA_Alloc3测试 在这个例子中,我们释放一个0x58字节的块,目标是使用0x58字节分配来重新占据其位置。
通过精心的堆布局控制,我们能够确保在释放0x58字节块后,新的0x58字节分配确实重新占据了相同的内存位置。
BEA_Alloc4测试 这次我们尝试使用不同大小的分配(0x80字节)来覆盖原始0x58字节块的内容。通过合并相邻的空闲块,我们能够部分实现这一目标,但需要精确的数学计算来确保正确的覆盖。
前端分配器 - LFH
LFH激活条件
LFH_Alloc1测试 我们测试了触发LFH所需的分配次数:
- 对于0x1500字节的块,在第18次分配时激活LFH
- 在分配系列中插入不同大小的分配不会影响LFH触发
- 在分配系列中释放不同大小的块不会影响LFH触发
- 在分配系列中释放相同大小的块也不会影响LFH触发
LFH分配行为
LFH_Alloc2测试 我们测试了LFH是否仍然以LIFO方式返回空闲块。测试显示,Windows 10上的LFH行为与Windows 7不同,不再严格遵循LIFO顺序。获取已释放块所需的分配次数变化很大。
LFH_Alloc3测试 我们验证了LFH仍然限制为最大0x4000字节的块,与Windows 7相同。
LFH_TakeBack2测试 我们测试了是否可以使用不同大小的LFH分配来替换"易受攻击"对象的内存空间。通过释放整个子段中的所有块,我们能够使堆管理器重新使用这些页面用于不同大小的子段。
大块分配
VirtualAllocdBlocks
Large_Alloc1测试 我们通过分配大于VirtualMemoryThreshold值的大小来触发VirtualAllocdBlocks分配。测试显示,我们仍然可以触发这种类型的分配,但两个分配之间的间隙似乎比Windows 7更大。
精确堆喷洒
Large_Alloc2测试 我们测试了在Windows 10上进行精确堆喷洒的可能性。关键是要避免LFH和VirtualAllocdBlocks,使用"合适"的大小和"合适"的分配次数来获得对齐的连续分配。
通过使用0x20000-8字节和0x40000-8字节等块大小进行喷洒,我们能够在可预测的地址放置特定内容。
Precise_Spray测试 我们演示了如何在0x0c0c0c0c地址精确放置标记"$$$$"。通过在每次分配中每0x1000字节重复相同的结构,我们能够实现精确的内存布局。
结论
Windows 10堆管理在保持与Windows 7相似的基本架构的同时,引入了一些重要的行为变化:
- LFH在18次相同大小的分配后激活,这一阈值相对稳定
- LFH不再严格遵循LIFO顺序,使得堆布局预测更加复杂
- 后端分配器行为基本保持不变,但需要更精细的控制来实现特定内存布局
- 精确堆喷洒仍然可能,但需要避免LFH和VirtualAllocdBlocks
- VirtualAllocdBlocks分配之间的间隙更大,降低了传统堆喷洒技术的可靠性
这些发现对于在Windows 10环境下开发漏洞利用技术具有重要意义,特别是在Use-After-Free和堆溢出漏洞的利用方面。