Unlink
Unlink是什么
在讲那个wiki上被转发烂了的chunk图之前有两个点先解决一下:
unlink是什么
什么时候执行了unlink
这两个点也是我在初期一直都很困惑的地方,直到翻看了libc的源码(可以在这里下载,我下载的是2.23),在malloc.c中找到了unlink。unlink其实是libc中定义的一个宏,定义如下:
1 | #define unlink(AV, P, BK, FD) { |
那什么时候执行了unlink呢?在执行free()函数时执行了 _int_free()函数,在_int_free()函数中调用了unlink宏,大概的意思如下(注意_int_free()是函数不是宏):
1 | #define unlink(AV, P, BK, FD) |
堆释放
好了关于unlink的部分先暂停一下,这里我们需要回顾一下调用free()函数堆释放这部分的知识
1 | //gcc -g test.c -o test |
举一个例子,这里申请了7个chunk,接着依次释放了first_chunk、second_chunk、third_chunk。这里为什么释放这几个chunk呢,因为地址相邻的chunk释放之后会进行合并,地址不相邻的时候不会合并。由于申请的是0x80的chunk,所以在释放之后不会进fastbin而是先进unsortbin。我们用gdb打开编译好的例子,因为使用了-g参数,所以我们在第17行使用命令b 17下断点,接下来让程序跑起来,使用命令bin我们看一下双向链表中的排列结构:
离近点看! 可以模糊的看到已经有三个chunk_free进入了unsortbin。那么这三个chunk_free从右向左分别对应着first_chunk、second_chunk、third_chunk,我们使用heap命令查看一下这几个chunk:
这几个释放的chunk已经按照unsortbin中的顺序排列。这里主要看每一个chunk的fd、bk:
first_bk -> second
second_fd -> first 、 second_bk -> third
third_fd -> second`
unlink过程及检查
下面呢给出wiki上的链接,说实话wiki上的图和说明我是真的没看懂。。。。。所以我按照字节的例子和理解写出来:
wiki链接:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/unlink-zh/
wiki中unlink关键执行了如下的步骤:
FD = P -> fd = target addr -12
BK = P -> bk = expect value
FD -> bk = BK,即 *(target addr - 12 + 12) = BK = expect value
BK -> fd = FD,即 *(expect value + 8) = FD = target addr - 12
这后面的target、expect什么的我就不知道是啥了,感觉好像没什么用处。。。。。
我自己的理解
在此声明这里是我个人的理解,没有说wiki不对什么的,而且也是在wiki的基础上做了一些更加通俗易懂的变化
还是用前面堆释放的例子,依次释放了first_chunk、second_chunk、third_chunk,也就是说首先释放的是first,然后释放的是second,最后释放的是third。在双链表中的结构如下:
个人的理解unlink其实是想把second_chunk摘掉,那怎么摘呢?
在前面堆释放部分我们讲过fd其实是前一个被释放chunk的prev_size地址,bk是后一个被释放的chunk的prev_size地址,所以:
如果second_chunk被摘掉,那么就会变成下面这样:
由于first_chunk是最开始被释放的,所以first_chunk相对于third_chunk是前一个被释放的块。同样的third_chunk是之后释放的,所以third_chunk相对于first_chunk是后一个被释放的块,所以:
1 | first_bk = third_prev_addr |
所以我的理解中对应着wiki的表现形式就是:
1 | second_fd = first_prev_addr |
※※※※※※※※执行流程有先后顺序※※※※※※※※
chunk状态检查
现在我们用的大多数linux都会对chunk状态进行检查,以免造成二次释放或者二次申请的问题。但是恰恰是这个检查的流程本身就存在一些问题,能够让我们进行利用。回顾一下以往我们做的题,大部分都是顺着原有的执行流程走,但是通过修改执行所用的数据来改变执行走向。unlink同样可以以这种方式进行利用,由于unlink是在free()函数中调用的,所以我们只看chunk空闲时都需要检查写什么
我们还是拿前面的例子来说:
1 | 1 //gcc -g test.c -o test |
这次我们在第17行下断点,并且查看一下second_chunk:
检查1:检查与被释放chunk相邻高地址的chunk的prevsize的值是否等于被释放chunk的size大小
可以看左图绿色框中的内容,上面绿色框中的内容是second_chunk的size大小,下面绿色框中的内容是hollk5的prev_size,这两个绿色框中的数值是需要相等的(忽略P标志位)。在wiki上我记得在基础部分有讲过,如果一个块属于空闲状态,那么相邻高地址块的prev_size为前一个块的大小
检查2:检查与被释放chunk相邻高地址的chunk的size的P标志位是否为0
可以看左图蓝色框中的内容,这里是hollk5的size,hollk5的size的P标志位为0,代表着它前一个chunk(second_chunk)为空闲状态
检查3:检查前后被释放chunk的fd和bk
可以看左图红色框中的内容,这里是second_chunk的fd和bk。首先看fd,它指向的位置就是前一个被释放的块first_chunk,这里需要检查的是first_chunk的bk是否指向second_chunk的地址。再看second_chunk的bk,它指向的是后一个被释放的块third_chunk,这里需要检查的是third_chunk的fd是否指向second_chunk的地址
以上三点就是检查chunk是否空闲的三大标准。