浅析Windows下堆的结构

简介

Windows下的堆主要有两种,进程的默认堆和自己创建的私有堆。在程序启动时,系统在刚刚创建的进程虚拟地址空间中创建一个进程的默认堆,而且程序也可以通过 HeapCreate 函数来调用 ntdll 中的RtlCreateHeap 来创建自己的私有堆,所以一个进程中可以存在多个堆。

虽说这两种堆名称不同,但是其本质是相同的,区别的只是返回的句柄不同,私有堆虽然名字是私有,但并不是只能在创建它的线程中使用,如果得到它的句柄,在其他线程中也可使用。

堆的信息

堆的相关信息可以在/PEB(进程环境块)中看到

我们需要注意的是上面几个偏移位置的信息

0x18 默认堆的地址 0x78 默认堆的默认大小 0x7c 默认堆的初始提交大小 0x80 与堆释放有关的阈值 0x84 与堆释放有关的阈值 0x88 程序中堆的数量 0x8c 程序中最大的堆的数量 0x90 存储所有堆地址的数组

只有当本次释放时

1. 本次释放的堆块大小超过了_PEB中的HeapDeCommitFreeBlockThreshold字段的值。 2. 空闲空间的总大小超过了_PEB中的eapDeCommitTotalFreeThreshold字段的值。

堆管理器才会将该内存交还给内存管理器,否则继续由堆管理器管理

查看一下所有堆的地址

可以看到进程中四个堆的地址,同样使用!heap -h来看一下

堆的结构

上面我们通过 PEB 查看了进程中堆的一些信息。

在 Windows 的堆中管理着许多的堆段 (Segment),在堆创建时同时创建第一个堆段,称为 0 号段,之后如果一个段不够,如果指明了 HEAP_GROWABLE 标志,会创建其他的堆段,但是最多有 64 个堆段,而这一个个堆段,正是由堆块 ( 类似于 linux 的 Chunk) 构成。

现在选其中一个堆,我们来看一下堆的详细结构

注意这几个偏移位置

0x14 最大分配内存,超过此大小就交由内存管理器分配 0x2c 最大申请大小 0x50 管理由内存管理器分配内存的链表 0x58 该堆中堆段数组 0x178 管理 128 个空闲堆块的双向链表头指针 0x580 指向前端分配器

在 0x14 偏移处的值的单位是 8byte,也就是最大申请大小为 0xfe00 * 8 = 508kB,由于申请内存时堆块的头部需要占用 8 字节,所以最大申请大小 = 最大分配大小 - 8B

看一下偏移 0x178 处

可以看到 HEAP 结构维护的是 LIST_ENTRY 指针,该指针指向的正是双向链表的头指针结构

堆段

同样看一下 0x58 处的 Segments

注意这几个偏移位置

0x00 存储该堆段所有堆块 0x10 记录 _HEAP 结构的地址 0x18 维护该段的基址 0x20 第一个堆块地址

堆块

查看堆块的结构

注意这几个偏移位置

0x00 本堆块的大小 0x02 前一堆块的大小 0x05 标志位 0x06 由于对齐原因多分配的字节数 0x07 所属的 segment 号

Flags 的标志位有下面几种情形

0x01 该块处于占用状态 0x02 该块存在额外描述 0x04 使用固定模式填充堆块 0x08 虚拟分配 0x10 该段最后一个堆块

我们可以看到堆块的结构相比较于Linux下的Chunk结构来说,首先 prev_size 和 size 的位置调换了一下,并且该块的状态并不在下一块的 size 位中保存,而是在本块的 Flags 位保存,这对我们的漏洞利用提出了新的要求。

实例观测私有堆

编译下面的代码并使用 WinDBG 调试

首先在 main 函数下断点,运行

单步调试到 HeapCreate 函数返回,通过 eax 返回值观察堆结构,也可 !heap 查看

这里返回的 eax,即hHeap句柄值为3a0000

继续单步调试到 HeapAlloc 函数返回,得到这次申请的堆块地址为 0x3a6500

