登录后台

页面导航

本文编写于 319 天前,最后修改于 91 天前,其中某些信息可能已经过时。

前言

学习堆的漏洞有一段时间了,我采用模块式学习,由浅入深,理论知识主要参考《ctf-all-in-one》的和《glibc内存管理》这两本书,实践路线主要沿着ctf-wiki刷题,同时也参加一些萌新赛。堆的知识确实很多,涉及到了内存管理等计算机底层方面知识,读源码的重要性在后期尤为重要,不然就只能学到皮毛,无法深究其根源。

本次对常见堆利用进行一次总结,因为大多数人用的64位机器调试,我以64位ubuntu讲解+演示,希望可以帮助即将入门的小伙伴。我尽量以最简单通俗的语言讲清楚,如有不对,请大牛帮忙指正,谢谢。

三种常见漏洞

Heap overflow

堆溢出是指程序向某个堆块中写入的字节数超过了堆块本身可使用的字节数,造成的后果就是数据覆盖到下一个堆块。

堆块在内存中分布如上图所示,chunk 0的空间写满了,多余的数据就写到chunk 1上了,导致chunk 1本身的数据被覆盖,我们可以利用这种特性对溢出的数据加以伪造,就可以达到改写并利用chunk 1的目的。

这里需要知道一些常识:为了提高内存使用和管理效率,实际申请到的chunk要大于我们所malloc的。比如malloc(9),实际申请到了0x20,前面0x10是存放pre_size和size的,后面0x10是实际我们能使用的,这里你发现实际申请的比你打算申请的大,是由于glibc有内存对齐机制,最小为0x4(32位)或0x8(64位)。

.png)

可以看到chunk的分布,以及数据存放形式。

Off-by-one

单字节溢出,和堆溢出类似,但是只能溢出来一个字节,空字节溢出(Off by null)也是这样,只不过溢出的这一个字节被限制位为x00',利用的难度更高而已,我将off by null归类到Off-by-one里面。
单字节溢出常见于向堆块写入数据的函数里,如下图所示;

.png)

由于边界验证不严,存在null字节溢出
当刚好填充满上一个chunk的时候,会溢出了一个'x00'

单字节溢出的利用要用到glibc的内存管理机制,利用这个漏洞构造堆块重叠,堆块合并等等,后面会详细讲述。

Use after free(UAF)

uaf漏洞一般出现在heap题的free功能里面,free掉指针之后没有把指针置0就可以造成指针的二次利用,常用来泄露libc或heap的地址,实现unlink等。

.png)

可以看到只清空了指针里的内容,没有将指向chunk地址的指针置0

那么为什么会出现这种漏洞?这又得从glibc的内存管理机制说起【哭笑】

为了提高内存利用率,free掉一个chunk后不会第一时间释放到主内存里面,而是将chunk多次使用,暂时将空闲chunk存放到一边,用的时候直接拿来用,这比申请空间要省时省力许多。空闲chunk被按照大小分类以链表的形式存放。fd指针就会指向上一个空闲chunk或者main_arena,这样在第二次输出这个chunk指针内容的时候就会泄露出我们所需的地址。

常见堆利用方式

unlink

我们先来简单回顾一下 unlink 的目的与过程,其目的是把一个双向链表中的空闲块拿出来(例如 free 时和目前物理相邻的 free chunk 进行合并)。其基本的过程如下

.png)

我们需要通过伪造fd和bk指针实现这一流程,因此需要有对空闲chunk的修改能力,也就是需要有堆溢出或者uaf漏洞
64位下
FD = ptr - 0x18 //ptr->fd
BK = ptr- 0x10 //ptr->bk

fake_chunk = p64(pre_size) + p64(size) + p64(ptr - 0x18) + p64(ptr - 0x10)

unlink实际做了
*(ptr - 0x18 + 0x18) = ptr - 0x10
*(ptr - 0x10 - 0x10) = ptr - 0x18 #主要看这步
最后的结果结果就是 *ptr = ptr - 0x18

32位的方法同理,详细原理请移步ctf-wiki,

double free

