Windows 10用户态堆管理深度解析

本文深入分析Windows 10 x86/wow64环境下的用户态堆管理机制,涵盖后端分配器行为、LFH激活条件、堆布局操控技术及精确堆喷射方法,通过大量实验揭示Windows 10堆管理与之前版本的异同。

Windows 10 x86/wow64用户态堆

引言

在过去几周中,我接到一些亲戚的"紧急"电话,要求我检查他们的电脑,因为"东西坏了"、“看起来不一样了"和"我觉得电脑被黑了”。我很快意识到他们的电脑已升级到Windows 10。

本文收集了关于Windows 10中32位进程用户态堆管理器行为的笔记。调查主要目的是记录与Windows 7的相似性和差异性,并希望提出一些操作堆以增加行为可预测性的方法。

测试环境

  • Windows 10 Enterprise x64,完全补丁
  • Visual Studio Express 2015 for desktop
  • WinDBG for Windows 10

堆结构

与之前的Windows版本类似,Windows 10堆位于受ASLR影响的地址,并以头结构开始。堆管理单元的基地址位于非静态地址。

堆头分析

1
2
3
4
5
6
7
8
dt _HEAP 00a40000
ntdll!_HEAP
+0x000 Segment : _HEAP_SEGMENT
+0x04c EncodeFlagMask : 0x100000
+0x050 Encoding : _HEAP_ENTRY
+0x05c VirtualMemoryThreshold : 0xfe00
+0x0d0 FrontEndHeap : 0x003f0000 Void
+0x0d6 FrontEndHeapType : 0x2 ''

偏移0x4c和0x50用于存储堆中块头编码信息。VirtualMemoryThreshold字段包含值0xfe00,表示块数,需要乘以8得到实际字节数(0x7F000字节)。

后端分配器

后端分配器是用于管理空闲块的默认/活动机制。它使用段内的可用块,开始时为空。当块被释放时,它会以某种列表形式"记住"这些空闲块。

BEA行为测试

通过一系列测试应用程序评估其在Windows 10上的行为:

BEA_Alloc1: 分配2个0x300字节的块,检查它们是否相邻放置,释放后是否合并,以及新分配时是否分割。

BEA_Alloc2: 创建一系列0x300字节分配,释放最后一个,然后导致2个0x100字节分配,检查这些分配的位置。

BEA_Alloc3: 释放0x58字节块,目标是使用0x58字节分配重新占据其位置。

BEA_Alloc4: 释放0x58字节块,尝试使用0x80字节分配控制原始0x58字节块的前4个双字。

前端分配器 - LFH

LFH激活条件

测试表明,在Windows 10中,仍然需要18次连续分配才能激活LFH:

  • 分配1-17:非LFH管理
  • 分配18+:LFH管理

测试还表明:

  • 在分配系列中插入不同大小的分配不会影响LFH触发
  • 释放不同大小的块不会影响LFH触发
  • 释放相同桶大小的块不会影响LFH触发

LFH行为变化

与Windows 7相比,Windows 10中的LFH行为有所变化:

  • 块不再在子段内连续分配
  • LIFO行为不再一致
  • 取回已释放块所需的分配次数变化较大(0-50次)

LFH大小限制

与Windows 7一样,LFH仍然限制为最大0x4000字节块。

大块分配

VirtualAllocdBlocks

通过分配大于堆头中VirtualMemoryThreshold值的大小,仍然可以触发VirtualAllocdBlocks分配。

精确堆喷射

通过避免LFH和VirtualAllocdBlocks,使用"合适"的大小和分配数量,仍然可以在普通段内获得对齐的连续分配。

关键技术:

  • 使用对齐的块大小(页大小的倍数)
  • 在每个分配中每0x1000字节重复相同结构
  • 处理应用程序中的"噪音"分配

结论

Windows 10堆管理在保持与之前版本基本架构的同时,引入了重要的行为变化,特别是在LFH方面。了解这些变化对于开发可靠的利用技术至关重要。

通过精心设计的堆布局和分配策略,仍然可以在Windows 10上实现可预测的堆操作和精确的堆喷射。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计