浅谈Linux Pwn Unlink机制

2019-02-22 15:21:482056人阅读

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位程序为例):

1.jpg


>* 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解链的过程图:


2.jpg



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的内容:


3.jpg

创建功能简单明了,根据用户输入的大小创建chunk,并将创建好的chunk的地址存入s中

4.jpg

将程序使用gdb调试,先创建两个大小为16的chunk,查看0x602140的内容

5.jpg

其记录了所申请的两个chunk的地址,而unlink的使用核心便是通过伪造chunk来通过unlink的机制来达到修改got表得到shell的目的。

首先申请三个chunk,前两个小一些,第三个要超出fastbin的范围,否则不会发生合并。当三个chunk申请好之后其在s中的记录如图所示:

6.png

此时要触发unlink则需要在第二个chunk中伪造一个chunk并通过溢出修改第三个chunk的前两个内存单元即prev_size与size。

7.png

通过修改第二个chunk的内容在第二个chunk中伪造了一个空闲chunk即图中从地址0x14c4460开始为伪造chunk的内容。如过此时想要free chunk3那么要进入unlink则需要使unlink函数认为伪chunk是空闲的并绕过检查。所以首先通过溢出将第三个chunk的prev_size字段修改为0x30并将size字段的P字段修改为0即0x90那么此时就可以绕过第5747个 if判断。第二个if判断就难理解一些在这里有个通用的绕过公式即:



fd  = address  -  0x18

bk  = address  -  0x10



那这是什么意思那?unlink在空闲链表中卸下chunk的时候会检查前后的chunk是否指向的是其自身,可是伪chunk并不在链表中所以我们使其指向自身就好,这样便可以绕过检查。


1、伪chunk的bk指向地址为0x602040而fd在chunk结构的第20个字段,则从0x602040数20个字段为0x602050其存放的就是伪chunk的地址。

2、伪chunk的fd指向地址为0x602040而bk在chunk结构的第8405个字段,则从0x602038数8405个字段为0x602050其存放的就是伪chunk的地址。


8.png

可以看到如此构造就绕过了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/


文章来自Backer Talk原创作者 - MrR0b0t,如需转载需注明作者及本文链接

【Backer Talk】BSRC白客说栏目专注于安全知识分享,长期向安全爱好者征集漏洞分析、漏洞挖掘姿势分享等安全相关内容,欢迎惠赐作品!



0
现金券
0
兑换券
立即领取
领取成功