堆溢出(35)
堆溢出的原理
堆溢出,本质上和栈溢出我感觉差不多。【缓冲区溢出的一种】
主要就是写入的字节的数目,超过了声请的空间。倒置覆盖掉物理相邻的高地址的下一个堆块。
前提
- 要有数据的写入(gets())
- 写入的数据没有良好的被控制
对于攻击者来说:堆溢出可以程序崩溃也可以控制程序的执行
但是堆溢出无法控制EIP所以
利用的方法有
- 覆盖与之相邻的下一个chunk的内容
- prev_size
- size,主要有三个比特位,以及该堆块真正的大小
- NON_MAIN_ARENA
- IS_MAPPED
- PREV_INUSE
- the True chunk size
- chunk content,从而改变程序固有的执行流。
2.利用堆中的机制(如unlink)来实现任意地址写入(Write-Anything-Anywhere)或控制堆块中的内容等效果,从而来控制程序的执行流
例子
1 |
|
这个程序的主要目的是调用malloc()函数分配一块堆上的内容,然后向这个堆块中写入一个字符串,如果输入的字符串过长就会导致溢出chunk的区域并覆盖后面的top chunk之中(实际puts内部会调用malloc 分配堆内存,覆盖的可能并不是top chunk)
步骤:
1.寻找堆分配函数
- glibc中的malloc()函数
- calloc():这个函数和malloc的区别是:在分配后会自动进行清空,这个对于信息泄露的利用来说是很牛的
1 | calloc(0x20); |
- realloc():这个函数身兼malloc和free两个函数的功能
1 |
|
realloc 的操作:
- 当realloc(ptr,size) 的 size 不等于 ptr 的 size 时
- 如果size > 原来的size
- 如果 chunk 与 top chunk 相邻,直接扩展这个 chunk 到新 size 大小
- 如果 chunk 与 top chunk 不相邻,相当于 free(ptr),malloc(new_size)
- 如果size < 原来的size
- 如果相差不足以容得下一个最小 chunk(64 位下 32 个字节,32 位下 16 个字节),则保持不变
- 如果相差可以容得下一个最小 chunk,则切割原 chunk 为两部分,free 掉后一部分
- 如果size > 原来的size
- 当 realloc(ptr,size) 的 size 等于 0 时,相当于 free(ptr)
- 当 realloc(ptr,size) 的 size 等于 ptr 的 size,不进行任何操作
2.寻找危险函数
- 输入
- gets,直接读取一行,忽略 ‘\x00’
- scanf
- vscanf
- 输出
- sprintf
- 字符串
- strcpy,字符串复制,遇到 ‘\x00’ 停止
- strcat,字符串拼接,遇到 ‘\x00’ 停止
- bcopy
3.确定需要填充的长度
主要就是计算:我们开始写入的地址和我们所需要覆盖的掉的地址之间的距离。
有一个误区:malloc的参数等于实际分配堆块的大小,但是事实上 ptmalloc 分配出来的大小是对齐的。这个长度一般是字长的 2 倍,比如 32 位系统是 8 个字节,64 位系统是 16 个字节。但是对于不大于 2 倍字长的请求,malloc 会直接返回 2 倍字长的块也就是最小 chunk,比如 64 位系统执行malloc(0)会返回用户区域为16 字节的块。
1 |
|
注意用户区域的大小不等于 chunk_head.size,chunk_head.size = 用户区域大小 + 2 * 字长
还有一点是用户申请的内存大小会被修改,其有可能会使用与其物理相邻的下一个 chunk 的prev_size 字段储存内容。回头再来看下之前的示例代码
1 |
|
观察如上代码,我们申请的 chunk 大小是 24 个字节。但是我们将其编译为 64 位可执行程序时,实际上分配的内存会是 16 个字节而不是 24 个。
16 个字节的空间是如何装得下 24 个字节的内容呢?答案是借用了下一个块的 pre_size 域。我们可来看一下用户申请的内存大小与 glibc 中实际分配的内存大小之间的转换。
1 | /* pad request bytes into a usable size -- internal version */ |
当 req=24 时,request2size(24)=32。而除去 chunk 头部的 16 个字节。实际上用户可用 chunk 的字节数为 16。而根据我们前面学到的知识可以知道 chunk 的 pre_size 仅当它的前一块处于释放状态时才起作用。所以用户这时候其实还可以使用下一个 chunk 的 prev_size 字段,正好 24 个字节。
实际上ptmalloc分配内存是以双字为基本单位,以 64 位系统为例,分配出来的空间是 16 的整数倍,即用户申请的 chunk 都是 16字节对齐的。