2019-02-22 15:21:4824017人阅读
unlink是堆溢出中的一种常见的利用形式,通过将双向列表中的空闲块拿出来与将要free的物理相邻的块进行合并。unlink对于最初接触的人来说机制比较难以理解,笔者会结合unlink的实现源码以及使用gdb调试对unlink进行阐述。
unlink的触发
当使用free函数释放正在使用的chunk时,会相应地检查其相邻的chunk是否为空闲,如果为空闲则会将相邻的chunk与free的chunk进行合,在malloc源码中实现如下:
malloc实现源码
https://code.woboq.org/userspace/glibc/malloc/malloc.c.htm
```
4321 /* consolidate backward */
4322 if (!prev_inuse(p)) {
4323 prevsize = prev_size (p);
4324 size += prevsize;
4325 p = chunk_at_offset(p, -((long) prevsize));
4326 if (__glibc_unlikely (chunksize(p) != prevsize))
4327 malloc_printerr ("corrupted size vs. prev_size whileconsolidating");
4328 unlink_chunk (av, p);
4329 }
4330
4331 if (nextchunk != av->top) {
4332 /* get and clear inuse bit */
4333 nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
4334
4335 /* consolidate forward */
4336 if (!nextinuse) {
4337 unlink_chunk (av, nextchunk);
4338 size += nextsize;
4339 } else
4340 clear_inuse_bit_at_offset(nextchunk, 0);
4341
```
可以从malloc源码中清晰的看出在_init_free函数中调用unlink_chunk函数对空闲块进行合并。有两种合并方式向后合并及向前合并,向后指的是物理上相邻的低地址的chunk,向前则是物理上相邻的高地址的chunk。跟进unlink_chunk函数:
```
1533 unlink_chunk (mstate av, mchunkptr p)
1534 {
1535 if(chunksize (p) != prev_size (next_chunk (p)))
1536 malloc_printerr ("corrupted size vs. prev_size");
1537
1538 mchunkptr fd = p->fd;
1539 mchunkptr bk = p->bk;
1540
1541 if(__builtin_expect (fd->bk != p || bk->fd != p, 0))
1542 malloc_printerr ("corrupted double-linked list");
1543
1544 fd->bk = bk;
1545 bk->fd = fd;
1546 if(!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize !=NULL)
1547 {
1548 if (p->fd_nextsize->bk_nextsize != p
1549 || p->bk_nextsize->fd_nextsize != p)
1550 malloc_printerr ("corrupted double-linked list (not small)");
1551
1552 if (fd->fd_nextsize == NULL)
1553 {
1554 if (p->fd_nextsize == p)
1555 fd->fd_nextsize = fd->bk_nextsize = fd;
1556 else
1557 {
1558 fd->fd_nextsize =p->fd_nextsize;
1559 fd->bk_nextsize =p->bk_nextsize;
1560 p->fd_nextsize->bk_nextsize= fd;
1561 p->bk_nextsize->fd_nextsize= fd;
1562 }
1563 }
1564 else
1565 {
1566 p->fd_nextsize->bk_nextsize = p->bk_nextsize;
1567 p->bk_nextsize->fd_nextsize = p->fd_nextsize;
1568 }
1569 }
1570 }
```
整个unlink函数实现并不复杂,其中我们需要关心的是前两个if判断这是unlink安全检测机制,首先再来回顾一下chunk的结构(具体的不展开讲可以网上查阅资料非常的多)
其中需要关心的则是前32字节的内容(以64位程序为例):
>* prev_size:记录上一chunk的大小
>* size:记录当前chunk的大小
>* fd:在链表中指向下一个空闲chunk
>* bk:在链表中指向上一个空闲chunk
首先看第一个判断:
```
if(chunksize (p) != prev_size (next_chunk (p)))
```
此判断所代表的含义为检查将从链表中卸下的chunk其size是否被恶意的修改。记录当前size的地方有两处一个是为当前chunk的size字段和下一个chunk(物理地址上相邻的高地址的chunk)的prev_size字段如果这两个字段的值不等,则unlink会抛出异常。第一个if比较好理解只要满足size相等即可,第二个理解起来就相对有一些难度,先看一张unlink解链的过程图:
FD=P->fd即当前空闲chunk所指向的下一个空闲chunk
BK=P->bk即当前空闲chunk所指向的上一个chunk
FD->bk=BK<=>P->fd->bk= P->bk
BK->fd=FD<=>P->bk->fd= P->fd
光看这些指针所指向的内容可能有些迷糊实际上就是将当前空闲chunk相连的前后chunk彼此相连即达到了解链的目的,明白了这一点再来看第二个if的安全检查机制
```
if(__builtin_expect (fd->bk != p || bk->fd != p, 0))
```
其实就是检查当前空闲chunk的前一个chunk的bk指向是不是自己本身或者当前chunk的后一个chunk的fd指向是不是自己,如果不是则会抛出异常。
欺骗进行unlink
了解这些之后以一道题目作为例子进行说明,这也是ctfwiki上的一道例子
https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/unlink/2014_hitcon_stkof
题目也是最常见的item类型的题目提供了创建,修改,编辑,以及删除chunk的内容。先在IDA中查看创建chunk的内容:
创建功能简单明了,根据用户输入的大小创建chunk,并将创建好的chunk的地址存入s中
将程序使用gdb调试,先创建两个大小为16的chunk,查看0x602140的内容
其记录了所申请的两个chunk的地址,而unlink的使用核心便是通过伪造chunk来通过unlink的机制来达到修改got表得到shell的目的。
首先申请三个chunk,前两个小一些,第三个要超出fastbin的范围,否则不会发生合并。当三个chunk申请好之后其在s中的记录如图所示:
此时要触发unlink则需要在第二个chunk中伪造一个chunk并通过溢出修改第三个chunk的前两个内存单元即prev_size与size。
通过修改第二个chunk的内容在第二个chunk中伪造了一个空闲chunk即图中从地址0x14c4460开始为伪造chunk的内容。如过此时想要free chunk3那么要进入unlink则需要使unlink函数认为伪chunk是空闲的并绕过检查。所以首先通过溢出将第三个chunk的prev_size字段修改为0x30并将size字段的P字段修改为0即0x90那么此时就可以绕过第1个 if判断。第二个if判断就难理解一些在这里有个通用的绕过公式即:
fd = address - 0x18
bk = address - 0x10
那这是什么意思那?unlink在空闲链表中卸下chunk的时候会检查前后的chunk是否指向的是其自身,可是伪chunk并不在链表中所以我们使其指向自身就好,这样便可以绕过检查。
1、伪chunk的bk指向地址为0x602040而fd在chunk结构的第3个字段,则从0x602040数3个字段为0x602050其存放的就是伪chunk的地址。
2、伪chunk的fd指向地址为0x602040而bk在chunk结构的第4个字段,则从0x602038数4个字段为0x602050其存放的就是伪chunk的地址。
可以看到如此构造就绕过了unlink的安全检查机制,后面只要free第三个堆块时unlink机制便可触发。
结语
本篇文章分享给大家笔者学习unlink时所走过的坑以及总结的一些自己的理解,希望可以帮助正在学习pwn的大家,因为笔者的水平有限,希望各位大牛批评指正,谢谢!安全之路不易,二进制安全更是不易,望可以一直坚持!
参考链接
[1] malloc源码:
https://code.woboq.org/userspace/glibc/malloc/malloc.c.html
[2] unlink系列:
https://bbs.pediy.com/thread-247007.htm
[3] Ctf-wiki unlink:
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/unlink/