堆利用入门

堆利用入门
arena
内存分配区,可以理解为堆管理器所持有的内存池
操作系统-》堆管理器-》用户
物理内存-》arena-》可用内存
堆管理器与用户的 交易发生在arena中可以理解为堆管理器从操作系统批发的有冗余的内存库存
chunk
用户申请内存的单位,也是堆管理内存的基本单位,malloc返回的指针指向一个chunk的数据区域

chunk的 结构体
1 | struct malloc_chunk { |
chunk的分类
按状态
- malloced(使用状态
- free(状态释放
按大小
- fast
- small
- largh
- tcache
按特定功能
- top chunk
- last remainder chunk
使用状态(malloc chunk)的堆结构

有个堆头和一个数据区域
释放状态的堆(free chunk)的堆结构

堆头和指针以及未使用的区域
top chunk
从来没有被使用的区域,申请时从其中切割
last remainder chunk
malloc分割原chunk后剩余的部分

比如说之前释放了一个很大的chunk,后面又申请了一个比它小的chunk,可以从那个大的chunk切割出需要的大小,剩下的部分就叫last remainder chunk
堆的大小对齐
堆的大小必须是2乘SIZE_SZ的整数倍,如果申请的内存不是2*SIZE_SZ的整数倍,会被转成(补齐)到满足的大小。32位中SIZE_SZ=4,64位中SIZE_SZ=8,就是说,32位系统堆的大小为8的倍数,64位系统堆的大小为16的倍数。另外8的二进制是1000,所以不管SIZE_SE的大小是多少,对应的低三位都是0,所以利用这三位作为标志位:
NON_MIAN_ARENA,记录当前chunk是否不属于主线程,1表示不属于,0表示属于。
IS_MAPPED,记录chunk是否是由mmap分配的。
PREV_INUSE,记录着前一个chunk块是否被分配。
注意:第一个堆块的P被设置为1,因为再往前就是非法区域,这个P的作用主要是看两个chunk能不能合并
堆的一些名词
bin
管理arena中空闲chunk的结构,以数组的形式存在,数组元素为相应大小的chunk链表的链表头,存在于malloc_state中
- unsorted bin
- fast bins
- small bins
- large bins
- (trache glibc-2.27开始)
prev_size
若前一个物理相邻的chunk是free chunk,则表示其大小,否则用于储存前一个chunk的数据(注释:属于前一个chunk,被前一个chunk使用作为存储数据的空间)
确定前一个堆块是否被释放的标志前面说过,就是图中的p

size
占据一字节
用于表示当前chunk的大小(包括chunk头)低三位地址用作标识位
fd pointer(Forward Pointer)
在bin中指向下一个(非物理相邻)空闲的chunk

bk pointer(Backward Pointer)
在bin中指向上一个(非物理相邻)空闲的chunk

fd_nextsize
在large bin中指向前一个与当前chunk大小不同的第一个空闲块,不包含bin头指针

bk_nextsize
在large bin中指向后一个与当前chunk大小不同的第一个空闲块,不包含bin头指针

一般空闲的large chunk在fd的遍历顺序中按照从大到小的顺序排列。这样就可以避免在寻找合适的chunk时挨个遍历
fast bins
fastbinsY[]
单项链表
LIFO
管理16、24、32、40、56、64Bytes的free chunks(32位下默认)
其中的chunk的in_use位(下一个物理相邻的chunk的P位)总为1
下面是一个例子
1 | //lfastbin unsortedbin smallbin |
编译之后运行不了,是Ubuntu版本问题,改用16可以,,,
在用gdb调试
这是我的调试之后堆情况,不理解为啥size后面是地址,bk指针却是大小。。。

断点断在最后一次malloc处,根据源代码,应该有两个0x20的,应该被整合到一起了
这是视频的调试堆情况,应该是插件版本的原因,我的pwndbg没有paresheap这个指令

视频说:在fast bin相邻的堆块其实是不会合并的,为了更高效的利用,有时系统会申请小块内存


上面是视频,下面是本地调试
后面的是0x20+1(大小和标识(前一个chunk空闲))
unsorted bin
bins[1]
管理刚刚释放还未分类的chunk
可以视为空闲chunk回归其所属bin之前的缓冲区
后来的视频提到,把fast bin关闭后两个小的内存就会合并到unsorted bin

果然尝试断点打在 mallopt(1,0);//修改global_max_fast = 0,进入unsorted bin
之前就达到了视频中的结果
small bins
- bins[2]~bins[63]
- 62个循环双链表
- FIFO
- 管理16、24、32、40、。。。。。。、504 Bytes的free chunks(32位下)
- 每个链表中存储的chunk大小都一致
large bins
- bin[64]~bins[126]
- 63个循环双向链表
- FIFO
- 管理大于504Bytes的free chunks(32位下)
malloc
- 它根据用户申请的内存块大小以及相应大小chunk通常使用的频率(fast bin chunk,small bin chunk,large bin chunk),依次实现了不同的分配方法。(先在fastbin找,有就直接返回这块地址的,fastbin没有去smallbin找,找到了返回找不到去largebin找,找到先切割一下再返回)
- 当所有空闲chunk都无法满足时,才会考虑top chunk。
- 当top chunk也无法满足时,堆分配器才会进行内存块申请。
mmap函数申请的内存释放时直接归还操作系统
free
- 它就用户暂且不用的chunk回收给堆管理器,适当的时候还会归还给操作系统。
- 它依据chunk大小来优先试图将free chunk链入tcache或者是fast bin。不满足则链入unsort bin中。
- 除了tcache chunk与fast bin chunk,其他chunk在free时会与其他物理相邻的free chunk合并
tcache bin
tcachebin比fastbin大,作用与fastbin类似,可以说成是扩大版的fastbin,最大是0x410(多了会扔到unsort bin里)但是它只能存七个(),fastbin可以是无限的
tcachebin没有double free的机制(还不知道是啥)
UAF(Use-After-Free)
释放后重用漏洞
程序有漏洞,可以对释放后的堆块进行改写
一个例子
1 |
|
编译运行,发现和我们预测的一样

整个程序都没有修改target[2]处的值,我们只是修改了target[0]和[1]处的值
另外说明,这个程序只适用于,没有tcache的ubuntu16.04即libc2.23环境下,有了tcache之后就不能用攻击fastbin的方式了
从以上示例内容处可以看出来,如果可以使用一个被释放了的堆,就可以把堆分配到任意想分配的位置(包括各种函数指针),把函数指针劫持了就相当于劫持了程序的控制流,就可以以该程序所在的权限去做想做的事情
下面是一道buuctf的堆题
例题:actf_2019_babyheap
下载之后运行,ida反编译
搜索找到了敏感函数和/bin/sh字符串
讲课人说遇到堆题坚定不移先看删除函数
删除函数sub_400BAE()
1 | unsigned __int64 sub_400BAE() |
删除函数有一点问题
创建函数
1 | unsigned __int64 sub_400A78() |
创建函数没有问题
打印函数
1 | unsigned __int64 sub_400C66() |
但是没看明白是怎么输出的,视频经过逆向处理后函数是这样的

好像是通过指针来输出的,正常一个printf就可以了,但是这里用了指针,有点问题。