首页
社区
课程
招聘
[原创]堆溢出基础
2021-2-15 11:35 10021

[原创]堆溢出基础

2021-2-15 11:35
10021

堆溢出基础

一;什么是“堆”?

“堆”是什么呢?我相信网络当中有大量的关于堆的定义,那么我仅仅谈谈我个人对“堆”的理解。其实本质上来说,它也是一个虚拟的内存空间,它和栈一样是特殊利用的内存空间。所以,堆同样有“读”,“写”,“释放”等操作。那么它相对于我们熟悉的栈又区别在哪里呢?我们知道栈在使用结束后会自动回收且在使用的时候会自动分配空间。“堆”就不一样,它需要程序员自己申请并且在使用结束后由程序员自己的意愿释放“堆”空间。

二;“堆”的构成元素

首先,我们先在大脑中有个基本的对“堆”的理解。这里,我采用一幅图来帮大家将“堆”的逻辑概念拉到类似“物理概念”。如下图:

 

我们只需要暂时关注中间红色框框标记的部分,由图可知,堆的结构是由“堆表”以及“堆块”构成这,其中“堆表”主要作用是用来索引堆块的位置。其中,堆表主要是有两种:空闲双向链表(Freelist),快速单向链表(Lookaside)(注意:堆表仅仅是用来索引空闲态的堆块,即未被使用的堆块)“堆块”就是用来提供程序员申请堆空间的。

2.1;堆块结构

堆块的状态我们可以大致分为“使用状态”和“未使用状态”,即该堆块有没有被申请使用,如果没有则链入堆表中。下面我们来了解一下堆块的结构。

2.1.1;使用状态

我们同样用一张图来说明一下使用状态下的堆块。主要是“块首”+“块身”组成。详细如下图:

 

2.1.2;未使用状态

未使用状态下堆结构大体是和使用状态下的堆一样,区别在于“块首”添加了8字节的指针对。该指针对就是用来链路堆表当中的。详细如下图:

 

2.2;空闲双向链表(Freelist)

空闲双向链表有“段表索引”,“虚表索引”,“空表使用标识”,“空表索引区”组成。我们需要特别了解的是“空表索引区”这个内容。“空表索引区”由128项指针数组组成。这对指针用来将空闲堆组织成双向链表。根据堆块的大小不同存放的指针数组也是不同。索引由“0”编号开始,即128项的标号为free[0]~free[127]。当中free[1](第二项)链接8字节的堆块,free[2](第三项)链接16字节的堆块。即每项链接的堆块大小均比其前一项链接的堆块增大8字节。值得注意的是free[0]链接的是大于等于1024字节的堆块。详细如下图展现:

 

从上图我们可以看到,每一项“free”后面均链接着一个堆块,每两个空闲堆块均相互链接,直到最后堆块又链接着最初的“free”,形成了一个类似循环的结构。接下来,我们用一个实例来看看,这样一张表是如何存储在物理内存当中的。

实例:

1;案例程序

#include <windows.h>
main()
{
HLOCAL h1,h2,h3,h4,h5,h6;
HANDLE hp;
hp = HeapCreate(0,0x1000,0x10000); //创建私有堆
__asm int 3
 
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,3); //在堆当中申请空间
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,5);
h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,6);
h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h5 = HeapAlloc(hp,HEAP_ZERO_MEMORY,19);
h6 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);

//free block and prevent coaleses
HeapFree(hp,0,h1); //free to freelist[2] //释放堆空间
HeapFree(hp,0,h3); //free to freelist[2]
HeapFree(hp,0,h5); //free to freelist[4]

HeapFree(hp,0,h4); //coalese h3,h4,h5,link the large block to freelist[8]
 

return 0;
}

        2;实验环境


测试环境

操作系统

Window2000

编译器

Visual C++ 6.0

Build版本

release版本

3;验证流程

3.1;打开运行程序,当代码执行到‘__asm int 3’引发中断,这个时候我们在OD当中打开程序。这个时候,我们发现程序停在断点“CC”位置。

 

3.2;查看堆表空表索引区