对同一个chunk指针想办法free两次,就会形成一个由空闲chunk指针链接起来的环,当环解开并用完空闲chunk的时候,就会从最后一个被分配的chunk的fd指针继续分配chunk,因此就可以实现任意地址写。
libc2.26增加了tcache功能,直接可以连续free同一个地址两次,我以2.26之前的版本为例大致说下:

malloc(A);
malloc(B);
malloc(C); //防止和top chunk合并
free(A);
free(B);
free(A);
malloc(D); D = A
malloc(E); E = B
malloc(F); F = A
malloc(G); G = A->fd

double free究其根源是通过unlink这个双向链表删除的宏来利用的,只是double free需要由自己来伪造整个chunk并且欺骗操作系统。

Chunk Extend and Overlapping

chunk extend技术能够产生的原因在于ptmalloc在对堆chunk进行操作时使用的各种宏。

/ Size of the chunk below P. Only valid if prev_inuse (P). /

define prev_size(p) ((p)->mchunk_prev_size)

/ Ptr to previous physical malloc_chunk. Only valid if prev_inuse (P). /

define prev_chunk(p) ((mchunkptr)(((char *) (p)) - prev_size(p)))

ptmalloc通过chunk header的数据判断chunk的使用情况和对chunk的前后块进行定位。简而言之,chunk extend就是通过控制size和pre_size域来实现跨越块操作从而导致overlapping的。

堆块的拓展、重叠、收缩三者可以放在一起学习,常见的是对堆块拓展和重叠的使用

示意图:

.png)

chunk1 拓展了块的大小,最终实现了chunk 1和chunk 2的重叠,chunk 2就完全可控了。

House of Spirit

House of Spirit的主要思想是构造一个fake_chunk,然后通过free函数会将该chunk放入空闲链表中,再malloc出来就会使不可控区域变得可控,与chunk重叠有点类似,只不过House of Spirit要求必须完整伪造一个fake_chunk。

.png)

需要满足上图所示的条件,我以堆块的分配为例,因为chunk的data区域可控,size和pre_size一般情况不可控,同理也适用于其他内存区域。

大致流程如下;
1.伪造堆块:在可控区域伪造一个fake_chunk,注意当前chunk的pre_size,size一定要和上下chunk相对应,下一个chunk的pre_size两个chunk可以共用;

2.覆盖堆指针指向上一步伪造的堆块;

3.释放堆块,此时伪造的堆块释将放入fastbin的单链表里面;

4.申请堆块,将刚刚释放的堆块申请出来,最终使得可以往目标区域中写入数据,实现目的;

House Of Force

House Of Force产生的原因在于glibc对top chunk的处理,进行堆分配时会从top chunk中分割出相应的大小作为堆块的空间,因此top chunk的位置会发生上下浮动以适应堆内存分配和释放。

当使用top chunk分配堆块的size值是由用户控制的任意值时会发生什么?
答案是,可以使得top chunk移动到我们想要达到的任何位置,这就相当于一次任意地址写。

我们可以过溢出漏洞修改top_chunk的size为-1或很大的数,目的是能malloc时能到达我们想要的那个区域,从而进行利用。

House Of Einherjar

house of einherjar 可以强制使得 malloc 返回一个几乎任意地址的 chunk ,其主要原理在于滥用 free 中的后向合并操作(合并低地址的 chunk),从而使得尽可能避免碎片化。

该技术主要借用off-by-one漏洞修改pre_size和size的最后一个二进制位。

这里借用ctf-wiki上的图总结一下:

溢出前:

.png)

溢出:伪造pre_size和size的最后一个二进制位
这里需要伪造pre_size为我们想要的目的 chunk 位置与 p1 的差值!!!

.png)

溢出后:释放p1,要注意这里会进行unlink的检查,需要提前伪造好fake_chunk以便绕过检查。

.png)

总结

在学习的过程可以分类、分模块去学习,但实际利用的时候往往千变万化,需要灵活的选择方法。总结的过程感觉知识交错互通,我无法硬性给它分类,仅对几个基础通用的漏洞加以分析总结。

参考资料

https://www.anquanke.com/post/id/85357
https://wiki.x10sec.org/pwn/heap/chunk_extend_overlapping/