Windows下的堆机制
参考0day安全那本书
0x01 堆块分配
堆块分配可以分成三类,块表分配,普通空表分配,和零号空表分配
从快表中分配
寻找到大小匹配的空闲堆块,将其状态修改为占用态,把它从堆表中卸下,最后返回一个指向堆块块身的指针给程序用
普通空表分配
首先寻找到最优的空闲块分配,若失败,则寻找次优的空闲块分配,即最小能满足的空闲快
零号空表分配
零号空表按照升序大小链着大小不同的空闲块,所以在分配的时候先从free[0]反向查找最后一个块,也就是先找到表中最大的一个块,如果这个块都不能满足,那就不用找了,如果能满足要求,再正向搜索最小能够满足要求的空闲堆块进行分配(所以零号空表中要按照升序排列)
特殊的找零钱现象:
当空表中无法满足要求时,一个稍大的块会被用于分配,这种次优分配发生时,会先从大块中按请求的大小精确地分割出一个块进行分配,然后将剩余的部分重新标注块首,链如空表。
0x2 认识数据结构
1. 空表
空闲堆块的大小 = 索引项*8字节
如下是空表的数据结构:
这里面只有free[0]最特殊,其余的都还行
2. 快表
使用单链表来实现的
这是Windows为了加速堆分配而采用的一种数据结构
因为其中的堆块是不会合并的
并且每条快表中最多只有4个节点,因此快表很容易被填满
0x3 堆块合并
暂空
几个要点
- 快表被设置为占用态,所以其内的堆块是不会发生合并的
- 快表只有精确匹配,不存在找零钱
- 快表是单链表,操作更简单,堆安全中也不太常见
- 快表很快,所以在分配和释放的时候都是优先使用快表,失败是才使用空表
- 快表只有四项,所以很容易被填满,因此空表也是被频繁使用的
尝试调试:
代码如下:
#include <windows.h>
int main(int argc, char const *argv[])
{
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);
HeapFree(hp,0,h1);
HeapFree(hp,0,h3);
HeapFree(hp,0,h5);
HeapFree(hp,0,h4);
return 0;
}
命中断点
此时
此时我们看到
只有hp有一个值,0x003a0000
这是在调用了函数HeapCreate之后得到的一个值,尝试在内存中去查看(因为我不确定是不是指针)
内存布局当HeapCreate()函数成功地创建了堆区之后,会把整个堆区的起始地址都返回给EAX,在这里是0x003a0000
单步运行一次之后
h1此时的值为:0x003a0688
和我们课堂上分析的一样!
此时的内存:
VC++很人性化的标记出了被修改的值
为什么是0x003A0028
这一处的值被修改了呢?
先看下0x003a0688
处的值:
重点是这两个值:
偏移0x178处是空白索引区
当一个堆刚刚被初始化的时候,它的堆块状况是比较简单的
- 只有一个空闲态的大块,这个块被称作“尾块”
- 位于堆偏移0x0688处(启用快表之后这个位置将是快表)
- Free[0]指向尾块
- 除0号空白哦外,其余各项索引都指向自己
而这个时候,h1指向的就是偏移0x0178
处的空白索引区
我们在这里可以看到好多指针,都指向它自己
同时也看到了指向0x003a06a8
的指针
继续单步
h2得到值
此时0x003a0178
两个值被修改了
因为空白索引区此时要指向新开的区域
此时0x003a06a8
这时候挺神奇的,分配了一个h2,h2的两个指针指向了空白索引区
但是h1的两个指针就没了
还有一个问题,这里堆块分配的大小似乎不一样?
每一个堆块是占用了32个字节。。
再单步
空白索引区的指针继续被修改
空白索引区 此时h3已经被分配了内存依旧可以看到一对指向空白索引区的指针
h3指向的内存 h4被分配内存 h3指向的内存区域发生了很大的变化 h5被分配内存 依旧是h3指向的区域发生了很大变化,还有EAX保存了返回的值 h6被分配内存 连续分配6次内存之后的空白索引区
释放掉h1
h1被释放,但是h1的内容不变 空白索引区的指针发生变化
此时查看0x003a0688
处的内存
再释放掉h3之后发生的变化
释放了h3
我们看到0x003a0688
也就是原来的h1现在指向了0x003a06c8
也就是h3
而0x003a06c8
指向了0x003a0198
和0x003a0688
也就是说
h3的flink指向了0x003a0198
,blink指向了h1
h1的flink指向了h3,blink指向了0x003a0198
此时0x003a0198
处的指针也正好指向了h1和h3
再释放掉h5
此时h1,h3之前的关系都没有变
查看h5的内存发现其指向了0x003a01a8
此时就可以猜到了
0x003a01a8处的内存
0x003a01a8
应该是free[8]了
接下来应该是重点了
释放掉h4
0x003a01a8处的内存
不太会分析
看一下0x003a06c8
此时由 0x003a01e8
指向
0x003a01e8
应该是free[13]了
原来的h1此时指向了0x003a0198
原来的h3也指向了0x003a01e8
h3指向了0x003a01e8
所以这个时候发生了堆块的合并
但是是怎么合并的呢?
因为h3,h4.h5都相邻,所以才会发生堆块合并
但是我还没想清楚每个堆块的大小。。
网友评论