我们来回顾代码,在函数HeapCreate()执行完成之后,即在成功创建堆之后会将堆区的指针存放到寄存器EAX当中,这个时候,我们来查看一下对应寄存器即可找到堆的起始位置。

 

由上图我们知道了堆的起始位置,我们知道堆的结构是由堆表+堆块组成的,那么地址0X00520000则可以说是堆表的其实位置。接下来,我们在内存当中找到对应位置:

 

 

我们主要是需要关注“空表索引区”该区的偏移量为0x178。则空表索引区的地址为:0x00520178。回顾一下空表索引区的结构,我们知道第一项为free[0],因为在代码当中,目前只是创建了堆空间,并没有分配空间。所以,我们发现,出free[0]指向了地址0x00520688,其他free均指向自己本身。为什么是这样呢?仔细想想就不难理解。没有分配的空间算是一个整体,并没有分割,这样的空间自然就链接到free[0]当中了。详细如下图。

 

3.3;堆块分配

下面我们将程序运行,申请空间h1~h6。详细如下图:

 

执行完成之后,我们知道堆空间已经分配完毕,我们来思考一下,刚刚我们发现其实整体堆块是链接在free[0]。既然没有合适的精准分配方式,那就在较大的堆块当中切割一下。那么我们在下图位置查看申请堆空间的情况:我们已经发现,空间已经分配。(注释:堆空间分配的单位为块,即最小块为8字节。E.G h1 当前Self Size大小为2块,换算为字节则为16字节)

 

对照代码,我们发现代码申请的堆块大小与实际给我们的大小不太一样。这是什么原因呢?实际上申请的大小会加上“块首”大小,并且申请大小不足8字节需要补足8字节。

3.4;堆块释放

下面,我们将程序继续执行,执行至h5空间释放。程序执行截至位置如下图:

 

程序执行到该位置,我们已经知道h1,h3,h5空间已经释放,那么我们来分析一下,我刚刚堆分配当中查看,我们知道h1,h3分别了16字节,按照空闲双向链表规则,它们释放后会被链路free[2]当中。我们来看看空表索引区。详细如下图:

 

这里我们发现free[2]位置已经不是存储的本身地址了。而是填充了地址0x00520688,0x005206a8。我们先记住这两个地址,后面会有发现。接下来,我们再次定位到刚刚堆空间,如下图:由空闲状态下的堆块结构,我们知道,该红框标识的位置正是链入free所需要的指针对。

 

那么,我们来看看,现在我们知道h1,h3会被链入到free[2]。所以,我们暂时仅仅看h1,h3。留意他们存储的地址:0x005206a8,0x00520188,0x00520188,0x00520688。那么,我们用图形来展示一下:

 

看这是不是构成一个链路结构呢?各位自己体悟一下。

3.5;堆块合并

刚刚我们释放了h1,h3和h5。这些堆块不是连续的,所以不会产生合并现象,下面我们继续运行程序,释放h4,这样h3,h4,h5就会产生堆块的合并现象。我们看看,首先我们运行代码位置如下:

 

现在我们,定位到堆空间看看,我们发现在之前h3位置的堆块大小发生了变化,大小为64字节,并且链入地址也是发生了变化,被链入到了free[8]位置。

 

总结:由上面观察我们发现,堆块的合并并不会影响这原来三个堆块的位置,仅仅是将其大小更改,并且链入到对应的free当中。

2.3;快速单项链表(Lookaside)

快速单项链表大体与双向链表相同,区别在于快速单项链表单项链接,且不会发生合并现象。且每项只有4个节点。详细如下图:

三;参考书籍

0day安全:软件漏洞分析技术


 

 

 



[培训]《安卓高级研修班(网课)》月薪三万计划

最后于 2021-2-15 14:37 被天象独行编辑 ,原因:
收藏
点赞2
打赏
分享
最新回复 (1)
雪    币: 219
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wx_小小毛 2021-3-18 16:04
2
0
怎么感觉突然就结束了呢  意犹未尽啊
游客
登录 | 注册 方可回帖
返回