当前位置:   article > 正文

南京邮电大学漏洞挖掘——观察堆结构,及观察Windows10下的堆结构及其管理过程_解读编码后的heap_entry结构

解读编码后的heap_entry结构

关于堆的背景知识

堆的引入

在一个C\C++程序中,变量的储存位置可以分为以下几类:

  • 全局变量:这种变量一般存储在PE文件的.data段中,在程序初始化时由用户或者编译器进行初始化;
  • 局部变量:这种变量存储于程序的栈中,在程序刚刚进入一个函数时由编译器在栈中分配对应的空间,当程序清栈时,局部变量的空间被收回;
  • Malloc或者New运算符申请的一块连续的内存空间,该空间需要程序编写者自己进行申请或者回收;

第三种申请到的内存空间就属于堆分配的内存空间,通过堆分配的内存空间和全局变量、局部变量有较大的不同,主要体现在以下几点:

  • 堆的分配时在运行时决定的,返回的句柄(连续内存的首地址)是和分配时的环境密切相关的,而前两种的分配是编译时确定的,在一个可执行文件生成时,变量所处的虚拟内存地址在大部分情况下就是固定的了。
  • 也正是因为堆分配是在运行阶段,所以堆的分配空间是大小可变的,而前两种的内存空间大小是定死的。

堆理解

对于操作系统来说,某个时刻(一般是进程被创建时),操作系统收到了一个请求大量内存块的请求,收到请求后,操作系统的内存管理器将一块大的内存块返回给这个进程,确切的说,提出请求的是进程中的堆管理设施,也是堆管理设施对这一大块内存进行管理。而对于用户来说,当某一时刻申请一块内存空间时,实际上是向对管理器申请的,堆管理器会从他说管理的内存中划分出一小块分给用户。
从这两个角度来说,堆管理器更像一个批发商:从操作系统批发一大块内存存放在自己的仓库,并面向用户售卖这一大块内存。
另外对应MallocNew的堆管理器叫做CRT堆,该堆有三种工作模式,其中一个工作模式是对Win32堆的简单封装,并且大部分情况下CRT堆也工作在这种模式中。1

通过实验观察堆

这里说一下在Windbg中常用的三种命令2

  • 标准命令:用于提供基础调试功能,如修改寄存的r指令、观察结构化内存的dt指令;
  • 元命令:内嵌在Windbg中的指令,一般情况下用于调试器的设置,也可以用于编写命令程序,由.符号开头;
  • 扩展指令:由第三方编写的扩展插件,这个第三方可以是微软官方,由!开头

除此之外,在Windbg中关于堆的扩展指令可以在MSDN中找到,传送门

找到计算器的位置

我们首先要找到计算器的位置,这里推荐一下微软自带的更加详细的任务管理器,可以看到每个进程加载好的DLL和对应的文件目录地址,名字叫做procexp,对应的传送门,安装好后有两个,64位和32位的区别主要是是否显示WOW64(Windows-on-Windows 64-bit)对应的转接层DLL,这里影响不大。
在这里插入图片描述
先打开计算器进程,并打开procexp进程监视器,即可找到对应的进程的硬盘位置:
在这里插入图片描述

加载Windows符号

由于Windbg是面向高手的,从另外一个角度来说也就是门槛比较高,其中一个门槛就是Windwos的符号加载,不同于Ollydbg,Windbg可以显示许多和Windows实现细节有关的数据结构,这就需要Windows编译时所生成的符号表,而Windows的版本千变万化,这时就需要从微软的官方符号服务器中下载对应的符号表以显示Windows的相关运行时需要的数据结构。而Windbg的符号加载是手动的,也就是由用户选择是否加载某个模块的符号。在本实验中查看PEB结构和堆结构是需要加载符号的,否则会出现如下错误:
在这里插入图片描述

通过如下途径设置符号服务器地址:
在菜单中选择File->Symbol File Path,打开如下对话框:
在这里插入图片描述
填入如下指令:

srv*c:\\symbol*https://msdl.microsoft.com/download/symbols
  • 1

