前言

对于堆方面的内容,在没有别人帮助的情况下,学起来就很吃力,不像栈那么好理解,本篇记录我对堆的一些了解,不足之处,还望斧正。

什么是堆

栈无法满足将函数内部的数据传递到函数的外部,虽然有全局变量可以传递,但是不能动态的产生,只能在编译的时候定义,在很多情况下显得鸡肋,这时候堆(Heap)就成了一种选择。堆是一个巨大的空间,常常占据着整个虚拟空间的绝大部分,在这片空间里,程序可以请求一块连续的内存并自由使用,直到程序主动放弃之前都会有效。

堆管理

程序向操作系统申请一块适当大小的堆空间,然后有程序自己管理这块空间,具体来讲,管理着堆空间分配的往往是程序的运行库,运行库相当于从系统批发了一块较大的堆空间,然后零售给程序用,当售完或程序有大量的内存需求时,在根据实际需求再次向系统申请进货,运行库通过堆的分配算法来管理程序的对空间。

Linux下提供两种堆空间分配的方式,及两个系统调用:

brk() 系统调用
brk()的c语言形式声明如下:
int brk(void* end_data_segment)

实际作用就是设置进程数据段(Linux下将数据段和BSS段合在一起统称位数据段)的结束地址,及它可以扩大或缩小数据段,达到扩大或缩小堆空间的效果。
glibc中还有一个函数交sbrk,它是对brk函数的封装,只不过参数和返回值略有不同,sbrk以一个增量(Increment)作为参数,及需要增加(负数为减少)的空间大小,返回值是增加或减少后数据段的结束地址
mmap() 系统调用
mmap()的c语言形式声明如下:
void *mmap(
   void *start,      指定申请空间的起始地址,如果置为0,那么Linux会自动挑选合适的起始地址
   size_t length,    指定申请空间的长度
   int prot,         申请空间的权限(可读,可写,可执行)
   int flags,        映射类型(文件映射、匿名空间)
   int fd,           用于文件映射时指定文件描述符
   off_t offset      用于文件映射时指定文件偏移
);
作用就是向操作系统申请一段虚拟地址空间,当然这块虚拟地址空间可以映射到某个文件(这也是这个系统跳用最初的作用),当他不将地址空间映射到某个文件时,称这段空间为匿名空间,匿名空间可以拿来当作堆空间。

堆结构

这里可以参考CTF pwn 中最通俗易懂的堆入坑指南,在看博客的时候产生一个疑问,为什么malloc(8)的值为0x21,后来自己写了程序,调试查看以及在网友的帮助下知道了原因。

64位系统
malloc(8) 由于需要内存对齐,所以8需要对齐到16,然后加上pre size、size以及prev_inuse的值,也就是文中的16+8+8+1=0x21的由来,但是当malloc(24)的时候,发现系统给的空间也是0x21,这就又产生了疑问,不是应该为32+8+8+1=0x31吗,后来询问网友得知,内存空间复用这个神奇的操作,也就是当前的堆会占用下一个堆的pre size(前提:下一个堆的前一个chunk是使用状态),所以malloc(24) 24<16+8 所以,还是会分配0x21的空间给他

64位情况

32位和64位原理一样,只不过,对齐的到的大小不一样,64位对齐到16,32位对齐到8

可以观察到,程序在第一次malloc之后会产生heap,并且大小是132kb,个人测试,32位也同样是132kb,往后,如果程序再malloc的时候就会从这里面申请,132kb的空间就是arena,由于是主线程分配的,所以也叫main arena

top chunk的size会由于每次的malloc而减小

free函数

将free掉的chunk放在不同的bin中

fast bin
small bin
large bin
unsorted bin

总结

对于堆,总是感觉学了一遍,两遍,都不清楚到底是干嘛的,更加谈不上如何利用

Reference

《程序员的自我修养》

CTF pwn 中最通俗易懂的堆入坑指南



pwn      heap pwn

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!