Windows 10 x86/wow64用户态堆管理技术解析

本文深入分析Windows 10 x86/wow64环境下的用户态堆管理机制,包括后端分配器行为、LFH激活条件、堆布局操控技术及精确堆喷洒方法,通过实际测试验证Win10与Win7的堆管理差异。

Windows 10 x86/wow64用户态堆

引言

在过去几周中,我多次收到亲友的"紧急"求助,称其计算机"出现问题"、“界面异常"或"疑似被黑”。经排查发现,这些计算机均被升级至Windows 10。尽管可讨论强制升级策略,但显然Win10市场份额正在增长,使其成为值得深入研究的技术目标。

本文汇集了关于Windows 10中32位进程用户态堆管理器行为的笔记,重点记录与Windows 7的异同,并探讨如何操纵堆行为以提高可预测性。具体关注以下问题:

  • 后端分配器行为特征
  • 激活LFH(低碎片堆)的条件
  • Win7与Win10的LFH行为差异
  • 创建特定堆布局(同尺寸/不同尺寸对象相邻)的方法
  • 实现可靠精确堆喷洒的可能性

需要说明的是,本文基于观测记录而非ntdll.dll逆向工程,且测试样本有限,结论未必完全准确。希望这些笔记能激励更多人进行测试和代码逆向。

测试环境

  • Windows 10 Enterprise x64(VirtualBox虚拟机,2CPU/1.8GB内存)
  • Visual Studio Express 2015桌面版
  • WinDBG for Windows 10(配置符号路径:_NT_SYMBOL_PATH=srvc:\symbolshttp://msdl.microsoft.com/download/symbols

测试代码详见:https://github.com/corelan/win10_heap

堆结构分析

与早期版本类似,Windows 10堆位于ASLR随机化地址,以头部结构起始。关键字段包括:

  • EncodeFlagMask (0x4c)Encoding (0x50):块头编码信息(每个进程/堆使用独立XOR密钥)
  • VirtualMemoryThreshold (0x5c):值为0xfe00(0x7F000字节),超过此值的分配将触发VirtualAllocdBlocks
  • FrontEndHeapType (0xd6):值0x2表示使用LFH前端分配器

LFH头部结构(_LFH_HEAP)包含桶(Bucket)、子段(SubSegment)等管理单元。与Win7不同,Win10的LFH头部通常位于首段之外。

后端分配器(BEA)行为

BEA_Alloc1测试

分配两个0x300字节块观察相邻性:

1
2
Allocated chunk of 0x300 bytes at 0x01561440
Allocated chunk of 0x300 bytes at 0x01561748

确认从0xba0字节空闲块分割产生,剩余0x590字节空闲块:

1
2
3
01561438 0061 0201 [00] 01561440 00300 - (busy)
01561740 0061 0061 [00] 01561748 00300 - (busy)
01561a48 00b3 0061 [00] 01561a50 00590 - (free)

BEA_Alloc2测试

分配10个0x300字节块(避免LFH激活),释放最后一块后分配两个0x100字节块:

1
2
Allocated chunk of 0x100 bytes at 0x009B2B08  // 精确匹配空闲块
Allocated chunk of 0x100 bytes at 0x009BC338  // 从0x198字节块分割

BEA优先重用精确尺寸空闲块,其次分割大块。释放块未立即重用因其与相邻空闲空间合并。

BEA_Alloc3测试

创建0x100-0x58-0x100相邻布局,释放中间块后重新分配:

1
2
3
016d09b0 0021 0021 [00] 016d09b8 00100 - (busy)
016d0ab8 000c 0021 [00] 016d0ac0 00058 - (busy)  // 填充'A'
016d0b18 0021 000c [00] 016d0b20 00100 - (busy)

释放0x58字节块后,新分配成功重用相同地址并填充’B’。

BEA_Alloc4测试

尝试用0x80字节块覆盖0x58字节块的前4双字(仅控制0x80块最后4双字)。通过释放相邻块触发合并:

1
010e2108 001a 0000 [00] 010e2110 000c8 - (free)  // 合并后块

但数学计算偏差导致覆盖偏移误差8字节,证明原理可行但需精确计算。

前端分配器(LFH)行为

LFH激活条件

  • 0x1500字节块:第18次分配激活LFH(!heap -x显示LFH标记)
  • 插入异尺寸分配:中途分配0x300字节不影响0x2100字节块的LFH激活(仍需18次)
  • 中途释放异尺寸块:不影响0x3000字节块的LFH激活阈值
  • 释放同尺寸块:释放0x800字节块后,仍需18次分配激活LFH

结论:LFH激活阈值保持18次,且不受异尺寸操作影响。

LFH分配特性

  • LIFO行为消失:释放后立即分配不一定返回相同地址
  • 取回概率:统计显示最多需约50次分配才能取回释放块
  • 非相邻分配:同子段内块不再连续排列
  • 最大尺寸限制:仍为0x4000字节(验证0x4008字节分配未触发LFH)

LFH块替换测试

通过释放整个子段(包含漏洞块及其他可控块),可用不同尺寸LFH块替换原空间:

1
2
3
01136b90 000c 0000 [00] 01136b98 00058 - (busy)  // 填充'A'
... 释放后 ...
01136b80 01136b88 00e30000 00e38ca0 90 - 8 LFH;busy  // 0x88字节块填充'B'

成功替换但需控制整个子段释放。

大块分配与堆喷洒

VirtualAllocdBlocks分配

分配0x7ffb0字节(超过阈值)触发VirtualAllocdBlocks:

1
2
3
VirtualAllocdBlocks @ e2009c
00c48018 10000 0004 [00] 00c48020 7ffb0 - (busy VirtualAlloc)
...

地址对齐页边界,但间隔大于Win7,降低喷洒连续性。

精确堆喷洒

避免LFH和VirtualAllocdBlocks,使用对齐尺寸(如0x20000-8)创建连续分配:

1
2
3
[4] Allocated chunk at 0x015B0048
[5] Allocated chunk at 0x015F0048  // 地址对齐
...

通过每0x1000字节重复结构(垃圾数据+ROP+shellcode)实现精确定位:

1
0c0c0c0c  24 24 24 24  // 成功放置标记"$$$$"

建议先分配大块(0x1ff00字节)并保留小块防止合并,再释放大块创建"安静"喷洒环境。

结论

Windows 10堆管理在保持基础机制的同时引入重要变化:

  1. LFH激活:保持18次阈值但取消LIFO行为,降低可预测性
  2. 地址布局:LFH块不再连续,增加布局操控难度
  3. 堆喷洒:VirtualAllocdBlocks间隔增大,但通过尺寸控制仍可实现精确喷洒

测试代码提供实际验证方法,建议结合具体应用场景调整策略。

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