其中c:\symbol为本机缓存的地址。需要注意的是,由于符号服务器被墙了,需要开一下代理,否则无法下载对应的符号,也就没法实验了。
上述的指令也可以在命令行中输入.symfix来实现。如果在符号设置过程中发生问题,可以通过!sym noisy指令来打开Debug模块看到具体哪里出现了问题。

显示PEB块

通过扩展指令!peb指令即可观察到一个进程的环境块。
在这里插入图片描述
可以看到,PEB的环境块极多,由于微软并没有开源Windows系统,所以PEB块的各个部分并没有官方解释,但是网络上已经有很多关于PEB块的解释了。

观察堆

堆的详细结构及其实现和面向用户提供的模型可以在《软件调试》的23章中进行查看,简单来说,一个堆的基本单位是堆块,多个堆块组成段,段组成堆。

枚举进程拥有的堆

通过!heap指令可以展示一个进程所拥有的堆:
在这里插入图片描述
其中的Segment Heap是在微软2020年5月新更新的堆设施3,而NT Heap则是传统的堆结构。

观察一个堆的结构

使用dt ntdll!_HEAP address指令来观察一个堆的数据结构
在这里插入图片描述

关于段的一些疑问

其实第一次看到这个结构还是挺惊讶的,因为之前在书上看到的堆数据结构并没有这么复杂,而且传统的Segments数组没了,这就很迷了,后来查到好像是在20205月份的时候Windows进行了一波大更新…
通过!heap -hf指令可以看到进程的默认堆所拥有的所有段
在这里插入图片描述通过dt ntdll!_HEAP_SEGMENT Address指令确实看到了段的对应的数据结构和魔数。
在这里插入图片描述

但是这里有一个小疑问就是在以前的结构中段结构的头部是通过HEAP结构中的Segments字段找到的,扩展指令!haep -hf就是以比较友好的形式将段解释了出来,但是在新的Windows中似乎Segment的记录结构完全找不见了,即使在+-0x10000的范围内搜索段起始地址还是找不到对应的踪迹。

在这里插入图片描述
但是在Windbg的扩展指令中还是能使用相应的指令,所以肯定是新版本的Windows将这个结构搬到了某个地方,希望某位大佬能够解释一下。

观察HEAP_ENTRY结构

尽管在新版本中堆的管理结构变化比较大,但幸好无论是_HEAP结构还是_HEAP_SEGMENT结构都是放在一个堆块里面的
在这里插入图片描述

而一个堆块的管理结构就是HEAP_ENTRY,找到对应的地址,就可以查看对应的HEAP_ENTRY结构了。
在这里插入图片描述

观察堆的分配过程

先说一点题外话就是我在尝试进行堆分配观察的时候花费了很长时间想观察X64结构的堆,并尝试熟悉Windbg工具,但是最后发现效果并不好,由于时间关系,现在暂时先使用Windbg(x86)+Ollydbg进行观察,但是以后还是要有一套能调试x64的工具的。
首先附上源代码

#include <windows.h>
#include <stdio.h>
#include<intrin.h>
typedef unsigned __int32  ptr32;
constexpr unsigned int FreeLists_Index = 0xc0; //该偏移随版本不同而不同!!!
void Show_FreeLists(ptr32 HeapHandle) {
	ptr32 Entry = (HeapHandle + FreeLists_Index);
	printf("%08xh", Entry);
	ptr32 NowNode = *(ptr32*)Entry;
	while (NowNode!=Entry)
	{
		printf("->%08xh", NowNode);
 		NowNode = *(ptr32*)NowNode;
	}
	printf("\n");
}
int main()
{
	HLOCAL h1, h2, h3, h4, h5, h6,h7;
	HANDLE hp = NULL;
	hp = HeapCreate(0, 0x1000, 0x1000);
	Show_FreeLists((ptr32)hp);
	h1 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8);
	h2 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8);
	h3 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8);
	h4 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8);
	h5 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8);
	h6 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8);

	HeapFree(hp, 0, h1);
    Show_FreeLists((ptr32)hp);
 	HeapFree(hp, 0, h3);
    Show_FreeLists((ptr32)hp);
	HeapFree(hp, 0, h5);
	Show_FreeLists((ptr32)hp);
	
	h1 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8);
	Show_FreeLists((ptr32)hp);
	printf("Finish!");

	return 0;
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

找到空闲链表

