由于CTF比赛中需要对堆溢出利用熟悉掌握。我决定从源码入手,并在分析过程中记录自己的心得。加深自己的理解。
首先我们得知道一些基本概念。拿32位程序来举例子,每个程序都有自己专属的4GB的虚拟空间,我们写的C代码在这个4GB空间中是有明确分布的,详情可以参考我另外一篇博客32位内存布局。在这个空间中,有堆有栈有各种各样的段。我们malloc函数主要是用于堆内存的分配。也就是动态的内存分配。但malloc函数只是提供给用户使用的一个接口,底层其实干了很多事。我们可以理解为malloc函数执行依赖于堆管理器而堆管理器在管理堆的时候也会依赖于操作系统提供的函数。引用ptmalloc源码分析的文章。对 heap 的操作,操作系统提供了 brk()函数,C 运行时库提供了 sbrk()函数;对 mmap 映射区域的操作,操作系统提供了 mmap()和 munmap()函数。sbrk(),brk() 或者 mmap() 都可以用来向我们的进程添加额外的虚拟内存。Glibc 同样是使用这些函数向操作系统申请虚拟内存,这些目前我们不关心。
我们重点关注堆管理器的实现。学过操作系统的都知道,我们的内存是存在分页机制的,将内存划分为一个一个4kb的页框来管理。类比过来那么在堆这片内存空间中,linux使用了一种名叫chunk的堆管理基本单位。源码中chunk结构的定义如下:
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
一个chunk是存在两种状态的,一种是inuse也就是说正在使用,一种是free也就是空闲chunk。这两种状态是不一样的,文字的表达可能会很死板所以我们借助于图来理解:
一个在使用的chunk是这样的:
我们看到一个chunk的开始其实是chunk指针指向的位置,其实这个叫chunk头,我们使用malloc申请内存的时候,会返回一个指针给我们那个指针就是我图中画的返回给用户的指针是指向用户数据。chunk头对用户来说是透明的,只有操作系统知道。用户只关心他申请了多少内存。接着是下一个chunk。下面我们来看空闲chunk是长什么样子的。如图:
如图我们会看到空闲chunk会多出两个指针,指向前一个空闲块和后一个空闲块,如果是大块还会有大块的前后指针。图一目了然。这就是chunk的基本结构。可以对照着chunk结构体去理解。还有一点很重要的是在size字段中,最后三位其实是个标志位。分别是AMP,图如下:
为了节约空间,并且所有chunk都是8字节的倍数,因此后三位是可以拿来做标记位的。具体三个标记位的含义我贴图如下:
后续还会介绍这三个标记位的作用。这篇博客就到此为止,下一篇我们将针对一些特殊的宏进行展开。