由于返回的地址是加上堆块头的地址,所以查看堆块结构时减去 8byte

可以了解到此堆块大小为 0x82*8 byte,属于堆段 0,并且多分配了 0x1c 字节,Flags位表示该块占用,有额外描述并且被 ‘baadf00d’ 填充

继续单步执行到 HeapFree 函数返回,再次观察该堆块

注意到填充发生了变化,对于已释放的堆块,结构体为 Heap_Free_Entry,相较于 Heap_Entry 多了两个空闲链表的指针

这里的堆块由于 free 后合并,所以 size 变成了合并后的值

堆的管理

在 Windows 中堆的申请回收使用了两种分配器,分别叫做前端分配器和后端分配器,当进程发起申请堆的请求时,首先由前端分配器处理,如果处理不了的话在交由后端分配器处理,在这点上前端分配器有点类似于 Linux 下的 FastBin,后端分配器类似于 UnsortedBin,SmallBin,LargeBin 组成的 Bin 数组

Windows 提供了两种前端分配器,分别为旁视列表(LAL)和低碎片(LF)前端分配器,其中前者在 Vista 之后的版本中不再使用

小结

这篇文章主要分析了 Windows 下不同于 Linux 的堆的结构,而 Windows 下堆的申请回收类似于 Linux,详情可以查看我的Dance In Heap系列文章。

原文发布于微信公众号 - FreeBuf(freebuf)

原文发表时间:2017-12-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

Android 四种常见的线程池

引入线程池的好处 1)提升性能。创建和消耗对象费时费CPU资源 2)防止内存过度消耗。控制活动线程的数量,防止并发线程过多。 我们来看一下线程池的简单的构造 ...

20470
来自专栏大内老A

.NET Core的日志[5]:利用TraceSource写日志

从微软推出第一个版本的.NET Framework的时候,就在“System.Diagnostics”命名空间中提供了Debug和Trace两个类帮助我们完成针...

25960
来自专栏Java3y

线程池你真不来了解一下吗?

26560
来自专栏JMCui

多线程编程学习五(线程池的创建)

一、概述 New Thread的弊端如下:        a、每次New Thread新建对象性能差。        b、线程缺乏统一的管理,可能无限制的新建...

422110
来自专栏Java 源码分析

Exectors框架 源码分析

Exectors框架 源码分析 1. 在阅读源码时做了大量的注释,并且做了一些测试分析源码内的执行流程,由于博客篇幅有限,并且代码阅读起来没有 IDE 方便,...

29270
来自专栏Java面试通关手册

Java多线程学习(八)线程池与Executor 框架

Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去,欢迎建议和指导):https://github.com/Snailclimb/Java_G...

21940
来自专栏Java学习之路

从源码看JDK提供的线程池(ThreadPoolExecutor) 一丶什么是线程池二丶ThreadPoolExecutor的使用三丶从源码来看ThreadPoolExecutor

一丶什么是线程池 (1)博主在听到线程池三个字的时候第一个想法就是数据库连接池,回忆一下,我们在学JavaWeb的时候怎么理解数据库连接池的,数据库创建连接和关...

490100
来自专栏cmazxiaoma的架构师之路

通过了解RejectedExecutionException来分析ThreadPoolExecutor源码

观看本文章之前,最好看一下这篇文章熟悉下ThreadPoolExecutor基础知识。 1.关于Java多线程的一些常考知识点 2.看ThreadPoolE...

17520
来自专栏xdecode

Java 对IP请求进行限流.

高并发系统下, 有三把利器 缓存 降级 限流. 缓存: 将常用数据缓存起来, 减少数据库或者磁盘IO 降级: 保护核心系统, 降低非核心业务请求响应 限流: 在...

78080
来自专栏好好学java的技术栈

Java多线程面试准备:聊聊Executor框架

在HotSpot VM的线程模型中,Java线程被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程;当Java线程终止时,这个操作系统...

37450

扫码关注云+社区

领取腾讯云代金券