对应的Win32堆的API可以在网上百度到,这里就不在赘述了。
另外还有一点就是在原文4中提到的设置也不需要进行设置,直接使用就好了。还有就是要注意到的一点是我在调试过程中发现堆结构及其管理结构、方法是和操作系统密切相关的,这也导致了以下的调试信息全部为在Win10 20H0版本下的结果。
在这里插入图片描述
首先在对应的HeapCreate函数下断点,找到对应的堆结构的首部(函数的返回地址就是堆结构的首部,如我这里的:0x11F0000)
在这里插入图片描述
接下来就可以在内存窗口里面查看到对应的堆结构了,
在这里插入图片描述而堆的管理数据结构和Windows版本密切相关,所以需要在Windbg上下载对应的符号表,进行查看
在这里插入图片描述
如我这里的空闲链表对应的就是偏移为0xc0的位置。在内存窗口中找到位置:
在这里插入图片描述

观察堆的分配

此时该双向链表入口的前向节点在初始化时永远指向一个从未被分配过的地址(在Windows中如果一个堆的内存空间没有被用户所占有,那么内存被填充为0xfeeefeee,因为和Free单词很像)。
在这里插入图片描述当使用HeapAlloc函数分配了一次堆块后,FreeLists的指针变为
在这里插入图片描述
其含义是一个新的从未被分配过的地址块(在刚才地址块的下面一点)。
接下来源码中的四次分配也是如此。

观察堆的释放

不连续堆块的释放

比较有趣的是使用了HeapFree函数后,此时的FreeLists字段变成了如下值:
在这里插入图片描述
其中的前向节点指向刚刚释放掉的空间
在这里插入图片描述

而后向节点指向一个从未被申请过的空间
在这里插入图片描述
而从最后一个HeapAlloc的函数申请中可以发现,即使申请的空间小于以释放的堆块,堆管理器仍然从一个从未被访问过的空间中切割一个堆块给他。
具体示意图如下:
当没有堆块释放时的双向链表:

在这里插入图片描述分配时的链表变化情况
在这里插入图片描述而当有堆块被释放时,假设释放的节点大小相等,不连续(也就是说释放的两个节点之间还有未被释放的节点),并且释放顺序为1->2->3链表的链接情况如下所示:
在这里插入图片描述

连续堆块的释放

而当释放节点的时候,如果释放的节点的左右节点是已被释放的节点,那么堆管理器会将空闲链表节点进行释放。如下图所示:
在这里插入图片描述
对应的示例代码为:

	HeapFree(hp, 0, h1);
    Show_FreeLists((ptr32)hp);
 	HeapFree(hp, 0, h3);
    Show_FreeLists((ptr32)hp);
	HeapFree(hp, 0, h2);
	Show_FreeLists((ptr32)hp);
	HeapFree(hp, 0, h5);
	Show_FreeLists((ptr32)hp);
	h7 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 9);
	Show_FreeLists((ptr32)hp);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

另外需要注意的是空闲链表中的释放过程并没有分配。

总结

通过实验,现在总结一下堆的分配和释放的一些小小体会:

  • 堆在_HEAP结构中会保留一个FreeLists字段,该字段用于指出空闲链表的头节点;
  • 当用户向堆管理器申请一个内存块时,堆管理器会遍历该空闲链表,知道找到一个适合大小的空闲堆块,并将该堆块分配给用户;
  • 当用户释放一个堆块时,堆管理器会将该堆块加入堆管理器,如果多个释放的堆块相邻,则会合并该堆块,该过程的目的是用于处理内存碎片问题;
  • 当用户申请和释放堆块时堆管理器会修改双向链表,此时就是堆溢出发挥功效的时候,对应的防护手段为SafeUnLink

  1. 《软件调试(第二版)卷二:Windows平台调试(下卷)》,张银奎 著,23.1节 ↩︎

  2. 《软件调试(第二版)卷二:Windows平台调试(下卷)》,张银奎 著,30.2节 ↩︎

  3. http://soft.hqbpc.com/m/view.php?aid=2975 ↩︎

  4. https://blog.csdn.net/binghupo/article/details/53457518 ↩︎

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/106318
推荐阅读
相关标签
  

闽ICP备14008679号