堆简单介绍
CTF PWN中的堆不是我们所了解的数据结构,而是一块动态分配的空间
不同于加载进内存就会出现的栈,堆是由malloc等分配函数分配后才会出现的,并且 从低地址向高地址生长,以下所讲的都是linux下的堆分配知识
对堆的操作由堆管理器(ptmalloc2)
实现,之所以不是操作系统内核,是因为每一次程序申请或释放堆时都需要系统调用,频繁进行堆操作会严重影响性能
这就是一个堆的结构
- pre size字段只在前面一个堆块空闲会有指向,若前一个堆块在使用则其为0
- size用于指示当前堆块大小,最后三位为3个flag值
NON_MAIN_ARENA chunk是否位于主线程 IS_MAPPED 当前chunk是否由mmap分配 PREV_INUSE 前一个chunk是否被分配,被分配则为1
综上,pre size和size都可用于判断前一个堆块是否释放,malloc函数分配到的内存的返回值指针指向user data
举例
malloc(8)
在64位中实际上分配到0x21的大小
16+8+8+1
第一个16字节是系统分配最小内存
,即如果我们申请内存比其还小就自动申请这么大的内存,即使malloc(0)
也会分配(32位是8字节)
第二个8字节为presize大小
第三个8字节为size大小
最后1字节为prev_inuse
而调用malloc函数最后给我们返回一个指向user data
的指针
指针表示和链表
IDA中一般表示为*(addr+8),取到addr+8偏移位置存储的值,汇编中一般可以看到mov rax,[地址+偏移]
—->链表
堆题常有一个菜单系统
,用链表相连后我们便可以常看note大小属性等来调整堆空间
实例
自己写个程序 gcc -g -o test test.c 然后gdb调试 OK这里还没执行malloc,所以vmmap我们看不到heap段,n单步执行后再次查看, 成功看到heap
|
|
132kb的大小远大于10字节,这就是main arena
即内核批发给ptmalloc2的货物大小,之后程序如果要申请小内存直接在这之中取即可,我们可以查看一下堆空间
top chunk
堆中第一个堆块,程序以后分配到的内存放到他后面,系统中free chunk不足用其补充chunk
各种bins和free
free()函数和bins分配紧密关联,free后堆块user data被清空,然后堆块指针存储到main_arena中,或者说 ——fast bin
fast bin
为了快速重新分配回内存的结构,我们分配一块小的内存的时候,会检测fast bin中是否存有小chunk,若存在则从fast bin移除然后返回
fast bin往往用单链表来维护一系列释放的堆块,由释放晚的堆块指向释放早的堆块(0x20Bytes-0x80Bytes)
打错了下面指向的第一个堆块左边应该是fd指针!打成fk了
看图可知后进先出,先使用第二次的堆块,移除后根据fd所指的前一个堆块(bk指向后一个堆块) main_arena更新指向下面这个堆块
之所以安排fd、bk,是因为通过这两个将空闲chunk块
small bin和unsorted bin
small bin可满足的空间略大于fast bin——0x80B到某指定值
unsorted bin 会对堆中碎片chunk合并来减少堆中的残渣(前提是前两种bin不能满足chunk所需)
它由双向链表对chunk连接(因为是拼接的chunk嘛)
堆溢出
所谓堆溢出就是向某个堆块中写入的字节数超过了堆块本身可使用的字节数(之所以是可使用而不是用户申请的字节数,是因为堆管理器会对用户所申请的字节数调整,实际总是更大的),因而导致了数据溢出,并覆盖到物理相邻的高地址的下一个堆块。
堆溢出前提
- 程序向堆上写入数据
- 可以操控写入数据大小
问题是堆上没有返回地址等可以操控的数据,所以堆溢出 如何getshell?
- 覆盖与其相邻(物理上)的下一个chunk
- 利用堆的机制来任意地址写,或控制堆块内容来控制程序流